Skip to content

Commit 0785631

Browse files
chore: remove e2e guidelines duplications
1 parent 25f43ec commit 0785631

3 files changed

Lines changed: 101 additions & 409 deletions

File tree

Lines changed: 29 additions & 192 deletions
Original file line numberDiff line numberDiff line change
@@ -1,225 +1,62 @@
11
You are helping the developer write or improve Playwright e2e specs for a Mendix pluggable widget in the web-widgets monorepo.
22

3-
## Context
4-
5-
- E2e specs live at: `packages/pluggableWidgets/<widget>/e2e/*.spec.js`
6-
- Specs run against a live Mendix runtime (Docker CI or Studio Pro dev mode)
7-
- Playwright strict mode is ON — a locator that resolves to multiple elements throws immediately
8-
- Each test opens a new Mendix session; the license allows max 5 concurrent sessions
9-
10-
## Mandatory boilerplate
11-
12-
Every spec file must include a session cleanup hook:
13-
14-
```js
15-
import { test, expect } from "@playwright/test";
16-
17-
test.afterEach("Cleanup session", async ({ page }) => {
18-
// Force logout after every test — Mendix limits concurrent sessions to 5
19-
await page.evaluate(() => window.mx.session.logout());
20-
});
21-
```
22-
23-
Without this, sessions accumulate and later tests will fail with a session-limit error.
3+
**Read first**: `docs/requirements/e2e-test-guidelines.md` — owns all rules for imports, assertions, locators, navigation, and snapshots. Do not duplicate them here.
244

25-
## Navigating to pages
26-
27-
**Option A — Direct URL (use when the page has a URL slug configured in Studio Pro):**
28-
29-
```js
30-
test.beforeEach(async ({ page }) => {
31-
await page.goto("/p/my-page-slug");
32-
});
33-
```
34-
35-
**Option B — Navigation menu (use when no URL slug exists — this is the safe default):**
5+
## Context
366

37-
```js
38-
async function navigateViaMenu(page, menuLabel, itemLabel) {
39-
await page.goto("/");
40-
await page.waitForLoadState("networkidle");
41-
await page.getByRole("menuitem", { name: menuLabel }).click();
42-
await page.getByRole("menuitem", { name: itemLabel }).click();
43-
await page.waitForLoadState("networkidle");
44-
}
7+
- Specs live at: `packages/pluggableWidgets/<widget>/e2e/*.spec.js`
8+
- Playwright strict mode is ON
9+
- Full rules: `docs/requirements/e2e-test-guidelines.md`
4510

46-
test.beforeEach(async ({ page }) => {
47-
await navigateViaMenu(page, "Different Views", "Listen To Grid");
48-
});
49-
```
11+
## Discovering selectors and navigation from the test project
5012

51-
Get the exact menu labels from the test project:
13+
Before writing selectors, look up the actual widget names and menu labels in the `.mpr`:
5214

5315
```bash
16+
# Full overview: modules, pages, entities, navigation menu labels
5417
bash automation/mxcli/mx-testproject.sh inspect <widget>
55-
# Navigation is included at the end of inspect output.
56-
# Or run directly:
57-
bash automation/mxcli/mx-testproject.sh exec <widget> "DESCRIBE NAVIGATION Responsive"
58-
```
59-
60-
Always call `waitForLoadState("networkidle")` after navigation — Mendix pages fire multiple XHR requests on load and widgets may not be ready yet.
6118

62-
## Selectors
63-
64-
**Use `.mx-name-<widgetName>` selectors** — they are stable across Mendix versions and match how widgets are named in Studio Pro.
65-
66-
```js
67-
// Good
68-
page.locator(".mx-name-badgeDanger");
69-
page.locator(".mx-name-dataGrid1");
70-
71-
// Avoid — brittle, tied to widget internals
72-
page.locator(".widget-badge span:first-child");
73-
```
74-
75-
To discover all `.mx-name-*` widget names available on a page, describe the page via mxcli:
76-
77-
```bash
19+
# Widget structure of a specific page (reveals .mx-name-* values)
7820
bash automation/mxcli/mx-testproject.sh exec <widget> "DESCRIBE PAGE <Module>.<PageName>"
79-
```
80-
81-
If you don't know the page name, first run `bash automation/mxcli/mx-testproject.sh list-pages <widget>`.
82-
83-
### Strict mode — multiple matches
84-
85-
A widget placed inside a list view or repeated container will produce multiple `.mx-name-*` elements. Playwright strict mode throws if your locator resolves to more than one element. Fix with `.first()` or a compound selector:
86-
87-
```js
88-
// Throws if badgeV23 appears more than once:
89-
await expect(page.locator(".mx-name-badgeV23")).toBeVisible(); //
90-
91-
// Safe:
92-
await expect(page.locator(".mx-name-badgeV23").first()).toBeVisible(); //
93-
94-
// Or narrow with a compound class (badge variant only, not label variant):
95-
await expect(page.locator(".widget-badge.badge.mx-name-badgeV23")).toBeVisible(); //
96-
```
9721

98-
The badge widget renders two `mx-name-*` elements when both a `badge` type and a `label` type share the same Studio Pro widget name — always check with `DESCRIBE PAGE` if you get unexpected strict-mode errors.
99-
100-
## Asserting content
101-
102-
**Wait for visibility before asserting text** — data-bound widgets may still be loading:
103-
104-
```js
105-
const badge = page.locator(".mx-name-badgeDanger");
106-
await expect(badge).toBeVisible();
107-
await expect(badge).toHaveText("Expected text");
108-
```
109-
110-
**For async state updates** (e.g., a data view that reloads when a grid row is selected), use a retrying assertion with a generous timeout instead of reading text synchronously:
111-
112-
```js
113-
// ❌ Synchronous read — may race with data view update:
114-
const text = await badge.textContent();
115-
expect(text?.trim().length).toBeGreaterThan(0);
116-
117-
// ✓ Auto-retrying assertion — waits up to 10 s for non-empty content:
118-
await expect(badge).toHaveText(/.+/, { timeout: 10000 });
119-
```
120-
121-
**Do not assert Atlas design classes** (e.g., `badge-danger`, `btn-primary`) — these are applied via Atlas design properties configured in Studio Pro and may or may not produce a matching CSS class depending on the Atlas version in the test project. Assert visibility and text content instead:
122-
123-
```js
124-
// ❌ Fragile — Atlas class may not be on the element:
125-
await expect(badge).toHaveClass(/badge-danger/);
126-
127-
// ✓ Robust — tests what the widget actually does:
128-
await expect(badge).toBeVisible();
129-
```
130-
131-
## Structuring tests
132-
133-
Group tests with `test.describe` by feature area. A good split for widget specs:
22+
# List pages if you don't know the page name
23+
bash automation/mxcli/mx-testproject.sh list-pages <widget>
13424

135-
```
136-
<widget>.spec.js — core behavior: rendering, data binding, visual snapshot
137-
onClick.spec.js — microflow, nanoflow, keyboard accessibility
138-
dataTypes.spec.js — each supported Mendix attribute type
139-
layouts.spec.js — widget behavior inside list view, tab, data view, listen-to-grid
25+
# Search for a widget name across all project strings
26+
bash automation/mxcli/mx-testproject.sh search <widget> "<keyword>"
14027
```
14128

142-
Keep each spec file focused. Avoid one giant file — Playwright runs files in parallel and smaller files improve isolation.
29+
The `inspect` output includes `DESCRIBE NAVIGATION Responsive` at the end — use those exact strings in `page.getByRole("menuitem", { name: "..." })`.
14330

144-
### Visual snapshots
31+
## Spec file split
14532

146-
Use sparingly — only for the main page to catch unintended rendering regressions:
33+
Standard naming convention per widget:
14734

148-
```js
149-
test("visual comparison", async ({ page }) => {
150-
await expect(page.locator(".mx-name-badgeDanger")).toBeVisible();
151-
await expect(page).toHaveScreenshot("badge.png");
152-
});
153-
```
154-
155-
When a data mutation in a prior test changes the snapshot baseline, update it:
156-
157-
```bash
158-
# Re-run only the visual test and update the snapshot:
159-
pnpm --filter @mendix/<widget> run e2e --no-setup-project --no-update-project -- --update-snapshots
160-
```
161-
162-
The snapshot file lives at `e2e/<spec>.spec.js-snapshots/<name>-chromium-darwin.png`.
163-
164-
## onClick patterns
165-
166-
**Nanoflow — verify via dialog:**
167-
168-
```js
169-
test("clicking badge calls nanoflow and shows result in dialog", async ({ page }) => {
170-
await page.locator(".mx-name-badgeCallNanoflow").click();
171-
await expect(page.locator(".modal-body")).toBeVisible();
172-
await expect(
173-
page
174-
.locator("div")
175-
.filter({ hasText: /^Data stringNewSuccess$/ })
176-
.locator("div")
177-
).toContainText("NewSuccess");
178-
});
179-
```
180-
181-
**Keyboard accessibility:**
182-
183-
```js
184-
test("badge is keyboard accessible — Enter key triggers nanoflow", async ({ page }) => {
185-
await page.locator(".mx-name-badgeCallNanoflow").focus();
186-
await page.keyboard.press("Enter");
187-
await expect(page.locator(".modal-body")).toBeVisible();
188-
});
189-
```
190-
191-
**Microflow smoke test (no dialog expected):**
192-
193-
```js
194-
test("clicking badge calls microflow without error", async ({ page }) => {
195-
const badge = page.locator(".mx-name-badgeV22");
196-
await expect(badge).toBeVisible();
197-
await badge.click();
198-
// Microflow click should not throw — page stays intact after click
199-
await expect(badge).toBeVisible();
200-
});
201-
```
35+
- `<widget>.spec.js` — core rendering and data binding
36+
- `onClick.spec.js` — microflow, nanoflow, keyboard accessibility
37+
- `dataTypes.spec.js` — each supported Mendix attribute type
38+
- `layouts.spec.js` — widget inside list view, tab, data view, listen-to-grid
20239

20340
## Running tests
20441

20542
```bash
206-
# Full CI run (Docker — downloads project, builds widget, runs Playwright):
43+
# Full CI run (Docker):
20744
pnpm --filter @mendix/<widget> run e2e
20845

20946
# Skip re-downloading the test project:
21047
pnpm --filter @mendix/<widget> run e2e --no-setup-project
21148

212-
# Also skip rebuilding the widget MPK:
49+
# Skip re-downloading and rebuilding:
21350
pnpm --filter @mendix/<widget> run e2e --no-setup-project --no-update-project
21451
```
21552

21653
⚠️ Pass flags directly — do NOT use `pnpm run e2e -- --no-setup-project`. The `--` separator causes yargs-parser to ignore the flags.
21754

218-
## Checklist before marking a spec done
55+
## Checklist
21956

220-
- [ ] `test.afterEach` with `window.mx.session.logout()` is present in every file
221-
- [ ] No locator resolves to more than one element without `.first()` or a compound selector
222-
- [ ] No assertions on Atlas design classes (e.g., `.toHaveClass(/badge-danger/)`)
223-
- [ ] Async state updates use retrying assertions (`toHaveText`, `toBeVisible`) not synchronous reads
224-
- [ ] `waitForLoadState("networkidle")` called after every menu-based navigation
225-
- [ ] All 32+ tests pass in CI (`pnpm run e2e`)
57+
- [ ] Import from `@mendix/run-e2e/fixtures` — NOT `@playwright/test`
58+
- [ ] No manual `afterEach` logout — fixture handles it
59+
- [ ] No `waitForLoadState("networkidle")` — prohibited, see guidelines
60+
- [ ] No locator matches multiple elements without `.first()` or a compound selector
61+
- [ ] No assertions on Atlas design classes
62+
- [ ] All tests pass in CI (`pnpm run e2e`)

0 commit comments

Comments
 (0)