feat: add comprehensive testing infrastructure
Some checks failed
CI Pipeline / Lint Code (push) Has been cancelled
CI Pipeline / Test API Package (push) Has been cancelled
CI Pipeline / Test Web Package (push) Has been cancelled
CI Pipeline / Test Shared Package (push) Has been cancelled
CI Pipeline / Build All Packages (push) Has been cancelled
CI Pipeline / Generate Coverage Report (push) Has been cancelled
Docker Build & Deploy / Build Docker Images (push) Has been cancelled
Docker Build & Deploy / Push Docker Images (push) Has been cancelled
Docker Build & Deploy / Deploy to Staging (push) Has been cancelled
Docker Build & Deploy / Deploy to Production (push) Has been cancelled
E2E Tests / End-to-End Tests (push) Has been cancelled
E2E Tests / E2E Tests (Mobile) (push) Has been cancelled
Security Scanning / NPM Audit (push) Has been cancelled
Security Scanning / Dependency License Check (push) Has been cancelled
Security Scanning / Code Quality Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
Some checks failed
CI Pipeline / Lint Code (push) Has been cancelled
CI Pipeline / Test API Package (push) Has been cancelled
CI Pipeline / Test Web Package (push) Has been cancelled
CI Pipeline / Test Shared Package (push) Has been cancelled
CI Pipeline / Build All Packages (push) Has been cancelled
CI Pipeline / Generate Coverage Report (push) Has been cancelled
Docker Build & Deploy / Build Docker Images (push) Has been cancelled
Docker Build & Deploy / Push Docker Images (push) Has been cancelled
Docker Build & Deploy / Deploy to Staging (push) Has been cancelled
Docker Build & Deploy / Deploy to Production (push) Has been cancelled
E2E Tests / End-to-End Tests (push) Has been cancelled
E2E Tests / E2E Tests (Mobile) (push) Has been cancelled
Security Scanning / NPM Audit (push) Has been cancelled
Security Scanning / Dependency License Check (push) Has been cancelled
Security Scanning / Code Quality Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
- Add Vitest for unit testing across all packages - Add Playwright for E2E testing - Add sample tests for API, Web, and Shared packages - Configure Gitea Actions CI/CD workflows (ci, e2e, security, docker) - Add testing documentation (TESTING.md) - Add Gitea Actions setup guide - Update .gitignore for test artifacts - Add test environment configuration
This commit is contained in:
241
e2e/recipes.spec.ts
Normal file
241
e2e/recipes.spec.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Recipe Management E2E Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Navigate to the app before each test
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test('should display the recipe list page', async ({ page }) => {
|
||||
// Wait for the page to load
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Check that we're on the recipe list page
|
||||
await expect(page.locator('h2')).toContainText('My Recipes');
|
||||
});
|
||||
|
||||
test('should show empty state when no recipes exist', async ({ page }) => {
|
||||
// Mock API to return empty recipe list
|
||||
await page.route('**/api/recipes', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
data: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
// Check for empty state message
|
||||
await expect(page.getByText(/No recipes yet/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should display recipes when available', async ({ page }) => {
|
||||
// Mock API to return recipes
|
||||
await page.route('**/api/recipes', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Spaghetti Carbonara',
|
||||
description: 'Classic Italian pasta',
|
||||
totalTime: 30,
|
||||
servings: 4,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Chocolate Cake',
|
||||
description: 'Rich chocolate dessert',
|
||||
totalTime: 60,
|
||||
servings: 8,
|
||||
},
|
||||
],
|
||||
total: 2,
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
// Check that recipes are displayed
|
||||
await expect(page.getByText('Spaghetti Carbonara')).toBeVisible();
|
||||
await expect(page.getByText('Chocolate Cake')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should navigate to recipe detail when clicking a recipe', async ({ page }) => {
|
||||
// Mock recipes list
|
||||
await page.route('**/api/recipes', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Test Recipe',
|
||||
description: 'Test Description',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
// Mock recipe detail
|
||||
await page.route('**/api/recipes/1', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
id: '1',
|
||||
title: 'Test Recipe',
|
||||
description: 'Detailed description',
|
||||
ingredients: [
|
||||
{ id: '1', name: 'Flour', amount: '2', unit: 'cups', order: 0 },
|
||||
],
|
||||
instructions: [
|
||||
{ id: '1', step: 1, text: 'Mix ingredients' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
// Click on the recipe
|
||||
await page.getByText('Test Recipe').click();
|
||||
|
||||
// Verify we navigated to the detail page
|
||||
await expect(page).toHaveURL(/\/recipes\/1/);
|
||||
});
|
||||
|
||||
test('should import recipe from URL', async ({ page }) => {
|
||||
// Mock import endpoint
|
||||
await page.route('**/api/recipes/import', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
recipe: {
|
||||
title: 'Imported Recipe',
|
||||
description: 'Recipe from URL',
|
||||
ingredients: [],
|
||||
instructions: [],
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
// Navigate to import page (adjust based on your routing)
|
||||
await page.goto('/import');
|
||||
|
||||
// Fill in URL
|
||||
await page.fill('input[type="url"]', 'https://example.com/recipe');
|
||||
|
||||
// Click import button
|
||||
await page.click('button:has-text("Import")');
|
||||
|
||||
// Wait for success message or redirect
|
||||
await expect(page.getByText(/Imported Recipe|Success/i)).toBeVisible({
|
||||
timeout: 5000,
|
||||
});
|
||||
});
|
||||
|
||||
test('should handle API errors gracefully', async ({ page }) => {
|
||||
// Mock API to return error
|
||||
await page.route('**/api/recipes', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 500,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
error: 'Internal server error',
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
// Check for error message
|
||||
await expect(page.getByText(/Failed to load recipes|error/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should be responsive on mobile devices', async ({ page }) => {
|
||||
// Set mobile viewport
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
|
||||
// Mock API response
|
||||
await page.route('**/api/recipes', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Mobile Recipe',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
// Verify content is visible on mobile
|
||||
await expect(page.getByText('Mobile Recipe')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Recipe Search and Filter', () => {
|
||||
test('should filter recipes by search term', async ({ page }) => {
|
||||
// This test assumes there's a search functionality
|
||||
await page.goto('/');
|
||||
|
||||
// Wait for search input to be available
|
||||
const searchInput = page.locator('input[type="search"], input[placeholder*="Search"]');
|
||||
|
||||
if (await searchInput.count() > 0) {
|
||||
await searchInput.fill('pasta');
|
||||
|
||||
// Mock filtered results
|
||||
await page.route('**/api/recipes?*search=pasta*', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Pasta Carbonara',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
// Verify filtered results
|
||||
await expect(page.getByText('Pasta Carbonara')).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user