Files
basil/e2e/recipes.spec.ts
Paul R Kartchner 554b53bec7
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
feat: add comprehensive testing infrastructure
- 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
2025-10-28 02:03:52 -06:00

242 lines
6.4 KiB
TypeScript

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();
}
});
});