|
1 | 1 | You are helping the developer write or improve Playwright e2e specs for a Mendix pluggable widget in the web-widgets monorepo. |
2 | 2 |
|
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. |
24 | 4 |
|
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 |
36 | 6 |
|
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` |
45 | 10 |
|
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 |
50 | 12 |
|
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`: |
52 | 14 |
|
53 | 15 | ```bash |
| 16 | +# Full overview: modules, pages, entities, navigation menu labels |
54 | 17 | 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. |
61 | 18 |
|
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) |
78 | 20 | 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 | | -``` |
97 | 21 |
|
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> |
134 | 24 |
|
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>" |
140 | 27 | ``` |
141 | 28 |
|
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: "..." })`. |
143 | 30 |
|
144 | | -### Visual snapshots |
| 31 | +## Spec file split |
145 | 32 |
|
146 | | -Use sparingly — only for the main page to catch unintended rendering regressions: |
| 33 | +Standard naming convention per widget: |
147 | 34 |
|
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 |
202 | 39 |
|
203 | 40 | ## Running tests |
204 | 41 |
|
205 | 42 | ```bash |
206 | | -# Full CI run (Docker — downloads project, builds widget, runs Playwright): |
| 43 | +# Full CI run (Docker): |
207 | 44 | pnpm --filter @mendix/<widget> run e2e |
208 | 45 |
|
209 | 46 | # Skip re-downloading the test project: |
210 | 47 | pnpm --filter @mendix/<widget> run e2e --no-setup-project |
211 | 48 |
|
212 | | -# Also skip rebuilding the widget MPK: |
| 49 | +# Skip re-downloading and rebuilding: |
213 | 50 | pnpm --filter @mendix/<widget> run e2e --no-setup-project --no-update-project |
214 | 51 | ``` |
215 | 52 |
|
216 | 53 | ⚠️ Pass flags directly — do NOT use `pnpm run e2e -- --no-setup-project`. The `--` separator causes yargs-parser to ignore the flags. |
217 | 54 |
|
218 | | -## Checklist before marking a spec done |
| 55 | +## Checklist |
219 | 56 |
|
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