Skip to content

Commit 1f13674

Browse files
authored
Merge pull request #115 from github-samples/copilot/fix-114
Add Playwright end-to-end tests for core website functionality using Chromium
2 parents 985e026 + 2e1d399 commit 1f13674

File tree

10 files changed

+430
-2
lines changed

10 files changed

+430
-2
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,8 @@ instance/
3737
.webassets-cache
3838
flask_session/
3939
.coverage
40-
htmlcov/
40+
htmlcov/
41+
42+
# playwright
43+
client/test-results/
44+
client/playwright-report/

client/e2e-tests/README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# End-to-End Tests for Tailspin Shelter
2+
3+
This directory contains Playwright end-to-end tests for the Tailspin Shelter website.
4+
5+
## Test Files
6+
7+
- `homepage.spec.ts` - Tests for the main homepage functionality
8+
- `about.spec.ts` - Tests for the about page
9+
- `dog-details.spec.ts` - Tests for individual dog detail pages
10+
- `api-integration.spec.ts` - Tests for API integration and error handling
11+
12+
## Running Tests
13+
14+
### Prerequisites
15+
16+
Make sure you have installed dependencies:
17+
```bash
18+
npm install
19+
```
20+
21+
### Running Tests
22+
23+
```bash
24+
# Run all tests
25+
npm run test:e2e
26+
27+
# Run tests with UI mode (for debugging)
28+
npm run test:e2e:ui
29+
30+
# Run tests in headed mode (see browser)
31+
npm run test:e2e:headed
32+
33+
# Debug tests
34+
npm run test:e2e:debug
35+
```
36+
37+
## Test Coverage
38+
39+
The tests cover the following core functionality:
40+
41+
### Homepage Tests
42+
- Page loads with correct title and content
43+
- Dog list displays properly
44+
- Loading states work correctly
45+
- Error handling for API failures
46+
47+
### About Page Tests
48+
- About page content displays correctly
49+
- Navigation back to homepage works
50+
51+
### Dog Details Tests
52+
- Navigation from homepage to dog details
53+
- Navigation back from dog details to homepage
54+
- Handling of invalid dog IDs
55+
56+
### API Integration Tests
57+
- Successful API responses
58+
- Empty dog list handling
59+
- Network error handling
60+
61+
## Configuration
62+
63+
Tests are configured in `../playwright.config.ts` and automatically start the application servers using the existing `scripts/start-app.sh` script before running tests.
64+
65+
The tests run against:
66+
- Client (Astro): http://localhost:4321
67+
- Server (Flask): http://localhost:5100

client/e2e-tests/about.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test.describe('About Page', () => {
4+
test('should load about page and display content', async ({ page }) => {
5+
await page.goto('/about');
6+
7+
// Check that the page title is correct
8+
await expect(page).toHaveTitle(/About - Tailspin Shelter/);
9+
10+
// Check that the main heading is visible
11+
await expect(page.getByRole('heading', { name: 'About Tailspin Shelter' })).toBeVisible();
12+
13+
// Check that content is visible
14+
await expect(page.getByText('Nestled in the heart of Seattle')).toBeVisible();
15+
await expect(page.getByText('The name "Tailspin" reflects')).toBeVisible();
16+
17+
// Check the fictional organization note
18+
await expect(page.getByText('Tailspin Shelter is a fictional organization')).toBeVisible();
19+
});
20+
21+
test('should navigate back to homepage from about page', async ({ page }) => {
22+
await page.goto('/about');
23+
24+
// Click the "Back to Dogs" button
25+
await page.getByRole('link', { name: 'Back to Dogs' }).click();
26+
27+
// Should be redirected to homepage
28+
await expect(page).toHaveURL('/');
29+
await expect(page.getByRole('heading', { name: 'Welcome to Tailspin Shelter' })).toBeVisible();
30+
});
31+
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test.describe('API Integration', () => {
4+
test('should fetch dogs from API', async ({ page }) => {
5+
// Mock successful API response
6+
await page.route('/api/dogs', route => {
7+
route.fulfill({
8+
status: 200,
9+
contentType: 'application/json',
10+
body: JSON.stringify([
11+
{ id: 1, name: 'Buddy', breed: 'Golden Retriever' },
12+
{ id: 2, name: 'Luna', breed: 'Husky' },
13+
{ id: 3, name: 'Max', breed: 'Labrador' }
14+
])
15+
});
16+
});
17+
18+
await page.goto('/');
19+
20+
// Check that mocked dogs are displayed
21+
await expect(page.getByText('Buddy')).toBeVisible();
22+
await expect(page.getByText('Golden Retriever')).toBeVisible();
23+
await expect(page.getByText('Luna')).toBeVisible();
24+
await expect(page.getByText('Husky')).toBeVisible();
25+
await expect(page.getByText('Max')).toBeVisible();
26+
await expect(page.getByText('Labrador')).toBeVisible();
27+
});
28+
29+
test('should handle empty dog list', async ({ page }) => {
30+
// Mock empty API response
31+
await page.route('/api/dogs', route => {
32+
route.fulfill({
33+
status: 200,
34+
contentType: 'application/json',
35+
body: JSON.stringify([])
36+
});
37+
});
38+
39+
await page.goto('/');
40+
41+
// Check that empty state message is displayed
42+
await expect(page.getByText('No dogs available at the moment')).toBeVisible();
43+
});
44+
45+
test('should handle network errors', async ({ page }) => {
46+
// Mock network error
47+
await page.route('/api/dogs', route => {
48+
route.abort('failed');
49+
});
50+
51+
await page.goto('/');
52+
53+
// Check that error message is displayed
54+
await expect(page.getByText(/Error:/)).toBeVisible({ timeout: 10000 });
55+
});
56+
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test.describe('Dog Details', () => {
4+
test('should navigate to dog details from homepage', async ({ page }) => {
5+
await page.goto('/');
6+
7+
// Wait for dogs to load
8+
await page.waitForSelector('.grid a[href^="/dog/"]', { timeout: 10000 });
9+
10+
// Get the first dog link
11+
const firstDogLink = page.locator('.grid a[href^="/dog/"]').first();
12+
13+
// Get the dog name for verification
14+
const dogName = await firstDogLink.locator('h3').textContent();
15+
16+
// Click on the first dog
17+
await firstDogLink.click();
18+
19+
// Should be on a dog details page
20+
await expect(page.url()).toMatch(/\/dog\/\d+/);
21+
22+
// Check that the page title is correct
23+
await expect(page).toHaveTitle(/Dog Details - Tailspin Shelter/);
24+
25+
// Check for back button
26+
await expect(page.getByRole('link', { name: 'Back to All Dogs' })).toBeVisible();
27+
});
28+
29+
test('should navigate back to homepage from dog details', async ({ page }) => {
30+
// Go directly to a dog details page (assuming dog with ID 1 exists)
31+
await page.goto('/dog/1');
32+
33+
// Click the back button
34+
await page.getByRole('link', { name: 'Back to All Dogs' }).click();
35+
36+
// Should be redirected to homepage
37+
await expect(page).toHaveURL('/');
38+
await expect(page.getByRole('heading', { name: 'Welcome to Tailspin Shelter' })).toBeVisible();
39+
});
40+
41+
test('should handle invalid dog ID gracefully', async ({ page }) => {
42+
// Go to a dog page with an invalid ID
43+
await page.goto('/dog/99999');
44+
45+
// The page should still load (even if no dog is found)
46+
await expect(page).toHaveTitle(/Dog Details - Tailspin Shelter/);
47+
48+
// Back button should still be available
49+
await expect(page.getByRole('link', { name: 'Back to All Dogs' })).toBeVisible();
50+
});
51+
});

client/e2e-tests/homepage.spec.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test.describe('Tailspin Shelter Homepage', () => {
4+
test('should load homepage and display title', async ({ page }) => {
5+
await page.goto('/');
6+
7+
// Check that the page title is correct
8+
await expect(page).toHaveTitle(/Tailspin Shelter - Find Your Forever Friend/);
9+
10+
// Check that the main heading is visible
11+
await expect(page.getByRole('heading', { name: 'Welcome to Tailspin Shelter' })).toBeVisible();
12+
13+
// Check that the description is visible
14+
await expect(page.getByText('Find your perfect companion from our wonderful selection')).toBeVisible();
15+
});
16+
17+
test('should display dog list section', async ({ page }) => {
18+
await page.goto('/');
19+
20+
// Check that the "Available Dogs" heading is visible
21+
await expect(page.getByRole('heading', { name: 'Available Dogs' })).toBeVisible();
22+
23+
// Wait for dogs to load (either loading state, error, or actual dogs)
24+
await page.waitForSelector('.grid', { timeout: 10000 });
25+
});
26+
27+
test('should show loading state initially', async ({ page }) => {
28+
await page.goto('/');
29+
30+
// Check that loading animation is shown initially
31+
// Look for the loading skeleton cards
32+
const loadingElements = page.locator('.animate-pulse').first();
33+
34+
// Either loading should be visible initially, or dogs should load quickly
35+
try {
36+
await expect(loadingElements).toBeVisible({ timeout: 2000 });
37+
} catch {
38+
// If loading finishes too quickly, that's fine - check for dog content instead
39+
await expect(page.locator('.grid')).toBeVisible();
40+
}
41+
});
42+
43+
test('should handle API errors gracefully', async ({ page }) => {
44+
// Intercept the API call and make it fail
45+
await page.route('/api/dogs', route => {
46+
route.fulfill({
47+
status: 500,
48+
contentType: 'application/json',
49+
body: JSON.stringify({ error: 'Internal Server Error' })
50+
});
51+
});
52+
53+
await page.goto('/');
54+
55+
// Check that error message is displayed
56+
await expect(page.getByText(/Failed to fetch data/)).toBeVisible({ timeout: 10000 });
57+
});
58+
});

client/package-lock.json

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
"dev": "astro dev",
77
"build": "astro build",
88
"preview": "astro preview",
9-
"astro": "astro"
9+
"astro": "astro",
10+
"test:e2e": "playwright test",
11+
"test:e2e:ui": "playwright test --ui",
12+
"test:e2e:debug": "playwright test --debug",
13+
"test:e2e:headed": "playwright test --headed",
14+
"test:e2e:chromium": "playwright test --project=chromium"
1015
},
1116
"dependencies": {
1217
"@astrojs/node": "^9.1.3",
@@ -17,6 +22,7 @@
1722
"typescript": "^5.8.2"
1823
},
1924
"devDependencies": {
25+
"@playwright/test": "^1.49.1",
2026
"@types/node": "^22.13.11",
2127
"autoprefixer": "^10.4.21",
2228
"postcss": "^8.5.3",

0 commit comments

Comments
 (0)