feather-testing-core 0.2.0 → 0.3.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/README.md +44 -0
- package/dist/playwright/driver.d.ts +2 -0
- package/dist/playwright/driver.d.ts.map +1 -1
- package/dist/playwright/driver.js +33 -16
- package/dist/rtl/driver.d.ts +28 -6
- package/dist/rtl/driver.d.ts.map +1 -1
- package/dist/rtl/driver.js +57 -33
- package/dist/session.d.ts +6 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -172,6 +172,20 @@ Every method returns `this` for chaining. A single `await` at the start of the c
|
|
|
172
172
|
| `upload(label, path)` | Set a file input (found by label) to the file at `path` |
|
|
173
173
|
| `dropFile(selector, path)` | Dispatch a `DataTransfer` drop of the file onto a drop area |
|
|
174
174
|
|
|
175
|
+
#### Interactions address controls **exactly**
|
|
176
|
+
|
|
177
|
+
Every interaction above names the control it wants, and that name is matched in full: `clickButton("Check")` clicks the button named *Check*, never the sidebar chip named *"Checklist Run — checklist"*. Whitespace is still normalized, so multi-line markup and padded labels keep working.
|
|
178
|
+
|
|
179
|
+
This matters because Playwright's bare-string matchers are case-insensitive *substring* matchers. Left as-is, a verb aimed at one control silently widens to any other control whose name merely contains the same text — and the run dies on a strict-mode violation that only appears when both are on screen at once, which turns a naming collision into an ordering-dependent flake. RTL matches whole strings by default, so with this both adapters answer the same question.
|
|
180
|
+
|
|
181
|
+
Assertions are the deliberate exception: `assertText` / `refuteText` / `assertHas` ask *"does this text appear"*, so they stay substring matches. An exact `refuteText("Check")` would pass while *Checklist Run* is plainly on the page.
|
|
182
|
+
|
|
183
|
+
To act on a control whose name is genuinely a prefix of another's, scope the lookup rather than loosening it:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
await session.within("main", (s) => s.clickButton("Check"));
|
|
187
|
+
```
|
|
188
|
+
|
|
175
189
|
#### How `submit()` finds the submit button
|
|
176
190
|
|
|
177
191
|
`submit()` tracks the `<form>` element from the last `fillIn`, `selectOption`, `check`, `uncheck`, or `choose` call, then uses this strategy:
|
|
@@ -392,6 +406,36 @@ The RTL adapter runs in JSDOM, which has no real browser. These methods are not
|
|
|
392
406
|
- `assertPath()` / `refutePath()` — no URL in JSDOM
|
|
393
407
|
- `assertHas()` / `refuteHas()` — RTL discourages CSS selectors; use `assertText()` instead
|
|
394
408
|
|
|
409
|
+
### Extending the RTL adapter
|
|
410
|
+
|
|
411
|
+
`RTLDriver` is meant to be subclassed when an app's markup needs a different lookup, so that a host harness binds *this* DSL rather than reimplementing it. Everything worth specializing is `protected`:
|
|
412
|
+
|
|
413
|
+
| Member | Why you'd override it |
|
|
414
|
+
|--------|----------------------|
|
|
415
|
+
| `findField(label)` | The single label-addressed lookup. Every labelled verb — `fillIn`, `selectOption`, `check`, `uncheck`, `upload`, `assertValue`, `assertChecked`, `assertSelected`, `assertOptions` — goes through it, so one override retargets them all |
|
|
416
|
+
| `scoped(element)` | Factory used by `within()`, so a scoped session keeps your driver's behaviour |
|
|
417
|
+
| `user`, `root`, `container`, `lastFormElement`, `timeout` | Shared state the built-in verbs read and write |
|
|
418
|
+
|
|
419
|
+
```ts
|
|
420
|
+
class WrapperLabelDriver extends RTLDriver {
|
|
421
|
+
// Labels with no htmlFor, control is a sibling inside a wrapper div
|
|
422
|
+
protected override async findField(label: string): Promise<HTMLElement> {
|
|
423
|
+
for (const l of this.rootElement().querySelectorAll("label")) {
|
|
424
|
+
if (l.textContent?.trim() !== label) continue;
|
|
425
|
+
const control = l.parentElement?.querySelector("input, textarea, select");
|
|
426
|
+
if (control) return control as HTMLElement;
|
|
427
|
+
}
|
|
428
|
+
throw new Error(`no field labelled '${label}'`);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
protected override scoped(element: HTMLElement) {
|
|
432
|
+
return new WrapperLabelDriver(this.user, element, this.timeout);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
The third constructor argument is a per-lookup timeout in ms; omit it to keep RTL's own default.
|
|
438
|
+
|
|
395
439
|
## Exports
|
|
396
440
|
|
|
397
441
|
```ts
|
|
@@ -15,6 +15,8 @@ export declare class PlaywrightDriver implements TestDriver<PlaywrightStepContex
|
|
|
15
15
|
click(text: string): Promise<void>;
|
|
16
16
|
clickLink(text: string): Promise<void>;
|
|
17
17
|
clickButton(text: string): Promise<void>;
|
|
18
|
+
/** The single label-addressed lookup: every labelled verb goes through it. */
|
|
19
|
+
private labelled;
|
|
18
20
|
private fieldByLabelOrPlaceholder;
|
|
19
21
|
fillIn(label: string, value: string): Promise<void>;
|
|
20
22
|
selectOption(label: string, option: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/playwright/driver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,OAAO,EAAgB,MAAM,kBAAkB,CAAC;AACzE,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,EACX,MAAM,aAAa,CAAC;AAErB,2EAA2E;AAC3E,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,IAAI,CAAC;IACX,yEAAyE;IACzE,KAAK,EAAE,IAAI,GAAG,OAAO,CAAC;CACvB;
|
|
1
|
+
{"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/playwright/driver.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { basename } from "node:path";
|
|
3
3
|
import { expect, test } from "@playwright/test";
|
|
4
|
+
/**
|
|
5
|
+
* Playwright matches a bare string name/label/text as a case-insensitive
|
|
6
|
+
* SUBSTRING. That makes every text-addressed verb ambient: `clickButton('Check')`
|
|
7
|
+
* also matches an unrelated "Checklist Run — checklist" control that happens to
|
|
8
|
+
* be on the page, and the run dies on a strict-mode violation whose appearance
|
|
9
|
+
* depends on what else rendered. When a spec names a control it means *that*
|
|
10
|
+
* control, so every addressing matcher here passes `exact: true`. Playwright
|
|
11
|
+
* still normalizes whitespace under exact matching, so multi-line markup and
|
|
12
|
+
* padded labels keep working.
|
|
13
|
+
*
|
|
14
|
+
* Assertions (assertText/refuteText/assertHas) deliberately stay substring:
|
|
15
|
+
* they ask "does this text appear", and an exact `refuteText` would pass while
|
|
16
|
+
* the text is plainly on the page inside a longer string.
|
|
17
|
+
*/
|
|
18
|
+
const EXACT = { exact: true };
|
|
4
19
|
export class PlaywrightDriver {
|
|
5
20
|
page;
|
|
6
21
|
scope;
|
|
@@ -13,23 +28,25 @@ export class PlaywrightDriver {
|
|
|
13
28
|
await this.page.goto(path);
|
|
14
29
|
}
|
|
15
30
|
async click(text) {
|
|
16
|
-
await this.scope.getByText(text).click();
|
|
31
|
+
await this.scope.getByText(text, EXACT).click();
|
|
17
32
|
}
|
|
18
33
|
async clickLink(text) {
|
|
19
|
-
await this.scope.getByRole("link", { name: text }).click();
|
|
34
|
+
await this.scope.getByRole("link", { name: text, ...EXACT }).click();
|
|
20
35
|
}
|
|
21
36
|
async clickButton(text) {
|
|
22
|
-
await this.scope.getByRole("button", { name: text }).click();
|
|
37
|
+
await this.scope.getByRole("button", { name: text, ...EXACT }).click();
|
|
38
|
+
}
|
|
39
|
+
/** The single label-addressed lookup: every labelled verb goes through it. */
|
|
40
|
+
labelled(label) {
|
|
41
|
+
return this.scope.getByLabel(label, EXACT);
|
|
23
42
|
}
|
|
24
43
|
fieldByLabelOrPlaceholder(label) {
|
|
25
44
|
// .or() lets Playwright auto-wait on whichever appears, so
|
|
26
45
|
// async-rendered labeled fields don't fall through to the
|
|
27
|
-
// placeholder branch.
|
|
46
|
+
// placeholder branch. Both branches match exactly — substring
|
|
28
47
|
// matching would collide with labels ("Name" vs placeholder
|
|
29
48
|
// "Nickname") and trip strict mode.
|
|
30
|
-
return this.scope
|
|
31
|
-
.getByLabel(label)
|
|
32
|
-
.or(this.scope.getByPlaceholder(label, { exact: true }));
|
|
49
|
+
return this.labelled(label).or(this.scope.getByPlaceholder(label, EXACT));
|
|
33
50
|
}
|
|
34
51
|
async fillIn(label, value) {
|
|
35
52
|
const field = this.fieldByLabelOrPlaceholder(label);
|
|
@@ -37,22 +54,22 @@ export class PlaywrightDriver {
|
|
|
37
54
|
this.lastFormLocator = this.scope.locator("form", { has: field });
|
|
38
55
|
}
|
|
39
56
|
async selectOption(label, option) {
|
|
40
|
-
const select = this.
|
|
57
|
+
const select = this.labelled(label);
|
|
41
58
|
await select.selectOption({ label: option });
|
|
42
59
|
this.lastFormLocator = this.scope.locator("form", { has: select });
|
|
43
60
|
}
|
|
44
61
|
async check(label) {
|
|
45
|
-
const checkbox = this.
|
|
62
|
+
const checkbox = this.labelled(label);
|
|
46
63
|
await checkbox.check();
|
|
47
64
|
this.lastFormLocator = this.scope.locator("form", { has: checkbox });
|
|
48
65
|
}
|
|
49
66
|
async uncheck(label) {
|
|
50
|
-
const checkbox = this.
|
|
67
|
+
const checkbox = this.labelled(label);
|
|
51
68
|
await checkbox.uncheck();
|
|
52
69
|
this.lastFormLocator = this.scope.locator("form", { has: checkbox });
|
|
53
70
|
}
|
|
54
71
|
async choose(label) {
|
|
55
|
-
const radio = this.scope.getByRole("radio", { name: label });
|
|
72
|
+
const radio = this.scope.getByRole("radio", { name: label, ...EXACT });
|
|
56
73
|
await radio.check();
|
|
57
74
|
this.lastFormLocator = this.scope.locator("form", { has: radio });
|
|
58
75
|
}
|
|
@@ -83,7 +100,7 @@ export class PlaywrightDriver {
|
|
|
83
100
|
}
|
|
84
101
|
}
|
|
85
102
|
async upload(label, path) {
|
|
86
|
-
const input = this.
|
|
103
|
+
const input = this.labelled(label);
|
|
87
104
|
await input.setInputFiles(path);
|
|
88
105
|
this.lastFormLocator = this.scope.locator("form", { has: input });
|
|
89
106
|
}
|
|
@@ -133,17 +150,17 @@ export class PlaywrightDriver {
|
|
|
133
150
|
await expect(this.fieldByLabelOrPlaceholder(label)).toHaveValue(value);
|
|
134
151
|
}
|
|
135
152
|
async assertChecked(label) {
|
|
136
|
-
await expect(this.
|
|
153
|
+
await expect(this.labelled(label)).toBeChecked();
|
|
137
154
|
}
|
|
138
155
|
async refuteChecked(label) {
|
|
139
|
-
await expect(this.
|
|
156
|
+
await expect(this.labelled(label)).not.toBeChecked();
|
|
140
157
|
}
|
|
141
158
|
async assertSelected(label, optionLabel) {
|
|
142
|
-
const select = this.
|
|
159
|
+
const select = this.labelled(label);
|
|
143
160
|
await expect(select.locator("option:checked")).toHaveText(optionLabel);
|
|
144
161
|
}
|
|
145
162
|
async assertOptions(label, optionLabels) {
|
|
146
|
-
const select = this.
|
|
163
|
+
const select = this.labelled(label);
|
|
147
164
|
await expect(select.locator("option")).toHaveText(optionLabels);
|
|
148
165
|
}
|
|
149
166
|
async assertPath(path, opts) {
|
package/dist/rtl/driver.d.ts
CHANGED
|
@@ -10,14 +10,36 @@ export interface RTLStepContext {
|
|
|
10
10
|
/**
|
|
11
11
|
* RTL adapter implementing the subset of TestDriver that applies in JSDOM.
|
|
12
12
|
* Navigation methods (visit, assertPath, refutePath) are not supported.
|
|
13
|
+
*
|
|
14
|
+
* Everything a host adapter is likely to specialize is `protected`: the
|
|
15
|
+
* label-to-control lookup (`findField`), the scoped-driver factory
|
|
16
|
+
* (`scoped`), and the `user` / `root` / `lastFormElement` state the verbs
|
|
17
|
+
* share. Subclass it rather than reimplementing the DSL when an app's markup
|
|
18
|
+
* needs a different lookup — that is how feather-testing-postgres binds this
|
|
19
|
+
* driver to markup whose labels are wrapper siblings rather than `htmlFor`
|
|
20
|
+
* targets.
|
|
13
21
|
*/
|
|
14
22
|
export declare class RTLDriver implements TestDriver<RTLStepContext> {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
protected user: UserEvent;
|
|
24
|
+
/** The element every query and selector in this driver resolves against. */
|
|
25
|
+
protected root: HTMLElement;
|
|
26
|
+
protected container: ReturnType<typeof rtlWithin>;
|
|
27
|
+
protected lastFormElement: HTMLFormElement | null;
|
|
28
|
+
/** Per-lookup timeout in ms; undefined leaves RTL's own default in place. */
|
|
29
|
+
protected timeout: number | undefined;
|
|
30
|
+
constructor(user?: UserEvent, container?: HTMLElement, timeout?: number);
|
|
31
|
+
protected rootElement(): HTMLElement;
|
|
32
|
+
/** Options forwarded to every async query and waitFor call. */
|
|
33
|
+
protected waitOpts(): {
|
|
34
|
+
timeout?: number;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* The single label-addressed lookup: every labelled verb goes through it,
|
|
38
|
+
* so overriding this one method retargets them all.
|
|
39
|
+
*/
|
|
40
|
+
protected findField(label: string): Promise<HTMLElement>;
|
|
41
|
+
/** Subclasses override this so within() yields a driver of their own type. */
|
|
42
|
+
protected scoped(element: HTMLElement): TestDriver<RTLStepContext>;
|
|
21
43
|
visit(): Promise<void>;
|
|
22
44
|
click(text: string): Promise<void>;
|
|
23
45
|
clickLink(text: string): Promise<void>;
|
package/dist/rtl/driver.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/rtl/driver.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,MAAM,EAEN,MAAM,IAAI,SAAS,EACpB,MAAM,wBAAwB,CAAC;AAChC,OAAkB,EAAE,KAAK,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEhE,oEAAoE;AACpE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,SAAS,CAAC;IAChB,qEAAqE;IACrE,SAAS,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,GAAG,OAAO,MAAM,CAAC;CACzD;AAOD
|
|
1
|
+
{"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/rtl/driver.ts"],"names":[],"mappings":"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"}
|
package/dist/rtl/driver.js
CHANGED
|
@@ -3,52 +3,76 @@ import userEvent from "@testing-library/user-event";
|
|
|
3
3
|
/**
|
|
4
4
|
* RTL adapter implementing the subset of TestDriver that applies in JSDOM.
|
|
5
5
|
* Navigation methods (visit, assertPath, refutePath) are not supported.
|
|
6
|
+
*
|
|
7
|
+
* Everything a host adapter is likely to specialize is `protected`: the
|
|
8
|
+
* label-to-control lookup (`findField`), the scoped-driver factory
|
|
9
|
+
* (`scoped`), and the `user` / `root` / `lastFormElement` state the verbs
|
|
10
|
+
* share. Subclass it rather than reimplementing the DSL when an app's markup
|
|
11
|
+
* needs a different lookup — that is how feather-testing-postgres binds this
|
|
12
|
+
* driver to markup whose labels are wrapper siblings rather than `htmlFor`
|
|
13
|
+
* targets.
|
|
6
14
|
*/
|
|
7
15
|
export class RTLDriver {
|
|
8
16
|
user;
|
|
17
|
+
/** The element every query and selector in this driver resolves against. */
|
|
18
|
+
root;
|
|
9
19
|
container;
|
|
10
20
|
lastFormElement = null;
|
|
11
|
-
|
|
21
|
+
/** Per-lookup timeout in ms; undefined leaves RTL's own default in place. */
|
|
22
|
+
timeout;
|
|
23
|
+
constructor(user, container, timeout) {
|
|
12
24
|
this.user = user ?? userEvent.setup();
|
|
13
|
-
this.
|
|
25
|
+
this.root = container ?? document.body;
|
|
26
|
+
this.container = rtlWithin(this.root);
|
|
27
|
+
this.timeout = timeout;
|
|
14
28
|
}
|
|
15
29
|
rootElement() {
|
|
16
|
-
return this.
|
|
17
|
-
? document.body
|
|
18
|
-
: (this.container
|
|
19
|
-
.container ?? document.body);
|
|
30
|
+
return this.root;
|
|
20
31
|
}
|
|
21
|
-
async
|
|
32
|
+
/** Options forwarded to every async query and waitFor call. */
|
|
33
|
+
waitOpts() {
|
|
34
|
+
return this.timeout === undefined ? {} : { timeout: this.timeout };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The single label-addressed lookup: every labelled verb goes through it,
|
|
38
|
+
* so overriding this one method retargets them all.
|
|
39
|
+
*/
|
|
40
|
+
async findField(label) {
|
|
22
41
|
try {
|
|
23
|
-
return await this.container.findByLabelText(label);
|
|
42
|
+
return await this.container.findByLabelText(label, undefined, this.waitOpts());
|
|
24
43
|
}
|
|
25
44
|
catch {
|
|
26
|
-
return await this.container.findByPlaceholderText(label);
|
|
45
|
+
return await this.container.findByPlaceholderText(label, undefined, this.waitOpts());
|
|
27
46
|
}
|
|
28
47
|
}
|
|
48
|
+
/** Subclasses override this so within() yields a driver of their own type. */
|
|
49
|
+
scoped(element) {
|
|
50
|
+
return new RTLDriver(this.user, element, this.timeout);
|
|
51
|
+
}
|
|
29
52
|
async visit() {
|
|
30
53
|
throw new Error("visit() is not available in the RTL adapter. Render the desired component directly.");
|
|
31
54
|
}
|
|
32
55
|
async click(text) {
|
|
33
|
-
const element = await this.container.findByText(text);
|
|
56
|
+
const element = await this.container.findByText(text, undefined, this.waitOpts());
|
|
34
57
|
await this.user.click(element);
|
|
35
58
|
}
|
|
36
59
|
async clickLink(text) {
|
|
37
|
-
const link = await this.container.findByRole("link", { name: text });
|
|
60
|
+
const link = await this.container.findByRole("link", { name: text }, this.waitOpts());
|
|
38
61
|
await this.user.click(link);
|
|
39
62
|
}
|
|
40
63
|
async clickButton(text) {
|
|
41
|
-
const button = await this.container.findByRole("button", { name: text });
|
|
64
|
+
const button = await this.container.findByRole("button", { name: text }, this.waitOpts());
|
|
42
65
|
await this.user.click(button);
|
|
43
66
|
}
|
|
44
67
|
async fillIn(label, value) {
|
|
45
|
-
const input = await this.
|
|
68
|
+
const input = await this.findField(label);
|
|
46
69
|
await this.user.clear(input);
|
|
47
|
-
|
|
70
|
+
if (value)
|
|
71
|
+
await this.user.type(input, value);
|
|
48
72
|
this.lastFormElement = input.closest("form");
|
|
49
73
|
}
|
|
50
74
|
async selectOption(label, option) {
|
|
51
|
-
const select = await this.
|
|
75
|
+
const select = await this.findField(label);
|
|
52
76
|
const optionEl = Array.from(select.querySelectorAll("option")).find((o) => o.textContent?.trim() === option);
|
|
53
77
|
if (!optionEl) {
|
|
54
78
|
throw new Error(`selectOption('${label}', '${option}'): no <option> with text '${option}' found.`);
|
|
@@ -57,21 +81,21 @@ export class RTLDriver {
|
|
|
57
81
|
this.lastFormElement = select.closest("form");
|
|
58
82
|
}
|
|
59
83
|
async check(label) {
|
|
60
|
-
const checkbox = await this.
|
|
84
|
+
const checkbox = await this.findField(label);
|
|
61
85
|
if (!checkbox.checked) {
|
|
62
86
|
await this.user.click(checkbox);
|
|
63
87
|
}
|
|
64
88
|
this.lastFormElement = checkbox.closest("form");
|
|
65
89
|
}
|
|
66
90
|
async uncheck(label) {
|
|
67
|
-
const checkbox = await this.
|
|
91
|
+
const checkbox = await this.findField(label);
|
|
68
92
|
if (checkbox.checked) {
|
|
69
93
|
await this.user.click(checkbox);
|
|
70
94
|
}
|
|
71
95
|
this.lastFormElement = checkbox.closest("form");
|
|
72
96
|
}
|
|
73
97
|
async choose(label) {
|
|
74
|
-
const radio = await this.container.findByRole("radio", { name: label });
|
|
98
|
+
const radio = await this.container.findByRole("radio", { name: label }, this.waitOpts());
|
|
75
99
|
await this.user.click(radio);
|
|
76
100
|
this.lastFormElement = radio.closest("form");
|
|
77
101
|
}
|
|
@@ -93,7 +117,7 @@ export class RTLDriver {
|
|
|
93
117
|
}
|
|
94
118
|
}
|
|
95
119
|
async upload(label, path) {
|
|
96
|
-
const input = await this.
|
|
120
|
+
const input = await this.findField(label);
|
|
97
121
|
// JSDOM has no filesystem access; synthesize a File from the basename.
|
|
98
122
|
const name = path.split(/[\\/]/).pop() ?? path;
|
|
99
123
|
const file = new File([""], name);
|
|
@@ -122,7 +146,7 @@ export class RTLDriver {
|
|
|
122
146
|
throw new Error("refuteHas() with CSS selectors is not recommended in RTL. Use refuteText() instead.");
|
|
123
147
|
}
|
|
124
148
|
async assertText(text) {
|
|
125
|
-
await this.container.findByText(text);
|
|
149
|
+
await this.container.findByText(text, undefined, this.waitOpts());
|
|
126
150
|
}
|
|
127
151
|
async refuteText(text) {
|
|
128
152
|
await waitFor(() => {
|
|
@@ -130,44 +154,44 @@ export class RTLDriver {
|
|
|
130
154
|
if (el) {
|
|
131
155
|
throw new Error(`Expected NOT to find text '${text}', but it was present.`);
|
|
132
156
|
}
|
|
133
|
-
});
|
|
157
|
+
}, this.waitOpts());
|
|
134
158
|
}
|
|
135
159
|
async assertValue(label, value) {
|
|
136
|
-
const field = await this.
|
|
160
|
+
const field = await this.findField(label);
|
|
137
161
|
await waitFor(() => {
|
|
138
162
|
const actual = field.value;
|
|
139
163
|
if (actual !== value) {
|
|
140
164
|
throw new Error(`assertValue('${label}', '${value}'): expected value '${value}', but found '${actual}'.`);
|
|
141
165
|
}
|
|
142
|
-
});
|
|
166
|
+
}, this.waitOpts());
|
|
143
167
|
}
|
|
144
168
|
async assertChecked(label) {
|
|
145
|
-
const checkbox = await this.
|
|
169
|
+
const checkbox = await this.findField(label);
|
|
146
170
|
await waitFor(() => {
|
|
147
171
|
if (!checkbox.checked) {
|
|
148
172
|
throw new Error(`assertChecked('${label}'): expected checkbox to be checked, but it was not.`);
|
|
149
173
|
}
|
|
150
|
-
});
|
|
174
|
+
}, this.waitOpts());
|
|
151
175
|
}
|
|
152
176
|
async refuteChecked(label) {
|
|
153
|
-
const checkbox = await this.
|
|
177
|
+
const checkbox = await this.findField(label);
|
|
154
178
|
await waitFor(() => {
|
|
155
179
|
if (checkbox.checked) {
|
|
156
180
|
throw new Error(`refuteChecked('${label}'): expected checkbox NOT to be checked, but it was.`);
|
|
157
181
|
}
|
|
158
|
-
});
|
|
182
|
+
}, this.waitOpts());
|
|
159
183
|
}
|
|
160
184
|
async assertSelected(label, optionLabel) {
|
|
161
|
-
const select = (await this.
|
|
185
|
+
const select = (await this.findField(label));
|
|
162
186
|
await waitFor(() => {
|
|
163
187
|
const selected = select.selectedOptions[0]?.textContent?.trim();
|
|
164
188
|
if (selected !== optionLabel) {
|
|
165
189
|
throw new Error(`assertSelected('${label}', '${optionLabel}'): expected selected option '${optionLabel}', but found '${selected ?? "(none)"}'.`);
|
|
166
190
|
}
|
|
167
|
-
});
|
|
191
|
+
}, this.waitOpts());
|
|
168
192
|
}
|
|
169
193
|
async assertOptions(label, optionLabels) {
|
|
170
|
-
const select = (await this.
|
|
194
|
+
const select = (await this.findField(label));
|
|
171
195
|
await waitFor(() => {
|
|
172
196
|
const actual = Array.from(select.querySelectorAll("option")).map((o) => o.textContent?.trim() ?? "");
|
|
173
197
|
const matches = actual.length === optionLabels.length &&
|
|
@@ -175,7 +199,7 @@ export class RTLDriver {
|
|
|
175
199
|
if (!matches) {
|
|
176
200
|
throw new Error(`assertOptions('${label}'): expected options [${optionLabels.join(", ")}], but found [${actual.join(", ")}].`);
|
|
177
201
|
}
|
|
178
|
-
});
|
|
202
|
+
}, this.waitOpts());
|
|
179
203
|
}
|
|
180
204
|
async assertPath() {
|
|
181
205
|
throw new Error("assertPath() is not available in the RTL adapter (no real URL in JSDOM).");
|
|
@@ -190,9 +214,9 @@ export class RTLDriver {
|
|
|
190
214
|
const element = this.rootElement().querySelector(selector);
|
|
191
215
|
if (!element)
|
|
192
216
|
throw new Error(`within('${selector}'): element not found`);
|
|
193
|
-
return
|
|
217
|
+
return this.scoped(element);
|
|
194
218
|
}
|
|
195
219
|
async debug() {
|
|
196
|
-
screen.debug();
|
|
220
|
+
screen.debug(this.rootElement());
|
|
197
221
|
}
|
|
198
222
|
}
|
package/dist/session.d.ts
CHANGED
|
@@ -38,7 +38,12 @@ export declare class Session<TContext = unknown> implements PromiseLike<void> {
|
|
|
38
38
|
* StepError output like any built-in step.
|
|
39
39
|
*/
|
|
40
40
|
step(name: string, fn: (context: TContext) => Promise<unknown>): this;
|
|
41
|
-
|
|
41
|
+
/**
|
|
42
|
+
* `fn` must either return the scoped session — so its queued steps run — or
|
|
43
|
+
* a promise it already awaited. Returning anything else would silently drop
|
|
44
|
+
* the scoped chain, which is why the callback's return type is not `unknown`.
|
|
45
|
+
*/
|
|
46
|
+
within(selector: string, fn: (scoped: Session<TContext>) => Session<TContext> | PromiseLike<unknown>): this;
|
|
42
47
|
debug(): this;
|
|
43
48
|
}
|
|
44
49
|
//# sourceMappingURL=session.d.ts.map
|
package/dist/session.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EAEjB,UAAU,EACX,MAAM,YAAY,CAAC;AAGpB,qBAAa,OAAO,CAAC,QAAQ,GAAG,OAAO,CAAE,YAAW,WAAW,CAAC,IAAI,CAAC;IAKvD,OAAO,CAAC,MAAM;IAJ1B,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,SAAS,CAAK;gBAEF,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC;IAEhD,IAAI,CAAC,QAAQ,GAAG,IAAI,EAAE,QAAQ,GAAG,KAAK,EACpC,WAAW,CAAC,EACR,CAAC,CAAC,KAAK,EAAE,IAAI,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,GACnD,IAAI,EACR,UAAU,CAAC,EACP,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,GACvD,IAAI,GACP,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAIjB,YAAY;IAuB1B,OAAO,CAAC,OAAO;IAOf,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAMzB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIzB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAM7B,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAM/B,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAM1C,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAMjD,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI1B,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAM5B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAM3B,MAAM,IAAI,IAAI;IAId,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAMzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAQ9C,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAM9B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAM9B,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAM/C,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAMlC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAMlC,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAOxD,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,IAAI;IAO1D,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,IAAI;IAO1D,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,IAAI;IAO1D,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,iBAAiB,GAAG,IAAI;IAMxD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAQ9B;;;;;OAKG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI;IAMrE,MAAM,CACJ,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
package/dist/session.js
CHANGED
|
@@ -120,6 +120,11 @@ export class Session {
|
|
|
120
120
|
return this.enqueue(`step('${name}')`, () => this.driver.step(fn));
|
|
121
121
|
}
|
|
122
122
|
// --- Scoping ---
|
|
123
|
+
/**
|
|
124
|
+
* `fn` must either return the scoped session — so its queued steps run — or
|
|
125
|
+
* a promise it already awaited. Returning anything else would silently drop
|
|
126
|
+
* the scoped chain, which is why the callback's return type is not `unknown`.
|
|
127
|
+
*/
|
|
123
128
|
within(selector, fn) {
|
|
124
129
|
return this.enqueue(`within('${selector}')`, async () => {
|
|
125
130
|
const scopedDriver = await this.driver.within(selector);
|