@unotest/web 0.47.0 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/write-e2e-test/SKILL.md +7 -2
- package/.claude/skills/write-e2e-test-ground/SKILL.md +7 -2
- package/CHANGELOG.md +159 -3
- package/README.md +2 -1
- package/dist/config/schema.d.ts +139 -118
- package/dist/config/schema.js +1 -1
- package/dist/driver/index.d.ts +2 -2
- package/dist/driver/index.js +1 -1
- package/dist/dsl/index.d.ts +36 -4
- package/dist/dsl/index.js +1 -1
- package/dist/dsl/web-dsl-language-service.d.ts +1 -1
- package/dist/dsl/web-dsl-language-service.js +1 -1
- package/dist/inspection/page-inject.d.ts +1 -1
- package/dist/{interfaces-LE6GsJ0c.d.ts → interfaces-CTMGjJIs.d.ts} +8 -0
- package/dist/{linter-BWSAYBQy.d.ts → linter-RrjB1n9R.d.ts} +5 -0
- package/dist/mcp/server.js +1 -1
- package/dist/runner/cli.d.ts +1 -1
- package/dist/runner/cli.js +1 -1
- package/dist/runner/init/chrome-probe.js +1 -1
- package/dist/runner/init.d.ts +6 -1
- package/dist/runner/init.js +1 -1
- package/dist/runner/install-chromium.js +1 -1
- package/dist/runner/prepare-fix.js +1 -1
- package/dist/runner/scaffold-workspace.js +1 -1
- package/dist/runner/serve-fixture.js +1 -1
- package/dist/runner/web-runner-adapter.js +1 -1
- package/guides/dsl-reference.md +128 -30
- package/package.json +8 -8
- package/src/mcp/prompts/agent-test-author.md +10 -7
- package/types/unotest-dsl.d.ts +4 -4
package/guides/dsl-reference.md
CHANGED
|
@@ -79,6 +79,11 @@ step.soft("label", {tag: <expr>}, () => { … });
|
|
|
79
79
|
with the next statement and records nothing — same as resuming past
|
|
80
80
|
any failure on web.
|
|
81
81
|
|
|
82
|
+
Porting a `@playwright/test` spec with several `test(...)` calls — one
|
|
83
|
+
`step.soft` per former test — and running one test over a table or a
|
|
84
|
+
JSONL file of cases are worked through in
|
|
85
|
+
[Port a @playwright/test spec](https://docs.unotest.com/guides/port-a-spec/).
|
|
86
|
+
|
|
82
87
|
The data-driven gate, in full (`readJsonLine` reads the cases; the outer
|
|
83
88
|
`step` satisfies step coverage):
|
|
84
89
|
|
|
@@ -285,11 +290,15 @@ Each requires a locator as the receiver / first argument.
|
|
|
285
290
|
|
|
286
291
|
### `filter(options)`
|
|
287
292
|
|
|
288
|
-
Narrow down a locator. `hasText`, `hasNotText`, `has`, `hasNot
|
|
293
|
+
Narrow down a locator. `hasText`, `hasNotText`, `has`, `hasNot` — any
|
|
294
|
+
other key is refused, at lint time (`lint:filter-unknown-option`) and at
|
|
295
|
+
run time. A class or an attribute of the element itself goes into a CSS
|
|
296
|
+
`locator(...)` instead.
|
|
289
297
|
|
|
290
298
|
```js
|
|
291
299
|
getByRole('row').filter({hasText: 'Alice'})
|
|
292
300
|
getByRole('row').filter({has: getByTestId('admin-badge')})
|
|
301
|
+
locator('tr.done') // own class: CSS, not filter
|
|
293
302
|
```
|
|
294
303
|
|
|
295
304
|
### `first()`, `last()`, `nth(index)`
|
|
@@ -459,9 +468,11 @@ assertAttribute(getByRole('form'), 'aria-busy', 'true', {not: true})
|
|
|
459
468
|
|
|
460
469
|
### `assertVisible(loc, options?)` / `assertHidden(loc, options?)`
|
|
461
470
|
|
|
471
|
+
**Options:** `timeout?`.
|
|
472
|
+
|
|
462
473
|
```js
|
|
463
474
|
assertVisible(getByTestId('toast'))
|
|
464
|
-
assertHidden(getByTestId('spinner'))
|
|
475
|
+
assertHidden(getByTestId('spinner'), {timeout: 10000})
|
|
465
476
|
```
|
|
466
477
|
|
|
467
478
|
### `assertNeverAppears(loc, options)`
|
|
@@ -486,6 +497,8 @@ the whole window.
|
|
|
486
497
|
|
|
487
498
|
Compares `inputValue()` (`<input>`, `<textarea>`, native `<select>`).
|
|
488
499
|
|
|
500
|
+
**Options:** `timeout?`.
|
|
501
|
+
|
|
489
502
|
```js
|
|
490
503
|
assertValue(getByLabel('Email'), 'a@b.c')
|
|
491
504
|
```
|
|
@@ -509,8 +522,11 @@ value, and so does the failure.
|
|
|
509
522
|
|
|
510
523
|
### `assertCount(loc, expected, options?)`
|
|
511
524
|
|
|
525
|
+
**Options:** `timeout?`.
|
|
526
|
+
|
|
512
527
|
```js
|
|
513
528
|
assertCount(getByRole('listitem'), 5)
|
|
529
|
+
assertCount(getByRole('row'), 20, {timeout: 15000})
|
|
514
530
|
```
|
|
515
531
|
|
|
516
532
|
### `assertUrl(pattern, options?)`
|
|
@@ -541,7 +557,7 @@ assertTrue(probe.code == 0, 'app.log should contain "ready"');
|
|
|
541
557
|
Deep equality, the way JSON sees the two values: key order does not
|
|
542
558
|
count, and a missing key differs from a key set to `null`. The failure
|
|
543
559
|
names the first path that differs and both values, then a line diff of
|
|
544
|
-
the two sides:
|
|
560
|
+
the two sides, every line of them — nothing is folded away:
|
|
545
561
|
|
|
546
562
|
```js
|
|
547
563
|
body = parseJson(getAttribute(locator('#state'), 'data-json'));
|
|
@@ -1254,9 +1270,14 @@ Both fields are required. Anything else is refused on that line,
|
|
|
1254
1270
|
and changing it would need a new browser context — the page under test
|
|
1255
1271
|
would be lost. See [Viewport and scale](#viewport-and-scale).
|
|
1256
1272
|
|
|
1257
|
-
`setViewport(null)`
|
|
1258
|
-
|
|
1259
|
-
|
|
1273
|
+
`setViewport(null)` runs the test on the real window — but only before
|
|
1274
|
+
the test does anything in its browser: the first navigation, `mockRoute`,
|
|
1275
|
+
a cookie or storage value, the page clock, `emulateMedia`, a second page
|
|
1276
|
+
or context. It opens the browser context again with the same options;
|
|
1277
|
+
after any of those it is refused, since the new context would lose them.
|
|
1278
|
+
On a browser profile (`launch.userDataDir`, `launch.profile`) it is
|
|
1279
|
+
refused always. For a whole suite, set `viewport: null` in the config;
|
|
1280
|
+
for the runs of one collection, in its YAML (below).
|
|
1260
1281
|
|
|
1261
1282
|
### `emulateMedia({reducedMotion?, colorScheme?})`
|
|
1262
1283
|
|
|
@@ -1288,7 +1309,21 @@ Where the window's size and scale come from, first match wins:
|
|
|
1288
1309
|
replace the config's window for that context. `explore_start` with a
|
|
1289
1310
|
window while a context is already open is refused — call
|
|
1290
1311
|
`new_context` with it instead.
|
|
1291
|
-
2. **The
|
|
1312
|
+
2. **The collection.** `viewport:` in `unotest/e2e/_collections/<name>.yaml`
|
|
1313
|
+
applies to every scenario that collection runs; the same scenario run
|
|
1314
|
+
on its own keeps the config's window. `null` is the real window,
|
|
1315
|
+
`{width, height}` a size (whole pixels, both required):
|
|
1316
|
+
|
|
1317
|
+
```yaml
|
|
1318
|
+
viewport: null
|
|
1319
|
+
scenarios:
|
|
1320
|
+
- shots/gallery
|
|
1321
|
+
```
|
|
1322
|
+
|
|
1323
|
+
It works with a browser profile, where `setViewport(null)` cannot. A
|
|
1324
|
+
`viewport: null` here over a config `device` is refused before the
|
|
1325
|
+
first scenario starts, as in the config.
|
|
1326
|
+
3. **The config.** `unotest.config.mjs`:
|
|
1292
1327
|
|
|
1293
1328
|
```js
|
|
1294
1329
|
export default {
|
|
@@ -1315,12 +1350,12 @@ Where the window's size and scale come from, first match wins:
|
|
|
1315
1350
|
|
|
1316
1351
|
A `device` next to `viewport: null` is refused: a preset is a size and
|
|
1317
1352
|
a scale of its own.
|
|
1318
|
-
|
|
1353
|
+
4. **A device preset.** `device: "iPhone 14"` applies Playwright's
|
|
1319
1354
|
preset whole: size, scale, touch, mobile mode, user agent. The name
|
|
1320
1355
|
is Playwright's, exactly; an unknown name is refused with the
|
|
1321
1356
|
nearest ones. `viewport` next to `device` overrides the preset field
|
|
1322
1357
|
by field.
|
|
1323
|
-
|
|
1358
|
+
5. **The default:** 1280×720 at scale 1.
|
|
1324
1359
|
|
|
1325
1360
|
A preset's viewport is the page area, smaller than the device's screen:
|
|
1326
1361
|
`"iPhone 14"` is 390×664 (the browser chrome takes the rest of 844). To
|
|
@@ -1367,9 +1402,10 @@ goto('chrome-extension://' + id + '/popup.html');
|
|
|
1367
1402
|
`chrome-extension://<id>/sw.js`. A running worker comes back at once;
|
|
1368
1403
|
otherwise it waits up to `timeoutMs` (default: the navigation timeout).
|
|
1369
1404
|
`url` — a substring or `/regex/` when several extensions run.
|
|
1370
|
-
- `evaluateInServiceWorker(js, ...args)` — `evaluate` in the worker
|
|
1371
|
-
|
|
1372
|
-
|
|
1405
|
+
- `evaluateInServiceWorker(js, ...args)` — `evaluate` in the worker. The
|
|
1406
|
+
extras reach the body by the same rule as `evaluate`: none →
|
|
1407
|
+
`undefined`, one → the value itself, two or more → one array. JSON
|
|
1408
|
+
return; a returned Promise is awaited. Not discouraged by the
|
|
1373
1409
|
linter: `chrome.*` has no other way in.
|
|
1374
1410
|
- `extensionId()` — the id out of the worker's URL.
|
|
1375
1411
|
- MV3 stops an idle worker after ~30 s. Each call finds the worker anew,
|
|
@@ -2226,12 +2262,16 @@ getters or the action API.
|
|
|
2226
2262
|
height = evaluate(`document.documentElement.scrollHeight`);
|
|
2227
2263
|
```
|
|
2228
2264
|
|
|
2229
|
-
|
|
2265
|
+
The extra arguments reach the body by one rule: none → `undefined`,
|
|
2266
|
+
exactly one → the value itself, two or more → ONE array the body
|
|
2267
|
+
destructures. So one argument is `function(x)`, not `function([x])` —
|
|
2268
|
+
that one fails with `2 is not iterable`.
|
|
2230
2269
|
|
|
2231
2270
|
```js
|
|
2271
|
+
doubled = evaluate(`function(n) { return n * 2; }`, 21); // one → the value
|
|
2232
2272
|
result = evaluate(
|
|
2233
2273
|
`function([a, b]) { return window.myApp.compute(a, b); }`,
|
|
2234
|
-
10, 20
|
|
2274
|
+
10, 20 // two → one array
|
|
2235
2275
|
);
|
|
2236
2276
|
```
|
|
2237
2277
|
|
|
@@ -2239,8 +2279,9 @@ Backticks are **raw** — `${…}` interpolation is rejected at parse time
|
|
|
2239
2279
|
(see `vendor/dsl/EXTENSIONS.md`).
|
|
2240
2280
|
|
|
2241
2281
|
`evaluateOnLocator(loc, js, ...args)` — the same hatch on one element.
|
|
2242
|
-
The body gets the element first, then the extras by the same rule
|
|
2243
|
-
→ the value itself, two or more → one
|
|
2282
|
+
The body gets the element first, then the extras by the same rule as
|
|
2283
|
+
`evaluate`: none → `undefined`, one → the value itself, two or more → one
|
|
2284
|
+
array.
|
|
2244
2285
|
|
|
2245
2286
|
```js
|
|
2246
2287
|
ok = evaluateOnLocator(getByRole('main'), `(el, [a, b]) => el.children.length > a + b`, 1, 2);
|
|
@@ -2408,6 +2449,10 @@ step.soft("Question", {tag: q.id}, () => {
|
|
|
2408
2449
|
that called the helper.
|
|
2409
2450
|
- Without a run journal (exploration, an ad-hoc runtime) `note` goes to
|
|
2410
2451
|
the logger instead; nothing fails.
|
|
2452
|
+
- When a statement fails, the notes of its `step(...)` block (the
|
|
2453
|
+
innermost one, in call order) come with the failure: under the error
|
|
2454
|
+
in the `e2e` output as `note label: value`, and as `notes` in the
|
|
2455
|
+
failure `run_test` returns. A green step prints nothing extra.
|
|
2411
2456
|
|
|
2412
2457
|
---
|
|
2413
2458
|
|
|
@@ -2416,17 +2461,16 @@ step.soft("Question", {tag: q.id}, () => {
|
|
|
2416
2461
|
The DSL is JavaScript-shaped but parsed by a small AST engine
|
|
2417
2462
|
(`vendor/dsl/`), not V8. What's supported in scenario files:
|
|
2418
2463
|
|
|
2419
|
-
- **`function test_*(): { … }`** —
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
`
|
|
2423
|
-
|
|
2424
|
-
(`step("…", {tag: id}, …)`), or move shared journeys to
|
|
2425
|
-
`unotest/e2e/_helpers/`.
|
|
2464
|
+
- **`function test_*(): { … }`** — a test. A file may hold several; each
|
|
2465
|
+
is its own run with its own browser context, retry and verdict, and the
|
|
2466
|
+
viewer shows it as its own row under the file. `e2e <file>` plays them
|
|
2467
|
+
in order; `e2e <file> --test <name>` plays one. Functions of the file
|
|
2468
|
+
are shared between its tests, state is not.
|
|
2426
2469
|
- **Assignments** — `name = expression;` (no `let`/`const`/`var`).
|
|
2427
2470
|
- **`if` / `else`** and **`for` in the canonical shape**
|
|
2428
2471
|
`for (i = 0; i < N; i = i + 1) { … }` (increment must be an
|
|
2429
|
-
assignment — `i++` is rejected everywhere).
|
|
2472
|
+
assignment — `i++` is rejected everywhere). A loop over cases, each a
|
|
2473
|
+
tagged soft step: [Port a @playwright/test spec](https://docs.unotest.com/guides/port-a-spec/).
|
|
2430
2474
|
- **`break`** — inside a loop body only. Typical polling loop:
|
|
2431
2475
|
|
|
2432
2476
|
```js
|
|
@@ -2446,7 +2490,8 @@ The DSL is JavaScript-shaped but parsed by a small AST engine
|
|
|
2446
2490
|
- **Index access on data** — `docs[0]`, `docs[0].objectsCount`,
|
|
2447
2491
|
`res.body.items[0].id`: see [Strings, objects, indexes](#strings-objects-indexes).
|
|
2448
2492
|
Indexing a **Locator** is rejected — disambiguate with chain methods
|
|
2449
|
-
(`.filter(...)`), and `arr[i] = v` assignment stays unsupported
|
|
2493
|
+
(`.filter(...)`), and `arr[i] = v` assignment stays unsupported —
|
|
2494
|
+
`arr.push(v)` appends: see [Logical not and arrays](#logical-not-and-arrays).
|
|
2450
2495
|
- **Object literals** — as call arguments (`click(loc, {force: true})`)
|
|
2451
2496
|
and as values in a variable: see [Strings, objects, indexes](#strings-objects-indexes).
|
|
2452
2497
|
- **Member access** — `row.email` (dot form; `obj['key']` doesn't
|
|
@@ -2457,7 +2502,8 @@ The DSL is JavaScript-shaped but parsed by a small AST engine
|
|
|
2457
2502
|
[Escape rules](#escape-rules).
|
|
2458
2503
|
- **Comparison operators** — `==` / `!=` (loose: `"1" == 1` is true,
|
|
2459
2504
|
numeric strings coerce), `===` / `!==` (strict: type AND value must
|
|
2460
|
-
match, `1 === "1"` is false), `<` `<=` `>` `>=`, logical `&&` /
|
|
2505
|
+
match, `1 === "1"` is false), `<` `<=` `>` `>=`, logical `&&` / `||`
|
|
2506
|
+
and `!` (see [Logical not and arrays](#logical-not-and-arrays)).
|
|
2461
2507
|
Caveat: DOM reads (`textContent`, `getInputValue`, `getAttribute`)
|
|
2462
2508
|
return **strings** — for comparing against a number use `==`
|
|
2463
2509
|
(`getAttribute(loc, "data-count") === 5` is always false; write
|
|
@@ -2548,6 +2594,41 @@ assertTrue(res.body.items[0].status == 'ready', json(res.body));
|
|
|
2548
2594
|
first = dbQuery('SELECT id FROM users ORDER BY id')[0].id;
|
|
2549
2595
|
```
|
|
2550
2596
|
|
|
2597
|
+
### Logical not and arrays
|
|
2598
|
+
|
|
2599
|
+
Since 0.48.0.
|
|
2600
|
+
|
|
2601
|
+
**`!x`** negates, as in JS: the result is always `true` or `false`, and
|
|
2602
|
+
`0`, `""`, `null` and `false` are the falsy values — the same ones `if`
|
|
2603
|
+
treats as false. An array or an object is truthy, empty or not. `!`
|
|
2604
|
+
binds tighter than any other operator: `!a && b` is `(!a) && b`.
|
|
2605
|
+
|
|
2606
|
+
```js
|
|
2607
|
+
if (!isVisible(getByRole('dialog'))) { log('dialog closed'); }
|
|
2608
|
+
assertTrue(!out.stdout.includes('error'), out.stdout);
|
|
2609
|
+
```
|
|
2610
|
+
|
|
2611
|
+
**`arr.push(x, …)`** appends to the array in place and returns its new
|
|
2612
|
+
length. Other array methods (`pop`, `map`, `join`, …) are not there;
|
|
2613
|
+
`length` is.
|
|
2614
|
+
|
|
2615
|
+
```js
|
|
2616
|
+
rows = getByRole('row');
|
|
2617
|
+
ids = [];
|
|
2618
|
+
for (i = 0; i < count(rows); i = i + 1) {
|
|
2619
|
+
ids.push(textContent(rows.nth(i)));
|
|
2620
|
+
}
|
|
2621
|
+
assertTrue(ids.length > 0, json(ids));
|
|
2622
|
+
```
|
|
2623
|
+
|
|
2624
|
+
A variable holds the array itself, not a copy, as in JS: after
|
|
2625
|
+
`b = a; a.push(1)`, `b` has the new element too. So does a caller whose
|
|
2626
|
+
array a helper pushed onto. A helper that assigns a new array to its
|
|
2627
|
+
parameter (`list = []`) changes only its own name. `push` on a value
|
|
2628
|
+
that is not an array — a string, a number, a `null` field — fails the
|
|
2629
|
+
step with that type in the message; on a string the linter flags it
|
|
2630
|
+
first.
|
|
2631
|
+
|
|
2551
2632
|
### `teardown` — give the stand back
|
|
2552
2633
|
|
|
2553
2634
|
Since 0.44.0.
|
|
@@ -2656,10 +2737,27 @@ helpers. A function, parameter or variable of your own named
|
|
|
2656
2737
|
## Conventions
|
|
2657
2738
|
|
|
2658
2739
|
- **Snake-case test names** — `function test_login_happy_path()`.
|
|
2659
|
-
- **
|
|
2660
|
-
runs
|
|
2661
|
-
|
|
2662
|
-
|
|
2740
|
+
- **Tests of one feature share a file.** Several `test_*` in a file are
|
|
2741
|
+
several runs — a collection counts and reports each. Many cases of ONE
|
|
2742
|
+
behaviour can still be one test with a loop and
|
|
2743
|
+
`step("…", {tag: id}, …)` per case.
|
|
2744
|
+
- **Expected failures — `// @expect-fail`.** A test that is supposed to
|
|
2745
|
+
fail carries the marker in a `//` comment. Where it sits says which
|
|
2746
|
+
tests it is about:
|
|
2747
|
+
|
|
2748
|
+
```js
|
|
2749
|
+
// @expect-fail <- every test in this file
|
|
2750
|
+
|
|
2751
|
+
// The badge must stay hidden.
|
|
2752
|
+
// @expect-fail <- test_badge_hidden only
|
|
2753
|
+
function test_badge_hidden() { … }
|
|
2754
|
+
```
|
|
2755
|
+
|
|
2756
|
+
The `//` lines directly above a `function test_*` — no blank line
|
|
2757
|
+
between — belong to that test, the first one in the file included. The
|
|
2758
|
+
file's leading comments, set apart from the first function by a blank
|
|
2759
|
+
line, mark every test in it. A marker trailing code (`foo(); //
|
|
2760
|
+
@expect-fail`) or inside a body is a comment, not a marker.
|
|
2663
2761
|
- **Helpers stay in `unotest/e2e/_helpers/`** — these are project-owned,
|
|
2664
2762
|
not part of the core surface. Use sandbox primitives there.
|
|
2665
2763
|
- **Two kinds of helper.** A `flow_*` helper is a composite journey —
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unotest/web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.0",
|
|
4
4
|
"description": "AI-native E2E testing for web applications. MCP server (run_test / step / resume / inspect_runtime / agent_fix) + CLI runner + JavaScript DSL scenarios on a sandboxed AST engine + semantic DOM snapshots.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -85,18 +85,18 @@
|
|
|
85
85
|
"tsx": "^4.19.2",
|
|
86
86
|
"yaml": "^2.6.0",
|
|
87
87
|
"zod": "^3.23.8",
|
|
88
|
-
"@unotest/
|
|
89
|
-
"@unotest/
|
|
90
|
-
"@unotest/
|
|
91
|
-
"@unotest/
|
|
92
|
-
"@unotest/viewer": "^0.
|
|
88
|
+
"@unotest/core": "^0.49.0",
|
|
89
|
+
"@unotest/protocol": "^0.49.0",
|
|
90
|
+
"@unotest/dsl": "^0.49.0",
|
|
91
|
+
"@unotest/grounder-client": "^0.49.0",
|
|
92
|
+
"@unotest/viewer": "^0.49.0"
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
95
|
"@types/node": "^22.10.0",
|
|
96
96
|
"javascript-obfuscator": "^4.1.1",
|
|
97
97
|
"tsup": "^8.5.1",
|
|
98
98
|
"typescript": "^5.7.2",
|
|
99
|
-
"@unotest/grounder": "^0.
|
|
100
|
-
"@unotest/judge": "^0.
|
|
99
|
+
"@unotest/grounder": "^0.49.0",
|
|
100
|
+
"@unotest/judge": "^0.49.0"
|
|
101
101
|
}
|
|
102
102
|
}
|
|
@@ -49,13 +49,16 @@ The linter rejects a test in the e2e root (`lint:scenario-in-root`), and
|
|
|
49
49
|
from the area of the app under test. Shared helpers live in
|
|
50
50
|
`unotest/e2e/_helpers/`; `_template.js` is a starting point to copy.
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
A file may hold several `test_*` — the tests of one feature. Each test is
|
|
53
|
+
its own run: its own browser context, retry, verdict and row in the viewer,
|
|
54
|
+
under the file. Functions of the file are shared between them; state is
|
|
55
|
+
not — every test starts from a fresh context. `run_test` plays one test:
|
|
56
|
+
in a file of several, name it (`run_test {name, test: "test_…"}`); without
|
|
57
|
+
it the reply is `test_required` with the file's tests. Many cases of one
|
|
58
|
+
behaviour can still be ONE test with a loop and
|
|
59
|
+
`step("…", {tag: id}, () => { … })` per case — `step.soft` when a failed
|
|
60
|
+
case must not stop the run; shared journeys are `flow_*` helpers in
|
|
61
|
+
`unotest/e2e/_helpers/`.
|
|
59
62
|
|
|
60
63
|
## Locator hierarchy (D-22, hard rule)
|
|
61
64
|
|
package/types/unotest-dsl.d.ts
CHANGED
|
@@ -201,14 +201,14 @@ declare function assertAttribute(locator: Locator, name: string, value: string |
|
|
|
201
201
|
*
|
|
202
202
|
* Element is visible.
|
|
203
203
|
*/
|
|
204
|
-
declare function assertVisible(locator: Locator): void;
|
|
204
|
+
declare function assertVisible(locator: Locator, options?: any): void;
|
|
205
205
|
|
|
206
206
|
/**
|
|
207
207
|
* assertHidden(locator, { timeout? })
|
|
208
208
|
*
|
|
209
209
|
* Element is hidden or detached.
|
|
210
210
|
*/
|
|
211
|
-
declare function assertHidden(locator: Locator): void;
|
|
211
|
+
declare function assertHidden(locator: Locator, options?: any): void;
|
|
212
212
|
|
|
213
213
|
/**
|
|
214
214
|
* assertNeverAppears(locator, { withinMs })
|
|
@@ -222,14 +222,14 @@ declare function assertNeverAppears(locator: Locator, options: any): void;
|
|
|
222
222
|
*
|
|
223
223
|
* Input's value equals `expected`. On a checkbox/radio/switch: its checked state — pass 'true' or 'false'.
|
|
224
224
|
*/
|
|
225
|
-
declare function assertValue(locator: Locator, value: string): void;
|
|
225
|
+
declare function assertValue(locator: Locator, value: string, options?: any): void;
|
|
226
226
|
|
|
227
227
|
/**
|
|
228
228
|
* assertCount(locator, expected, { timeout? })
|
|
229
229
|
*
|
|
230
230
|
* Number of matches equals `expected`.
|
|
231
231
|
*/
|
|
232
|
-
declare function assertCount(locator: Locator, count: number): void;
|
|
232
|
+
declare function assertCount(locator: Locator, count: number, options?: any): void;
|
|
233
233
|
|
|
234
234
|
/**
|
|
235
235
|
* assertUrl(pattern, { timeout? })
|