Skip to content

Commit 5a034c6

Browse files
itsjustrileyclaude
andcommitted
test(e2e): implement PR feedback and update CLAUDE.md
Implemented feedback from PR review including test improvements and updated e2e/CLAUDE.md following best practices for documentation. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 981ca2a commit 5a034c6

File tree

2 files changed

+60
-60
lines changed

2 files changed

+60
-60
lines changed

e2e/CLAUDE.md

Lines changed: 60 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,47 @@
22

33
## Test Isolation
44

5-
### Cart State Management
6-
To clear cart state between tests, **clear ALL cookies**. Cart state is stored in a cookie, so there's no need to individually remove line items, discounts, etc. Clearing all cookies provides complete test isolation.
5+
Playwright automatically provides test isolation - each test runs in its own browser context with isolated storage, cookies, and state. You generally don't need to manually clear cookies or storage between tests.
76

8-
```typescript
9-
test.afterEach(async ({storefront}) => {
10-
await storefront.clearAllCookies();
11-
});
12-
```
13-
14-
For maximum isolation, consider clearing cookies in both `beforeEach` and `afterEach`:
15-
```typescript
16-
test.beforeEach(async ({storefront}) => {
17-
await storefront.clearAllCookies();
18-
});
19-
20-
test.afterEach(async ({storefront}) => {
21-
await storefront.clearAllCookies();
22-
});
23-
```
7+
**Exception:** If you need to clear state within a single test (e.g., testing empty cart after clearing), do so explicitly in that test.
248

259
## Test Organization
2610

27-
### Shared Setup in beforeEach
28-
Prefer putting statements shared at the start of ALL tests in a file into a `beforeEach` hook rather than repeating them in each test:
11+
### Test Setup Strategy
12+
13+
Per [Playwright best practices](https://playwright.dev/docs/best-practices#make-tests-as-isolated-as-possible): Use `beforeEach` hooks to eliminate repetitive setup steps while maintaining test isolation.
2914

3015
```typescript
31-
// GOOD: Shared setup in beforeEach
32-
test.describe('Cart Operations', () => {
16+
// GOOD: Use beforeEach for shared setup
17+
test.describe('Quantity Management', () => {
3318
test.beforeEach(async ({storefront}) => {
3419
await storefront.goto('/');
3520
await storefront.navigateToFirstProduct();
3621
await storefront.addToCart();
3722
});
3823

3924
test('increases quantity', async ({storefront}) => {
40-
// Test starts with item already in cart
25+
const firstItem = storefront.getCartLineItemByIndex(0);
26+
await storefront.increaseLineItemQuantity(firstItem);
27+
expect(await storefront.getLineItemQuantity(firstItem)).toBe(2);
4128
});
4229
});
4330

44-
// AVOID: Repeating setup in each test
45-
test.describe('Cart Operations', () => {
46-
test('increases quantity', async ({storefront}) => {
47-
await storefront.goto('/'); // Repeated
48-
await storefront.navigateToFirstProduct(); // Repeated
49-
await storefront.addToCart(); // Repeated
50-
// ...
51-
});
31+
// ACCEPTABLE: Duplicate simple 1-2 line setups when it improves clarity
32+
test('adds item to empty cart', async ({storefront}) => {
33+
await storefront.goto('/');
34+
await storefront.navigateToFirstProduct();
35+
await storefront.addToCart();
36+
37+
await expect(storefront.getCartLineItems()).toHaveCount(1);
38+
});
39+
40+
// AVOID: Repeating 3+ lines in every test
41+
test('increases quantity', async ({storefront}) => {
42+
await storefront.goto('/'); // Repeated
43+
await storefront.navigateToFirstProduct(); // Repeated
44+
await storefront.addToCart(); // Repeated
45+
// Use beforeEach instead
5246
});
5347
```
5448

@@ -58,37 +52,42 @@ test.describe('Cart Operations', () => {
5852
Always choose selectors based on **DOM elements and semantic structure**, NOT CSS classes or styles. Tests should reflect how a user perceives and interacts with the page.
5953

6054
**Priority order for selectors:**
61-
1. Role + accessible name: `getByRole('button', {name: 'Add to cart'})`
62-
2. Role + landmark: `getByRole('banner').getByRole('link', {name: /cart/i})`
63-
3. Text content: `getByText('Continue to Checkout')`
64-
4. Test IDs (when necessary): `getByTestId('cart-drawer')`
65-
5. CSS classes (last resort, for entities only): `locator('li.cart-line:visible')`
55+
1. **Role + accessible name** (preferred): `getByRole('button', {name: 'Add to cart'})`
56+
2. **Role + landmark**: `getByRole('banner').getByRole('link', {name: /cart/i})`
57+
3. **Text content**: `getByText('Continue to Checkout')`
58+
4. **Test IDs** (when semantic selectors aren't possible): `getByTestId('cart-drawer')`
59+
5. **CSS classes** (last resort only): Only when semantic selectors are impractical
60+
61+
**Always try role-based selectors first.** Only use CSS classes when:
62+
- The element has no semantic role
63+
- Multiple similar elements need disambiguation
64+
- Role-based selectors would be overly complex
6665

6766
### Write Tests from User Perspective
68-
Write tests based on how a user would perceive events happening, not based on technical implementation details.
67+
Write tests based on how a user would perceive events, not implementation details.
6968

7069
```typescript
7170
// GOOD: Wait for user-visible state change
7271
await storefront.addGiftCard('GIFT123');
73-
await expect(giftCardInput).toHaveValue(''); // Input cleared means success
74-
await expect(applyButton).toBeEnabled(); // Button re-enabled means ready
72+
await expect(giftCardInput).toHaveValue(''); // Input cleared = success
73+
await expect(applyButton).toBeEnabled(); // Button enabled = ready
7574

76-
// AVOID: Waiting for network requests
75+
// AVOID: Waiting for network requests (implementation detail)
7776
await storefront.addGiftCard('GIFT123');
78-
await page.waitForResponse(resp => resp.url().includes('cart')); // Implementation detail
77+
await page.waitForResponse(resp => resp.url().includes('cart'));
7978
```
8079

8180
### Waiting for State Changes
82-
When an action triggers a state change, wait for the **visible effect** rather than the underlying mechanism:
81+
Wait for the **visible effect** rather than the underlying mechanism:
8382

8483
```typescript
85-
// GOOD: Wait for quantity text to change
84+
// GOOD: Wait for actual data change
8685
await increaseButton.click();
8786
await expect.poll(() => getLineItemQuantity(item)).toBe(2);
8887

89-
// AVOID: Just waiting for button to be re-enabled
88+
// AVOID: Waiting for button state (re-enables before data updates)
9089
await increaseButton.click();
91-
await expect(increaseButton).toBeEnabled(); // Button might re-enable before quantity updates
90+
await expect(increaseButton).toBeEnabled();
9291
```
9392

9493
## Running Tests
@@ -130,16 +129,27 @@ async increaseLineItemQuantity(lineItem) {
130129
```
131130

132131
### Visibility-Aware Locators
133-
The skeleton template has both a cart drawer (aside) and a cart page. Both contain similar elements but only one is visible at a time. Use `:visible` pseudo-selector to match only the current context:
132+
The skeleton template has both a cart drawer (aside) and a cart page. Both contain similar elements but only one is visible at a time.
134133

135134
```typescript
136-
// GOOD: Only matches visible elements
135+
// GOOD: Role-based selectors with chaining for specificity
137136
getCartLineItems() {
138-
return this.page.locator('li.cart-line:visible');
137+
return this.page.getByRole('list', {name: /cart|line items/i}).getByRole('listitem');
139138
}
140139

141-
// AVOID: May match hidden elements in drawer when on cart page
140+
getCheckoutButton() {
141+
return this.page.getByRole('button', {name: /checkout/i});
142+
}
143+
144+
// ACCEPTABLE: Test IDs when semantic selectors need disambiguation
142145
getCartLineItems() {
143-
return this.page.locator('li.cart-line');
146+
return this.page.getByTestId('cart-line-items').getByRole('listitem');
147+
}
148+
149+
// AVOID: CSS selectors (DOM can change, leading to flaky tests)
150+
getCartLineItems() {
151+
return this.page.locator('li.cart-line:visible'); // Not resilient
144152
}
145153
```
154+
155+
**Why avoid CSS?** Per [Playwright docs](https://playwright.dev/docs/locators), CSS selectors are "not recommended as the DOM can often change leading to non resilient tests." Use role-based selectors that reflect how users perceive the page.

e2e/specs/skeleton/cart.spec.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,6 @@ setTestStore('hydrogenPreviewStorefront');
44

55
test.describe('Cart', () => {
66
test.describe('Line Items', () => {
7-
// Clear ALL cookies before AND after each test for isolation on shared store.
8-
// Cart state is stored in a cookie, and clearing all ensures complete isolation.
9-
test.beforeEach(async ({storefront}) => {
10-
await storefront.clearAllCookies();
11-
});
12-
13-
test.afterEach(async ({storefront}) => {
14-
await storefront.clearAllCookies();
15-
});
16-
177
test.describe('Adding Items', () => {
188
test('adds item to cart and opens aside drawer', async ({storefront}) => {
199
await storefront.goto('/');

0 commit comments

Comments
 (0)