Skip to content

Commit 07858ab

Browse files
committed
Enhance E2E Testing and Documentation
1 parent 752cdce commit 07858ab

20 files changed

+596
-136
lines changed

.github/copilot-instructions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ pets-workshop/
6565
- **Data Fetching**: Fetch data on the server side when possible
6666
- **Styling**: Use Tailwind utility classes, avoid custom CSS unless necessary
6767
- **Routing**: File-based routing through Astro's pages directory
68+
- **Test Identifiers**: Always include `data-testid` attributes for E2E testing resilience (see [`test-identifiers.md`](.github/instructions/test-identifiers.md))
6869

6970
### Database Patterns
7071
- **Models**: Use SQLAlchemy declarative base with proper relationships
@@ -75,6 +76,7 @@ pets-workshop/
7576
- **E2E Tests**: Playwright tests in `client/e2e-tests/` cover full user workflows
7677
- **Test Structure**: Organize tests by page/feature (homepage, dog-details, API integration)
7778
- **Test Commands**: Use `npm run test:e2e` for all tests, `npm run test:e2e:ui` for debugging
79+
- **Test Identifiers**: Always use `data-testid` attributes for reliable element selection (see [`test-identifiers.md`](.github/instructions/test-identifiers.md))
7880
- **Server Tests**: Python unittest framework for backend API testing
7981
- **Test Coverage**: Include tests for user interactions, API responses, and error handling
8082

.github/instructions/playwright.instructions.md

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,37 @@ Essential patterns for writing effective Playwright tests in the Tailspin Shelte
99
## Core Principles
1010

1111
1. **Test User Workflows** - Focus on complete user journeys, not implementation details
12-
2. **Use Semantic Locators** - Prefer `getByRole()`, `getByText()`, `getByLabel()` over CSS selectors
13-
3. **Handle Async Behavior** - Always account for loading states and API calls
12+
2. **Use Test IDs First** - Always prefer `data-testid` attributes for reliable element identification
13+
3. **Semantic Locators Second** - Use `getByRole()`, `getByText()`, `getByLabel()` when test IDs aren't available
14+
4. **Handle Async Behavior** - Always account for loading states and API calls
15+
16+
> 📋 **Reference**: See [`test-identifiers.md`](./test-identifiers.md) for complete list of available test IDs
1417
1518
## Locator Patterns
1619

17-
### Preferred Approach
20+
### Preferred Approach (In Order of Priority)
1821
```typescript
19-
// ✅ Semantic locators
20-
await expect(page.getByRole('heading', { name: 'Welcome to Tailspin Shelter' })).toBeVisible();
22+
// ✅ Test IDs (most reliable)
23+
await page.getByTestId('dog-card-1').click();
24+
await expect(page.getByTestId('homepage-title')).toBeVisible();
25+
await page.getByTestId('back-to-dogs-button').click();
26+
27+
// ✅ Semantic locators (when test IDs aren't available)
28+
await page.getByRole('heading', { name: 'Welcome to Tailspin Shelter' }).click();
2129
await page.getByRole('link', { name: 'Back to All Dogs' }).click();
22-
await expect(page.getByText('Find your perfect companion')).toBeVisible();
2330

24-
// ✅ Test IDs when needed
25-
await page.getByTestId('dog-card-123').click();
31+
// ✅ Combined approach (test ID + semantic validation)
32+
const dogCard = page.getByTestId('dog-card-1');
33+
await expect(dogCard.getByTestId('dog-name-1')).toContainText('Buddy');
34+
await dogCard.click();
2635
```
2736

2837
### Avoid
2938
```typescript
30-
// ❌ Fragile selectors
39+
// ❌ Fragile CSS selectors
3140
await page.locator('.bg-slate-800 .p-6 h3').click();
3241
await page.locator('a').nth(0).click();
42+
await page.locator('.grid > div:first-child').click();
3343
```
3444

3545
## Essential Test Patterns
@@ -52,12 +62,12 @@ test.describe('Feature Name', () => {
5262
test('should handle loading content', async ({ page }) => {
5363
await page.goto('/');
5464

55-
// Wait for content to load
56-
await page.waitForSelector('.grid', { timeout: 10000 });
65+
// Wait for content to load using test IDs
66+
await page.waitForSelector('[data-testid="dog-list-grid"]', { timeout: 10000 });
5767

5868
// Verify loading is complete
59-
await expect(page.locator('.animate-pulse')).not.toBeVisible();
60-
await expect(page.getByRole('link', { name: /dog/i }).first()).toBeVisible();
69+
await expect(page.getByTestId('dog-list-loading')).not.toBeVisible();
70+
await expect(page.getByTestId('dog-card-1')).toBeVisible();
6171
});
6272
```
6373

@@ -66,20 +76,21 @@ test('should handle loading content', async ({ page }) => {
6676
test('should navigate dog details workflow', async ({ page }) => {
6777
await page.goto('/');
6878

69-
// Wait for dogs to load
70-
await page.waitForSelector('.grid a[href^="/dog/"]', { timeout: 10000 });
79+
// Wait for dogs to load using test ID
80+
await page.waitForSelector('[data-testid="dog-list-grid"]', { timeout: 10000 });
7181

72-
// Click first dog
73-
const firstDogLink = page.locator('.grid a[href^="/dog/"]').first();
74-
await firstDogLink.click();
82+
// Click first dog using test ID
83+
await page.getByTestId('dog-card-1').click();
7584

7685
// Verify navigation
7786
await expect(page.url()).toMatch(/\/dog\/\d+/);
7887
await expect(page).toHaveTitle(/Dog Details/);
88+
await expect(page.getByTestId('dog-details-container')).toBeVisible();
7989

80-
// Navigate back
81-
await page.getByRole('link', { name: 'Back to All Dogs' }).click();
90+
// Navigate back using test ID
91+
await page.getByTestId('back-to-dogs-button').click();
8292
await expect(page).toHaveURL('/');
93+
await expect(page.getByTestId('homepage-container')).toBeVisible();
8394
});
8495
```
8596

@@ -95,7 +106,10 @@ test('should handle API errors', async ({ page }) => {
95106
});
96107

97108
await page.goto('/');
98-
await expect(page.getByText(/Failed to fetch/)).toBeVisible({ timeout: 10000 });
109+
110+
// Verify error state using test ID
111+
await expect(page.getByTestId('dog-list-error')).toBeVisible({ timeout: 10000 });
112+
await expect(page.getByTestId('error-message')).toContainText('Failed to fetch');
99113
});
100114
```
101115

@@ -106,13 +120,17 @@ test('should handle API errors', async ({ page }) => {
106120
await expect(page).toHaveTitle(/Expected Title/);
107121
await expect(page).toHaveURL('/path');
108122

109-
// Element visibility
110-
await expect(page.getByRole('heading', { name: 'Title' })).toBeVisible();
111-
await expect(page.getByText('Loading')).not.toBeVisible();
123+
// Element visibility using test IDs
124+
await expect(page.getByTestId('homepage-title')).toBeVisible();
125+
await expect(page.getByTestId('dog-list-loading')).not.toBeVisible();
126+
127+
// Element content using test IDs
128+
await expect(page.getByTestId('dog-name-1')).toContainText('Buddy');
129+
await expect(page.getByTestId('error-message')).toContainText('Failed to fetch');
112130

113-
// Element states
114-
await expect(page.getByRole('button')).toBeEnabled();
115-
await expect(page.getByRole('textbox')).toHaveValue('value');
131+
// Element states using test IDs
132+
await expect(page.getByTestId('submit-button')).toBeEnabled();
133+
await expect(page.getByTestId('search-input')).toHaveValue('search term');
116134
```
117135

118136
## File Organization
@@ -132,7 +150,9 @@ npm run test:e2e:headed # See browser
132150

133151
## Key Tips
134152

135-
- Use `page.waitForSelector()` for dynamic content, not `networkidle`
153+
- **Use test IDs first**: Always prefer `data-testid` attributes for reliable element identification
154+
- Use `page.waitForSelector('[data-testid="element"]')` for dynamic content, not `networkidle`
136155
- Group tests with `test.describe()` and descriptive names
137156
- Set reasonable timeouts (5-10 seconds)
138157
- Test real user scenarios, not implementation details
158+
- Include both happy path and error scenarios in your test suites

.github/instructions/svelte.instructions.md

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ Essential patterns for writing Svelte 5.23+ components with TypeScript in the da
1212
2. **Dark Theme** - Use slate color palette (`bg-slate-800`, `text-slate-100`, `border-slate-700`)
1313
3. **Responsive** - Mobile-first with Tailwind responsive prefixes
1414
4. **Accessibility** - Semantic HTML and ARIA attributes
15+
5. **Test Identifiers** - Always include `data-testid` attributes for E2E testing
16+
17+
> 📋 **Reference**: See [`test-identifiers.md`](./test-identifiers.md) for complete list of required test IDs
1518
1619
## Basic Component Structure
1720

@@ -37,13 +40,29 @@ Essential patterns for writing Svelte 5.23+ components with TypeScript in the da
3740
});
3841
</script>
3942
40-
{#if loading}
41-
<!-- Loading state -->
42-
{:else if error}
43-
<!-- Error state -->
44-
{:else}
45-
<!-- Main content -->
46-
{/if}
43+
<!-- Always include data-testid for containers -->
44+
<div data-testid="component-container">
45+
<h2 class="text-2xl font-medium mb-6 text-slate-100" data-testid="component-title">
46+
{title}
47+
</h2>
48+
49+
{#if loading}
50+
<!-- Loading state with test ID -->
51+
<div data-testid="component-loading">
52+
<!-- Loading content -->
53+
</div>
54+
{:else if error}
55+
<!-- Error state with test ID -->
56+
<div data-testid="component-error">
57+
<p data-testid="error-message">{error}</p>
58+
</div>
59+
{:else}
60+
<!-- Main content with test ID -->
61+
<div data-testid="component-content">
62+
<!-- Content here -->
63+
</div>
64+
{/if}
65+
</div>
4766
```
4867

4968
## API Data Fetching
@@ -79,24 +98,27 @@ Essential patterns for writing Svelte 5.23+ components with TypeScript in the da
7998
onMount(fetchData);
8099
</script>
81100
82-
<div>
101+
<div data-testid="api-component-container">
83102
{#if loading}
84-
<div class="animate-pulse bg-slate-800/60 rounded-xl p-6">
103+
<div class="animate-pulse bg-slate-800/60 rounded-xl p-6" data-testid="api-component-loading">
85104
<div class="h-6 bg-slate-700 rounded w-3/4 mb-3"></div>
86105
<div class="h-4 bg-slate-700 rounded w-1/2"></div>
87106
</div>
88107
{:else if error}
89-
<div class="bg-red-500/20 border border-red-500/50 text-red-400 rounded-xl p-6">
90-
{error}
108+
<div class="bg-red-500/20 border border-red-500/50 text-red-400 rounded-xl p-6" data-testid="api-component-error">
109+
<p data-testid="error-message">{error}</p>
91110
</div>
92111
{:else if apiData.length === 0}
93-
<div class="text-center py-12 bg-slate-800/50 rounded-xl border border-slate-700">
94-
<p class="text-slate-300">No data available.</p>
112+
<div class="text-center py-12 bg-slate-800/50 rounded-xl border border-slate-700" data-testid="api-component-empty">
113+
<p class="text-slate-300" data-testid="empty-message">No data available.</p>
95114
</div>
96115
{:else}
97-
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
116+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6" data-testid="api-component-grid">
98117
{#each apiData as item (item.id)}
99-
<!-- Item content -->
118+
<div data-testid={`item-${item.id}`} data-item-id={item.id}>
119+
<h3 data-testid={`item-name-${item.id}`}>{item.name}</h3>
120+
<!-- More item content -->
121+
</div>
100122
{/each}
101123
</div>
102124
{/if}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Test Identifiers Reference
2+
3+
This document provides a comprehensive reference for all `data-testid` attributes used in the Tailspin Shelter application for E2E testing with Playwright.
4+
5+
## Why Test Identifiers?
6+
7+
Test identifiers (`data-testid` attributes) provide stable, reliable selectors for E2E tests that won't break when:
8+
- CSS classes change
9+
- Styling is updated
10+
- Component structure is refactored
11+
- Text content is modified
12+
13+
## Naming Conventions
14+
15+
- **Format**: Use kebab-case (`dog-card-1`, `homepage-title`)
16+
- **Descriptive**: Include component and purpose in the name
17+
- **Unique**: Append IDs for repeated elements (`dog-card-${id}`)
18+
- **Hierarchical**: Use prefixes for related elements (`dog-details-name`, `dog-details-breed`)
19+
20+
## Component Test IDs
21+
22+
### Homepage (`src/pages/index.astro`)
23+
- `homepage-container`: Main page wrapper
24+
- `homepage-title`: Welcome heading
25+
- `homepage-description`: Descriptive text
26+
27+
### Dog List (`src/components/DogList.svelte`)
28+
- `dog-list-container`: Main container
29+
- `available-dogs-heading`: Section heading
30+
- `dog-list-grid`: Grid of dog cards
31+
- `dog-list-loading`: Loading state container
32+
- `dog-list-error`: Error state container
33+
- `dog-list-empty`: Empty state container
34+
- `dog-card-{id}`: Individual dog cards (with ID)
35+
- `dog-name-{id}`: Dog name within card
36+
- `dog-breed-{id}`: Dog breed within card
37+
- `dog-view-details-{id}`: View details link
38+
39+
### Dog Details (`src/components/DogDetails.svelte`)
40+
- `dog-details-page`: Page container
41+
- `dog-details-container`: Main details container
42+
- `dog-details-loading`: Loading state
43+
- `dog-details-error`: Error state
44+
- `dog-details-no-data`: No data state
45+
- `dog-details-name`: Dog name heading
46+
- `dog-details-breed`: Breed information
47+
- `dog-details-age`: Age information
48+
- `dog-details-gender`: Gender information
49+
- `dog-details-description`: About text
50+
- `dog-details-about-heading`: About section heading
51+
- `dog-status-available`: Available status badge
52+
- `dog-status-pending`: Pending status badge
53+
- `dog-status-adopted`: Adopted status badge
54+
55+
### Navigation
56+
- `back-to-dogs-button`: Navigation button (dog details and about pages)
57+
- `navigation-section`: Navigation container
58+
59+
### About Page (`src/pages/about.astro`)
60+
- `about-page-container`: Page wrapper
61+
- `about-page-title`: Page heading
62+
- `about-page-content`: Main content area
63+
- `about-page-description-1`: First paragraph
64+
- `about-page-description-2`: Second paragraph
65+
- `about-page-note`: Disclaimer section
66+
- `fictional-organization-note`: Fictional org note
67+
- `about-page-navigation`: Navigation section
68+
69+
### Header (`src/components/Header.astro`)
70+
- `site-header`: Header container
71+
- `menu-toggle-button`: Hamburger menu button
72+
- `navigation-menu`: Dropdown menu
73+
- `nav-home-link`: Home navigation link
74+
- `nav-about-link`: About navigation link
75+
- `site-title`: Site title heading
76+
- `site-title-link`: Site title link
77+
78+
### Common State Elements
79+
- `{component}-loading`: Loading states
80+
- `{component}-error`: Error states
81+
- `{component}-empty`: Empty states
82+
- `error-message`: Error message text
83+
- `empty-message`: Empty state message text
84+
85+
## Usage in Tests
86+
87+
### Preferred Locator Strategy
88+
```typescript
89+
// 1. Test IDs (most reliable)
90+
await page.getByTestId('dog-card-1').click();
91+
await expect(page.getByTestId('homepage-title')).toBeVisible();
92+
93+
// 2. Semantic locators (when test IDs aren't available)
94+
await page.getByRole('heading', { name: 'Welcome' }).click();
95+
96+
// 3. Text content (for validation)
97+
await expect(page.getByTestId('dog-name-1')).toContainText('Buddy');
98+
```
99+
100+
### Common Patterns
101+
```typescript
102+
// Wait for content to load
103+
await page.waitForSelector('[data-testid="dog-list-grid"]', { timeout: 10000 });
104+
105+
// Navigate to dog details
106+
await page.getByTestId('dog-card-1').click();
107+
await expect(page.getByTestId('dog-details-container')).toBeVisible();
108+
109+
// Handle error states
110+
await expect(page.getByTestId('dog-list-error')).toBeVisible();
111+
await expect(page.getByTestId('error-message')).toContainText('Failed to fetch');
112+
```
113+
114+
## Adding New Test IDs
115+
116+
When creating new components or pages:
117+
118+
1. **Container**: Always add a main container ID
119+
2. **States**: Include loading, error, and empty states
120+
3. **Interactive Elements**: Buttons, links, form inputs
121+
4. **Content**: Important headings and text
122+
5. **Lists**: Individual items with unique identifiers
123+
124+
Example:
125+
```svelte
126+
<div data-testid="new-component-container">
127+
{#if loading}
128+
<div data-testid="new-component-loading">Loading...</div>
129+
{:else if error}
130+
<div data-testid="new-component-error">
131+
<p data-testid="error-message">{error}</p>
132+
</div>
133+
{:else}
134+
<div data-testid="new-component-content">
135+
{#each items as item}
136+
<div data-testid={`item-${item.id}`}>
137+
<h3 data-testid={`item-title-${item.id}`}>{item.title}</h3>
138+
</div>
139+
{/each}
140+
</div>
141+
{/if}
142+
</div>
143+
```

0 commit comments

Comments
 (0)