Skip to content

Commit 25f43ec

Browse files
test: add new e2e workflow cc command using mxcli
1 parent 088acab commit 25f43ec

7 files changed

Lines changed: 871 additions & 0 deletions

File tree

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
You are helping the developer write or improve Playwright e2e specs for a Mendix pluggable widget in the web-widgets monorepo.
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.
24+
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):**
36+
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+
}
45+
46+
test.beforeEach(async ({ page }) => {
47+
await navigateViaMenu(page, "Different Views", "Listen To Grid");
48+
});
49+
```
50+
51+
Get the exact menu labels from the test project:
52+
53+
```bash
54+
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+
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
78+
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+
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:
134+
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
140+
```
141+
142+
Keep each spec file focused. Avoid one giant file — Playwright runs files in parallel and smaller files improve isolation.
143+
144+
### Visual snapshots
145+
146+
Use sparingly — only for the main page to catch unintended rendering regressions:
147+
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+
```
202+
203+
## Running tests
204+
205+
```bash
206+
# Full CI run (Docker — downloads project, builds widget, runs Playwright):
207+
pnpm --filter @mendix/<widget> run e2e
208+
209+
# Skip re-downloading the test project:
210+
pnpm --filter @mendix/<widget> run e2e --no-setup-project
211+
212+
# Also skip rebuilding the widget MPK:
213+
pnpm --filter @mendix/<widget> run e2e --no-setup-project --no-update-project
214+
```
215+
216+
⚠️ Pass flags directly — do NOT use `pnpm run e2e -- --no-setup-project`. The `--` separator causes yargs-parser to ignore the flags.
217+
218+
## Checklist before marking a spec done
219+
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`)

0 commit comments

Comments
 (0)