Skip to content

Commit 365e8cb

Browse files
committed
Replace index based lookups with text
1 parent b751344 commit 365e8cb

12 files changed

Lines changed: 418 additions & 88 deletions

.claude/commands/e2e-create.md

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,20 @@ Create a new E2E test following the existing patterns in the codebase.
66

77
- Test files location: `frontend/e2e/tests/*.pw.ts`
88
- Test helpers: `frontend/e2e/helpers.playwright.ts`
9+
- Test results: Individual test directories in `frontend/e2e/test-results/`
10+
- **For EACH failed test**, a directory is created with these files:
11+
- `failed.json` - Summary of failures with error messages and stack traces
12+
- `error-context.md` - **MOST VALUABLE** - Page snapshot showing DOM state, field values, button states
13+
- `trace.zip` - Detailed execution trace (unzip to get action logs)
14+
- Screenshots (`.png`) and videos (`.webm`)
915
- Existing tests to reference for patterns
1016
- Tests use Playwright with Firefox
1117
- Tests are tagged with `@oss` or `@enterprise`
1218

1319
## Workflow
1420

21+
**IMPORTANT: Always start by changing to the frontend directory** - the `.env` file and dependencies are located there.
22+
1523
1. **Ask the user what they want to test:**
1624
- What feature/page/flow to test
1725
- Any specific scenarios or edge cases
@@ -47,10 +55,36 @@ Create a new E2E test following the existing patterns in the codebase.
4755
- Include proper test descriptions and tags
4856
- Add comments explaining complex test logic
4957

50-
6. **Report what was created:**
58+
6. **Run the new test to verify it works:**
59+
- Run the test 2-3 times to ensure it's stable:
60+
```bash
61+
cd frontend
62+
SKIP_BUNDLE=1 E2E_CONCURRENCY=1 npm run test -- tests/new-test.pw.ts --quiet
63+
```
64+
- **If the test fails, ALWAYS READ TRACES FIRST:**
65+
1. **Read error-context.md** in the failed test directory:
66+
- Shows exact DOM state when test failed
67+
- YAML tree with all elements, their states, and data-test attributes
68+
- Check if expected elements exist and their actual values
69+
- Verify selector is correct by searching for the data-test attribute
70+
2. **If error-context.md doesn't show the issue**, unzip and check trace files:
71+
```bash
72+
cd frontend/e2e/test-results/<failed-test-directory>
73+
unzip -q trace.zip
74+
grep -i "error\|failed" 0-trace.network # Check for network errors
75+
```
76+
3. **Only after analyzing traces**, fix the issue:
77+
- Wrong selector → Update to match actual DOM from error-context.md
78+
- Missing `data-test` attribute → Add it to the component
79+
- Element hidden → Filter for visible elements or wait for visibility
80+
- Missing wait → Add appropriate `waitFor*` calls
81+
- Re-run until it passes consistently
82+
83+
7. **Report what was created:**
5184
- Show the test file path
5285
- List any `data-test` attributes that were added
53-
- Suggest how to run the test
86+
- Report test stability (how many runs passed/failed)
87+
- If there were failures, explain what was fixed
5488
5589
## Important Notes
5690

.claude/commands/e2e.md

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,50 +9,108 @@ Run E2E tests with the following configuration:
99
## Context
1010

1111
- Test files location: `frontend/e2e/tests/*.pw.ts`
12-
- Test results: `frontend/e2e/test-results/results.json`
12+
- Test results: Individual test directories in `frontend/e2e/test-results/`
13+
- **For EACH failed test**, a directory is created with these files:
14+
15+
## ALWAYS read these files in this order when debugging failures:
16+
17+
1. **`failed.json`** (in `test-results/` root) - START HERE
18+
- Contains only failed tests with error messages, stack traces, file paths, line numbers
19+
- Much smaller than results.json - only failures
20+
21+
2. **`error-context.md`** (in each failed test directory) - READ THIS SECOND
22+
- **THIS IS THE MOST VALUABLE FILE** - shows exact page state when test failed
23+
- YAML tree structure of the entire DOM with element states
24+
- Shows which buttons are disabled/enabled
25+
- Shows form field values
26+
- Shows what's visible on the page
27+
- Example: Can see if a button is `[disabled]` or a field is empty
28+
29+
3. **Trace files** (if needed for more detail):
30+
- Unzip `trace.zip` to get:
31+
- `0-trace.trace` - Every action taken with timestamps and selectors
32+
- `0-trace.network` - Network requests
33+
- Screenshots (jpegs) showing visual state at each step
34+
35+
4. **Full JSON report**: `results.json` - contains ALL test results (passed + failed), only use if needed
36+
37+
- **HTML report**: `frontend/e2e/playwright-report/` - browsable HTML interface (for humans, not programmatic use)
1338
- Tests use Playwright with Firefox
1439
- The retry logic is built into the test runner (via `run-with-retry.ts`)
1540
- Tests run against a local Docker environment (API + DB)
1641

1742
## Workflow
1843

44+
**IMPORTANT: Always start by changing to the frontend directory** - the `.env` file and dependencies are located there.
45+
1946
1. **Run tests** from the frontend directory:
2047
```bash
2148
cd frontend
2249
SKIP_BUNDLE=1 E2E_CONCURRENCY=20 npm run test -- --grep-invert @enterprise --quiet
2350
```
2451

25-
2. **If ANY tests fail (even after automatic retries):**
52+
2. **ALWAYS check for flaky tests after test run completes:**
53+
54+
**CRITICAL:** Tests that fail initially but pass on retry are FLAKY and MUST be investigated, even if the final result shows all tests passed.
55+
56+
a. Check the test output for any tests that failed on first run (look for the initial failure messages before "Retrying")
57+
58+
b. For EACH test that failed initially (even if it passed on retry):
59+
- Parse the error from the test output:
60+
* Error type (e.g., `TimeoutError`, `AssertionError`)
61+
* Error message (e.g., `"waiting for locator('#button') to be visible"`)
62+
* File path and line number where it failed
63+
* Stack trace showing the call chain
64+
- Read the test file at the failing line number to understand what was being tested
65+
- **Report these as FLAKY TESTS** - they indicate timing issues, race conditions, or environmental problems
66+
- **Analyze the root cause**:
67+
* Timeout errors → likely missing waits or race conditions
68+
* Assertion errors → check if value is correct or if timing is off
69+
* Element not found → selector may have changed or element loads slowly
70+
71+
c. **If ANY tests are still failing after automatic retries:**
72+
73+
For EACH failed test, **ALWAYS READ TRACES FIRST:**
74+
75+
1. **Read error-context.md** in the failed test directory:
76+
- Shows exact DOM state when test failed
77+
- YAML tree with all elements, their states, and data-test attributes
78+
- Check if expected elements exist and their actual values
79+
- Verify selector is correct by searching for the data-test attribute
2680

27-
a. Parse the test output and `e2e/test-results/results.json` to identify failures
81+
2. **If error-context.md doesn't show the issue**, unzip and check trace files:
82+
```bash
83+
cd frontend/e2e/test-results/<failed-test-directory>
84+
unzip -q trace.zip
85+
grep -i "error\|failed" 0-trace.network # Check for network errors
86+
```
2887

29-
b. For EACH failed test:
30-
- Read the test file
31-
- Read the error message and stack trace
32-
- Analyze the root cause
33-
- **Attempt to fix the issue** if it's one of these:
88+
3. **Only after analyzing traces**, read the test file and fix:
89+
- Wrong selector → Update to match actual DOM from error-context.md
3490
- Missing `data-test` attribute → Add it to the component
35-
- Selector changedUpdate the test to use the new selector
91+
- Element hiddenFilter for visible elements or wait for visibility
3692
- Missing wait → Add appropriate `waitFor*` calls
3793
- Race condition → Add network waits, increase timeouts, or use more specific waits
3894
- Flaky element interaction → Add `scrollIntoView` or `waitForVisible` before clicking
3995

40-
c. **After making fixes**, re-run ONLY the failed tests:
96+
d. **After making fixes**, re-run ONLY the failed tests:
4197
```bash
4298
cd frontend
4399
SKIP_BUNDLE=1 E2E_CONCURRENCY=1 npm run test -- tests/flag-tests.pw.ts tests/invite-test.pw.ts
44100
```
45101
- Use concurrency=1 to avoid race conditions
46102
- Only run the specific test files that failed
47103

48-
d. **If tests still fail after fixes:**
104+
e. **If tests still fail after fixes:**
49105
- Try a second round of fixes if the error changed
50106
- Otherwise, report the issue with details on what was attempted
51107

52108
3. **Report final results:**
53-
- List which tests passed/failed
109+
- **ALWAYS report flaky tests first** (tests that failed initially but passed on retry)
110+
- List which tests passed/failed after all retries
54111
- Document any fixes that were applied
55112
- For unfixable issues, explain why and suggest manual investigation
113+
- Show the error message and line number for any failures
56114

57115
## Important Notes
58116

.claude/commands/optimise-e2e.md

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,22 @@ Clean up E2E tests by removing unnecessary timeouts/waits and documenting necess
66

77
- Test files location: `frontend/e2e/tests/*.pw.ts`
88
- Test helpers: `frontend/e2e/helpers.playwright.ts`
9+
- Test results: Individual test directories in `frontend/e2e/test-results/`
10+
- **For EACH failed test**, a directory is created with these files:
11+
- `failed.json` - Summary of failures with error messages and stack traces
12+
- `error-context.md` - **MOST VALUABLE** - Page snapshot showing DOM state, field values, button states
13+
- `trace.zip` - Detailed execution trace (unzip to get action logs)
14+
- Screenshots (`.png`) and videos (`.webm`)
915
- Tests should be fast, reliable, and well-documented
1016

1117
## Workflow
1218

19+
**IMPORTANT: Always start by changing to the frontend directory** - the `.env` file and dependencies are located there.
20+
1321
1. **Scan all test files:**
1422
```bash
15-
ls frontend/e2e/tests/*.pw.ts
23+
cd frontend
24+
ls e2e/tests/*.pw.ts
1625
```
1726

1827
2. **For each test file, look for:**
@@ -73,9 +82,24 @@ Clean up E2E tests by removing unnecessary timeouts/waits and documenting necess
7382
SKIP_BUNDLE=1 E2E_CONCURRENCY=1 npm run test -- tests/modified-test.pw.ts --quiet
7483
done
7584
```
76-
- If any run fails, investigate whether the optimisation caused it
77-
- If failures are due to removed waits, add them back with proper comments
78-
- Only keep optimisations that pass all 3 runs
85+
- **If any run fails, ALWAYS READ TRACES FIRST:**
86+
1. **Read error-context.md** in the failed test directory:
87+
- Shows exact DOM state when test failed
88+
- YAML tree with all elements, their states, and data-test attributes
89+
- Check if expected elements exist and their actual values
90+
- Verify selector is correct by searching for the data-test attribute
91+
2. **If error-context.md doesn't show the issue**, unzip and check trace files:
92+
```bash
93+
cd frontend/e2e/test-results/<failed-test-directory>
94+
unzip -q trace.zip
95+
grep -i "error\|failed" 0-trace.network # Check for network errors
96+
```
97+
3. **Only after analyzing traces**, investigate whether the optimisation caused it:
98+
- Wrong selector → Update to match actual DOM from error-context.md
99+
- Missing wait → If optimisation removed a necessary wait, add it back with proper comment explaining why it's needed
100+
- Element hidden → Check if removed wait was allowing element to become visible
101+
- Race condition → If timeout removal caused timing issue, use event-based wait instead
102+
- Only keep optimisations that pass all 3 runs consistently
79103

80104
6. **Report changes:**
81105
- List files modified and verified

frontend/e2e/global-setup.playwright.ts

Lines changed: 79 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,49 @@ import { FullConfig } from '@playwright/test';
22
import fetch from 'node-fetch';
33
import flagsmith from 'flagsmith/isomorphic';
44
import Project from '../common/project';
5+
import * as fs from 'fs';
6+
import * as path from 'path';
57

68
async function globalSetup(config: FullConfig) {
79
console.log('Starting global setup for E2E tests...');
810

11+
// Clear previous test results and reports (skip on retries to preserve failed.json)
12+
const testResultsDir = path.join(__dirname, 'test-results');
13+
const reportDir = path.join(__dirname, 'playwright-report');
14+
15+
if (!process.env.E2E_SKIP_CLEANUP) {
16+
if (fs.existsSync(testResultsDir)) {
17+
// Preserve error-context.md files from previous runs for debugging
18+
// Only delete test result directories that don't contain error-context.md
19+
const entries = fs.readdirSync(testResultsDir, { withFileTypes: true });
20+
for (const entry of entries) {
21+
if (entry.isDirectory()) {
22+
const errorContextPath = path.join(testResultsDir, entry.name, 'error-context.md');
23+
if (!fs.existsSync(errorContextPath)) {
24+
// No error context - safe to delete
25+
fs.rmSync(path.join(testResultsDir, entry.name), { recursive: true, force: true });
26+
}
27+
} else {
28+
// Delete files in test-results root (like .last-run.json, results.json)
29+
fs.unlinkSync(path.join(testResultsDir, entry.name));
30+
}
31+
}
32+
console.log('Cleared previous test results (preserved error contexts)');
33+
}
34+
35+
if (fs.existsSync(reportDir)) {
36+
fs.rmSync(reportDir, { recursive: true, force: true });
37+
console.log('Cleared previous HTML report');
38+
}
39+
} else {
40+
console.log('Skipping cleanup (retry attempt)');
41+
}
42+
43+
// Ensure test-results directory exists for the JSON reporter
44+
if (!fs.existsSync(testResultsDir)) {
45+
fs.mkdirSync(testResultsDir, { recursive: true });
46+
}
47+
948
const e2eTestApi = `${process.env.FLAGSMITH_API_URL || Project.api}e2etests/teardown/`;
1049
const token = process.env.E2E_TEST_TOKEN
1150
? process.env.E2E_TEST_TOKEN
@@ -26,33 +65,52 @@ async function globalSetup(config: FullConfig) {
2665
fetch,
2766
});
2867

29-
// Teardown previous test data
68+
// Teardown previous test data with retry logic
3069
if (token) {
31-
try {
32-
const res = await fetch(e2eTestApi, {
33-
body: JSON.stringify({}),
34-
headers: {
35-
'Accept': 'application/json',
36-
'Content-Type': 'application/json',
37-
'X-E2E-Test-Auth-Token': token.trim(),
38-
},
39-
method: 'POST',
40-
});
70+
const maxAttempts = 3;
71+
const delayMs = 2000;
72+
let teardownSuccess = false;
73+
74+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
75+
if (attempt > 0) {
76+
console.log(`\x1b[33m%s\x1b[0m`, `Retrying teardown (attempt ${attempt + 1}/${maxAttempts})...`);
77+
await new Promise(resolve => setTimeout(resolve, delayMs));
78+
}
4179

42-
if (res.ok) {
43-
console.log('\n', '\x1b[32m', 'e2e teardown successful', '\x1b[0m', '\n');
44-
} else {
45-
const errorMsg = `e2e teardown failed with status ${res.status}`;
46-
console.error('\n', '\x1b[31m', errorMsg, '\x1b[0m', '\n');
47-
if (process.env.E2E_LOCAL !== 'true' && process.env.E2E_DEV !== 'true') {
48-
throw new Error(errorMsg); // Fail tests early in CI if teardown fails
80+
try {
81+
const res = await fetch(e2eTestApi, {
82+
body: JSON.stringify({}),
83+
headers: {
84+
'Accept': 'application/json',
85+
'Content-Type': 'application/json',
86+
'X-E2E-Test-Auth-Token': token.trim(),
87+
},
88+
method: 'POST',
89+
});
90+
91+
if (res.ok) {
92+
console.log('\n', '\x1b[32m', 'e2e teardown successful', '\x1b[0m', '\n');
93+
teardownSuccess = true;
94+
break;
95+
} else {
96+
console.error('\x1b[31m%s\x1b[0m', `✗ E2E teardown failed: ${res.status}`);
97+
if (attempt < maxAttempts - 1) {
98+
console.log('');
99+
}
100+
}
101+
} catch (error) {
102+
console.error('\x1b[31m%s\x1b[0m', `✗ E2E teardown error: ${error.message || String(error)}`);
103+
if (attempt < maxAttempts - 1) {
104+
console.log('');
49105
}
50106
}
51-
} catch (error) {
52-
const errorMsg = `e2e teardown error: ${error.message || String(error)}`;
107+
}
108+
109+
if (!teardownSuccess) {
110+
const errorMsg = `e2e teardown failed after ${maxAttempts} attempts`;
53111
console.error('\n', '\x1b[31m', errorMsg, '\x1b[0m', '\n');
54112
if (process.env.E2E_LOCAL !== 'true' && process.env.E2E_DEV !== 'true') {
55-
throw error; // Fail tests early in CI if teardown errors
113+
throw new Error(errorMsg); // Fail tests early in CI if teardown fails
56114
}
57115
}
58116
} else {

frontend/e2e/global-teardown.playwright.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { FullConfig } from '@playwright/test';
22
import * as fs from 'fs';
33
import * as path from 'path';
44
import * as archiver from 'archiver';
5+
import { extractFailedTests } from './extract-failed-tests';
56

67
let upload: ((file: string) => Promise<void>) | null = null;
78
try {
@@ -30,6 +31,9 @@ async function zipDirectory(sourceDir: string, outPath: string): Promise<void> {
3031
async function globalTeardown(config: FullConfig) {
3132
console.log('Running global teardown for E2E tests...');
3233

34+
// Extract failed tests to a smaller JSON file for easier debugging
35+
extractFailedTests(__dirname);
36+
3337
// Upload screenshots/videos if they exist and not in dev mode
3438
const dir = path.join(__dirname, 'test-results');
3539

0 commit comments

Comments
 (0)