Skip to content

Commit 28debe1

Browse files
docs(e2e): consolidate guidelines, remove duplication
- Replace inline rules in AGENTS.md with pointer to guidelines file - Remove redundant waitForMendixApp calls from template (fixture wraps goto) - Drop checklist section — rules already covered inline - Drop Session Management section — merged into Imports & Setup - Expand Locator Patterns: composed selectors, text-only = false positive
1 parent 7028fdd commit 28debe1

2 files changed

Lines changed: 37 additions & 53 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,7 @@ docs/requirements/ -> Detailed technical requirements
3535

3636
## E2E Test Rules (Playwright)
3737

38-
- Import from `@mendix/run-e2e/fixtures` (not `@playwright/test`)
39-
- Wait with `waitForMendixApp(page)`, never `waitForTimeout` or `networkidle`
40-
- Use web-first assertions: `toBeVisible`, `toHaveCount`, `toContainText`, `toHaveCSS`
41-
- Locators: prefer `.mx-name-*` attributes over nth() or text selectors
42-
- Screenshots: no per-test threshold overrides, ensure element visible first
43-
- No manual afterEach logout — fixture handles session lifecycle
44-
- Tag critical-path tests with `@smoke`
45-
- See `docs/requirements/e2e-test-guidelines.md` for full rules + template
38+
- docs/requirements/e2e-test-guidelines.md — Full rules + template
4639

4740
## Development Setup
4841

docs/requirements/e2e-test-guidelines.md

Lines changed: 36 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,101 @@
11
# E2E Test Guidelines
22

3-
Rules for writing reliable, non-flaky Playwright E2E tests in this monorepo. Derived from systematic fixes to 58+ spec files.
3+
Rules for writing reliable, non-flaky Playwright E2E tests in this monorepo.
44

55
## Imports & Setup
66

77
Always use the custom fixtures, never raw Playwright:
88

99
```javascript
1010
import { test, expect } from "@mendix/run-e2e/fixtures";
11-
import { waitForMendixApp } from "@mendix/run-e2e/mendix-helpers";
1211
```
1312

14-
The custom fixture:
13+
Import helpers only when explicitly needed:
1514

16-
- Auto-wraps `page.goto()` to call `waitForMendixApp()`
17-
- Manages worker-scoped Mendix sessions (1 per worker, auto-logout on teardown)
18-
- No manual `afterEach` logout needed
15+
```javascript
16+
import { waitForDataReady } from "@mendix/run-e2e/mendix-helpers";
17+
```
1918

20-
## Waiting Strategies
19+
The custom fixture:
2120

22-
### Hierarchy (use the highest applicable level)
21+
- Auto-wraps `page.goto()` to call `waitForMendixApp()` — do NOT call it manually after `goto`
22+
- Worker-scoped sessions: 1 Mendix session per Playwright worker (4 in CI, 2 locally)
23+
- Auto-logout on teardown — no manual `afterEach` logout needed
2324

24-
1. `waitForMendixApp(page)` — session exists + no progress indicator + `.mx-page` rendered
25-
2. `await expect(element).toBeVisible()` — specific element appeared
26-
3. `await expect(rows).toHaveCount(N)` — data loaded with expected count
27-
4. `waitForDataReady(page)` — opt-in ONLY when data sync timing genuinely matters
25+
## Waiting Strategies
2826

29-
### Banned Patterns
27+
Prefer web-first assertions over explicit waits — they auto-retry until timeout.
3028

3129
| Don't | Do Instead | Why |
3230
| ------------------------------------------------ | ------------------------------------- | ---------------------------------------------------- |
3331
| `page.waitForTimeout(N)` | Web-first assertion on expected state | Arbitrary delays: too short = flaky, too long = slow |
3432
| `page.waitForLoadState("networkidle")` | `waitForMendixApp(page)` | Unrelated network traffic delays indefinitely |
3533
| `page.waitForSelector(...)` then separate assert | `await expect(locator).toBeVisible()` | Combined wait+assert auto-retries |
3634

35+
Use `waitForDataReady(page)` only when data sync timing genuinely matters.
36+
3737
## Assertions
3838

39-
Always prefer Playwright web-first assertions — they auto-retry until timeout.
39+
Preferred: `toBeVisible`, `toHaveText`, `toHaveCount`, `toHaveCSS`, `toContainText`, `toHaveScreenshot`.
4040

4141
| Don't | Do Instead | Why |
4242
| -------------------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------ |
4343
| `const text = await el.allTextContents(); expect(text).toEqual(...)` | `await expect(locator).toContainText([...])` | Non-retrying snapshot vs auto-retrying |
4444
| `await el.evaluate(el => el.getBoundingClientRect())` | `await expect(el).toHaveCSS("transform", "...")` | DOM inspection races vs CSS state assertion |
45-
| `el.nth(1)` to disambiguate | More specific selector or wait first | nth() fragile to render order |
4645
| `page.$$eval(...)` to extract data | `expect(locator).toContainText()` or `.toHaveText()` | evaluate snapshots DOM; locator assertions retry |
4746

48-
Preferred assertions: `toBeVisible`, `toHaveText`, `toHaveCount`, `toHaveCSS`, `toContainText`, `toHaveScreenshot`.
49-
5047
## Locator Patterns
5148

52-
| Don't | Do Instead | Why |
53-
| --------------------------------------- | ------------------------------------------ | --------------------------------------- |
54-
| `.nth(N)` on ambiguous selectors | `.mx-name-*` attribute selectors | nth fragile to DOM order |
55-
| Complex CSS selectors | `page.locator(".mx-name-widgetName")` | mx-name attributes are stable, semantic |
56-
| `page.click("text=...")` for navigation | `page.locator(".mx-name-navItem").click()` | Text fragile to i18n/copy changes |
49+
Prefer `.mx-name-*` attributes — set by Mendix Studio Pro from widget names, stable across DOM refactors and i18n changes.
50+
51+
| Don't | Do Instead | Why |
52+
| ----------------------------------- | ----------------------------------------------- | -------------------------------------------- |
53+
| `.nth(N)` on ambiguous selectors | `.mx-name-*` attribute selectors | nth fragile to DOM order |
54+
| `page.click("text=...")` standalone | `.mx-name-*` or compose: CSS scope + role/label | Text alone = false positive, fragile to i18n |
55+
| Asserting text content in E2E | Unit/snapshot tests for text correctness | Text assertions belong in unit tests |
56+
57+
When `.mx-name-*` is not available, compose locators — see [Playwright locator docs](https://playwright.dev/docs/locators):
58+
59+
```javascript
60+
// mx-name — preferred
61+
page.locator(".mx-name-btnSubmit");
62+
63+
// composed: CSS scope + role
64+
page.locator(".mx-name-myForm").getByRole("button", { name: "Save" });
65+
66+
// composed: CSS scope + label
67+
page.locator(".mx-name-myWidget").getByLabel("Start date");
68+
```
5769

5870
## Screenshot Testing
5971

6072
- No per-test `{ threshold: N }` or `{ maxDiffPixels: N }` overrides — use global config (`threshold: 0.1`)
6173
- Always ensure element is visible before screenshot: `await expect(el).toBeVisible()`
6274
- Animations disabled globally (`animations: "disabled"` + `reducedMotion: "reduce"`)
6375

64-
## Session Management
65-
66-
- Worker-scoped sessions: 1 Mendix session per Playwright worker
67-
- Workers: 4 in CI, 2 locally (stays under 5-session license limit)
68-
- No manual `afterEach` logout — fixture handles cleanup
69-
- No per-test browser context creation
70-
7176
## ESLint Enforcement
7277

73-
These rules are configured in `automation/run-e2e/eslint.config.mjs`:
78+
Configured in `automation/run-e2e/eslint.config.mjs`:
7479

7580
```
7681
playwright/no-wait-for-timeout: error
7782
playwright/no-networkidle: warn
7883
playwright/prefer-web-first-assertions: warn
7984
```
8085

81-
## Code Review Checklist
82-
83-
- [ ] Uses `@mendix/run-e2e/fixtures` import (not `@playwright/test`)
84-
- [ ] No `waitForTimeout` calls
85-
- [ ] No `waitForLoadState("networkidle")` without explicit justification
86-
- [ ] All assertions use web-first Playwright assertions
87-
- [ ] No per-test screenshot threshold overrides
88-
- [ ] No manual `afterEach` logout
89-
- [ ] Locators use `.mx-name-*` attributes where possible
90-
- [ ] Tests tagged `@smoke` if they cover critical paths
91-
9286
## Spec File Template
9387

9488
```javascript
9589
import { test, expect } from "@mendix/run-e2e/fixtures";
96-
import { waitForMendixApp } from "@mendix/run-e2e/mendix-helpers";
9790

9891
test.describe("WidgetName", () => {
9992
test.beforeEach(async ({ page }) => {
10093
await page.goto("/");
101-
await waitForMendixApp(page);
10294
});
10395

104-
test("describes user-visible behavior", async ({ page }) => {
96+
test("describes user-visible behavior @smoke", async ({ page }) => {
10597
// Arrange
10698
await page.locator(".mx-name-navItem").click();
107-
await waitForMendixApp(page);
10899

109100
// Act
110101
await page.locator(".mx-name-myWidget .some-input").fill("value");

0 commit comments

Comments
 (0)