feather-testing-core 0.3.0 → 0.4.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.
Files changed (43) hide show
  1. package/README.md +133 -4
  2. package/dist/errors.d.ts +8 -0
  3. package/dist/errors.d.ts.map +1 -1
  4. package/dist/errors.js +12 -0
  5. package/dist/eslint-plugin/index.d.ts +18 -0
  6. package/dist/eslint-plugin/index.d.ts.map +1 -0
  7. package/dist/eslint-plugin/index.js +37 -0
  8. package/dist/eslint-plugin/rules/no-conditional-skip.d.ts +4 -0
  9. package/dist/eslint-plugin/rules/no-conditional-skip.d.ts.map +1 -0
  10. package/dist/eslint-plugin/rules/no-conditional-skip.js +56 -0
  11. package/dist/eslint-plugin/rules/no-swallowed-cleanup-catch.d.ts +4 -0
  12. package/dist/eslint-plugin/rules/no-swallowed-cleanup-catch.d.ts.map +1 -0
  13. package/dist/eslint-plugin/rules/no-swallowed-cleanup-catch.js +63 -0
  14. package/dist/eslint-plugin/rules/no-wait-for-timeout.d.ts +4 -0
  15. package/dist/eslint-plugin/rules/no-wait-for-timeout.d.ts.map +1 -0
  16. package/dist/eslint-plugin/rules/no-wait-for-timeout.js +69 -0
  17. package/dist/eslint-plugin/rules/no-weak-assertions.d.ts +4 -0
  18. package/dist/eslint-plugin/rules/no-weak-assertions.d.ts.map +1 -0
  19. package/dist/eslint-plugin/rules/no-weak-assertions.js +73 -0
  20. package/dist/eslint-plugin/rules/warn-serial-mode.d.ts +4 -0
  21. package/dist/eslint-plugin/rules/warn-serial-mode.d.ts.map +1 -0
  22. package/dist/eslint-plugin/rules/warn-serial-mode.js +59 -0
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/playwright/driver.d.ts +16 -3
  27. package/dist/playwright/driver.d.ts.map +1 -1
  28. package/dist/playwright/driver.js +49 -2
  29. package/dist/playwright/index.d.ts +4 -4
  30. package/dist/playwright/index.d.ts.map +1 -1
  31. package/dist/playwright/index.js +1 -1
  32. package/dist/rtl/driver.d.ts +28 -4
  33. package/dist/rtl/driver.d.ts.map +1 -1
  34. package/dist/rtl/driver.js +78 -2
  35. package/dist/rtl/index.d.ts +5 -4
  36. package/dist/rtl/index.d.ts.map +1 -1
  37. package/dist/rtl/index.js +2 -2
  38. package/dist/session.d.ts +36 -4
  39. package/dist/session.d.ts.map +1 -1
  40. package/dist/session.js +67 -1
  41. package/dist/types.d.ts +35 -3
  42. package/dist/types.d.ts.map +1 -1
  43. package/package.json +10 -2
@@ -0,0 +1,59 @@
1
+ function memberPath(node) {
2
+ if (node.type === "Identifier")
3
+ return node.name;
4
+ if (node.type === "MemberExpression" &&
5
+ !node.computed &&
6
+ node.property.type === "Identifier") {
7
+ const prefix = memberPath(node.object);
8
+ return prefix === null ? null : `${prefix}.${node.property.name}`;
9
+ }
10
+ return null;
11
+ }
12
+ const rule = {
13
+ meta: {
14
+ type: "suggestion",
15
+ docs: {
16
+ description: "Flag serial mode, which makes each test depend on the ones before it",
17
+ recommended: true,
18
+ },
19
+ schema: [],
20
+ messages: {
21
+ serial: "Serial mode couples these tests: one failure cascades into the rest, " +
22
+ "so the report stops naming the real cause and a flake becomes a wall " +
23
+ "of red. Make them independent — share setup, not state — or keep " +
24
+ "serial with an eslint-disable-next-line comment saying why it is " +
25
+ "required.",
26
+ },
27
+ },
28
+ create(context) {
29
+ return {
30
+ CallExpression(node) {
31
+ const path = memberPath(node.callee);
32
+ if (path === null)
33
+ return;
34
+ const segments = path.split(".");
35
+ const isSerialRunner = segments.includes("serial") &&
36
+ ["test", "it", "describe", "suite"].includes(segments[0] ?? "");
37
+ if (isSerialRunner) {
38
+ context.report({ node, messageId: "serial" });
39
+ return;
40
+ }
41
+ // test.describe.configure({ mode: "serial" })
42
+ if (!path.endsWith(".configure"))
43
+ return;
44
+ const [options] = node.arguments;
45
+ if (!options || options.type !== "ObjectExpression")
46
+ return;
47
+ const declaresSerial = options.properties.some((property) => property.type === "Property" &&
48
+ !property.computed &&
49
+ property.key.type === "Identifier" &&
50
+ property.key.name === "mode" &&
51
+ property.value.type === "Literal" &&
52
+ property.value.value === "serial");
53
+ if (declaresSerial)
54
+ context.report({ node, messageId: "serial" });
55
+ },
56
+ };
57
+ },
58
+ };
59
+ export default rule;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { Session } from "./session.js";
2
- export { StepError } from "./errors.js";
3
- export type { AssertHasOptions, AssertPathOptions, QueuedStep, TestDriver, } from "./types.js";
2
+ export { StepError, BrowserOnlyVerbError } from "./errors.js";
3
+ export type { AssertHasOptions, AssertPathOptions, DownloadOptions, QueuedStep, TestDriver, UntilOptions, UntilPredicate, } from "./types.js";
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,EACV,UAAU,GACX,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC9D,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,UAAU,EACV,UAAU,EACV,YAAY,EACZ,cAAc,GACf,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  export { Session } from "./session.js";
2
- export { StepError } from "./errors.js";
2
+ export { StepError, BrowserOnlyVerbError } from "./errors.js";
@@ -1,12 +1,12 @@
1
1
  import { type Page, type Locator } from "@playwright/test";
2
- import type { AssertHasOptions, AssertPathOptions, TestDriver } from "../types.js";
2
+ import type { AssertHasOptions, AssertPathOptions, DownloadOptions, TestDriver, UntilOptions, UntilPredicate } from "../types.js";
3
3
  /** Context handed to custom step() callbacks in the Playwright adapter. */
4
4
  export interface PlaywrightStepContext {
5
5
  page: Page;
6
6
  /** Current scope: the page, or the container locator inside within(). */
7
7
  scope: Page | Locator;
8
8
  }
9
- export declare class PlaywrightDriver implements TestDriver<PlaywrightStepContext> {
9
+ export declare class PlaywrightDriver implements TestDriver<PlaywrightStepContext, Page> {
10
10
  private page;
11
11
  private scope;
12
12
  private lastFormLocator;
@@ -24,7 +24,11 @@ export declare class PlaywrightDriver implements TestDriver<PlaywrightStepContex
24
24
  uncheck(label: string): Promise<void>;
25
25
  choose(label: string): Promise<void>;
26
26
  submit(): Promise<void>;
27
+ attachFile(label: string, path: string): Promise<void>;
28
+ /** @deprecated Older name for {@link PlaywrightDriver.attachFile}. */
27
29
  upload(label: string, path: string): Promise<void>;
30
+ pressKey(key: string): Promise<void>;
31
+ hover(text: string): Promise<void>;
28
32
  dropFile(selector: string, path: string): Promise<void>;
29
33
  assertHas(selector: string, opts?: AssertHasOptions): Promise<void>;
30
34
  refuteHas(selector: string, opts?: AssertHasOptions): Promise<void>;
@@ -37,8 +41,17 @@ export declare class PlaywrightDriver implements TestDriver<PlaywrightStepContex
37
41
  assertOptions(label: string, optionLabels: string[]): Promise<void>;
38
42
  assertPath(path: string, opts?: AssertPathOptions): Promise<void>;
39
43
  refutePath(path: string): Promise<void>;
44
+ assertDownload(expected: string | RegExp, trigger: () => Promise<void>, opts?: DownloadOptions): Promise<void>;
45
+ until(description: string, predicate: UntilPredicate<PlaywrightStepContext>, opts?: UntilOptions): Promise<void>;
40
46
  step(fn: (context: PlaywrightStepContext) => Promise<unknown>): Promise<void>;
41
- within(selector: string): Promise<TestDriver<PlaywrightStepContext>>;
47
+ /**
48
+ * raw() hands over the page itself, not the within() scope: it exists for
49
+ * the cases the DSL does not model, where re-scoping is the caller's job.
50
+ */
51
+ raw(fn: (page: Page) => unknown | Promise<unknown>): Promise<void>;
52
+ /** The adapter context handed to step(), until(), and friends. */
53
+ protected context(): PlaywrightStepContext;
54
+ within(selector: string): Promise<TestDriver<PlaywrightStepContext, Page>>;
42
55
  debug(): Promise<void>;
43
56
  wrapStep(name: string, fn: () => Promise<void>): Promise<void>;
44
57
  }
@@ -1 +1 @@
1
- {"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/playwright/driver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/playwright/driver.ts"],"names":[],"mappings":"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"}
@@ -99,11 +99,21 @@ export class PlaywrightDriver {
99
99
  .press("Enter");
100
100
  }
101
101
  }
102
- async upload(label, path) {
102
+ async attachFile(label, path) {
103
103
  const input = this.labelled(label);
104
104
  await input.setInputFiles(path);
105
105
  this.lastFormLocator = this.scope.locator("form", { has: input });
106
106
  }
107
+ /** @deprecated Older name for {@link PlaywrightDriver.attachFile}. */
108
+ async upload(label, path) {
109
+ await this.attachFile(label, path);
110
+ }
111
+ async pressKey(key) {
112
+ await this.page.keyboard.press(key);
113
+ }
114
+ async hover(text) {
115
+ await this.scope.getByText(text, EXACT).hover();
116
+ }
107
117
  async dropFile(selector, path) {
108
118
  const content = await readFile(path);
109
119
  const name = basename(path);
@@ -183,8 +193,45 @@ export class PlaywrightDriver {
183
193
  })
184
194
  .not.toBe(path);
185
195
  }
196
+ async assertDownload(expected, trigger, opts) {
197
+ // The wait has to be armed before the click that starts the download,
198
+ // which is why the trigger arrives as a callback rather than as an
199
+ // earlier step in the chain.
200
+ const [download] = await Promise.all([
201
+ this.page.waitForEvent("download", { timeout: opts?.timeout }),
202
+ trigger(),
203
+ ]);
204
+ const filename = download.suggestedFilename();
205
+ const matched = typeof expected === "string"
206
+ ? filename === expected
207
+ : expected.test(filename);
208
+ if (!matched) {
209
+ throw new Error(`assertDownload(${typeof expected === "string" ? `'${expected}'` : String(expected)}): ` +
210
+ `the browser offered a download named '${filename}' instead.`);
211
+ }
212
+ }
213
+ async until(description, predicate, opts) {
214
+ await expect
215
+ .poll(async () => Boolean(await predicate(this.context())), {
216
+ message: `until: ${description}`,
217
+ timeout: opts?.timeout,
218
+ intervals: opts?.interval === undefined ? undefined : [opts.interval],
219
+ })
220
+ .toBe(true);
221
+ }
186
222
  async step(fn) {
187
- await fn({ page: this.page, scope: this.scope });
223
+ await fn(this.context());
224
+ }
225
+ /**
226
+ * raw() hands over the page itself, not the within() scope: it exists for
227
+ * the cases the DSL does not model, where re-scoping is the caller's job.
228
+ */
229
+ async raw(fn) {
230
+ await fn(this.page);
231
+ }
232
+ /** The adapter context handed to step(), until(), and friends. */
233
+ context() {
234
+ return { page: this.page, scope: this.scope };
188
235
  }
189
236
  async within(selector) {
190
237
  const scopedLocator = this.scope.locator(selector);
@@ -2,12 +2,12 @@ import { type Page } from "@playwright/test";
2
2
  import { Session } from "../session.js";
3
3
  import { type PlaywrightStepContext } from "./driver.js";
4
4
  export { Session } from "../session.js";
5
- export { StepError } from "../errors.js";
5
+ export { StepError, BrowserOnlyVerbError } from "../errors.js";
6
6
  export { PlaywrightDriver, type PlaywrightStepContext } from "./driver.js";
7
- export type { AssertHasOptions, AssertPathOptions, TestDriver, } from "../types.js";
8
- export declare function createSession(page: Page): Session<PlaywrightStepContext>;
7
+ export type { AssertHasOptions, AssertPathOptions, DownloadOptions, TestDriver, UntilOptions, UntilPredicate, } from "../types.js";
8
+ export declare function createSession(page: Page): Session<PlaywrightStepContext, Page>;
9
9
  export declare const test: import("playwright/test").TestType<import("playwright/test").PlaywrightTestArgs & import("playwright/test").PlaywrightTestOptions & {
10
- session: Session<PlaywrightStepContext>;
10
+ session: Session<PlaywrightStepContext, Page>;
11
11
  }, import("playwright/test").PlaywrightWorkerArgs & import("playwright/test").PlaywrightWorkerOptions>;
12
12
  export { expect } from "@playwright/test";
13
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/playwright/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAoB,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE3E,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAC3E,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAExE;AAED,eAAO,MAAM,IAAI;aAA0B,OAAO,CAAC,qBAAqB,CAAC;sGAIvE,CAAC;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/playwright/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAoB,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE3E,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAC3E,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,UAAU,EACV,YAAY,EACZ,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,wBAAgB,aAAa,CAC3B,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAEtC;AAED,eAAO,MAAM,IAAI;aACN,OAAO,CAAC,qBAAqB,EAAE,IAAI,CAAC;sGAK7C,CAAC;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC"}
@@ -2,7 +2,7 @@ import { test as base } from "@playwright/test";
2
2
  import { Session } from "../session.js";
3
3
  import { PlaywrightDriver } from "./driver.js";
4
4
  export { Session } from "../session.js";
5
- export { StepError } from "../errors.js";
5
+ export { StepError, BrowserOnlyVerbError } from "../errors.js";
6
6
  export { PlaywrightDriver } from "./driver.js";
7
7
  export function createSession(page) {
8
8
  return new Session(new PlaywrightDriver(page));
@@ -1,6 +1,8 @@
1
1
  import { screen, within as rtlWithin } from "@testing-library/react";
2
2
  import { type UserEvent } from "@testing-library/user-event";
3
- import type { AssertHasOptions, TestDriver } from "../types.js";
3
+ import type { AssertHasOptions, DownloadOptions, TestDriver, UntilOptions, UntilPredicate } from "../types.js";
4
+ /** The scoped query object raw() callbacks receive in the RTL adapter. */
5
+ export type RTLQueries = ReturnType<typeof rtlWithin>;
4
6
  /** Context handed to custom step() callbacks in the RTL adapter. */
5
7
  export interface RTLStepContext {
6
8
  user: UserEvent;
@@ -19,7 +21,7 @@ export interface RTLStepContext {
19
21
  * driver to markup whose labels are wrapper siblings rather than `htmlFor`
20
22
  * targets.
21
23
  */
22
- export declare class RTLDriver implements TestDriver<RTLStepContext> {
24
+ export declare class RTLDriver implements TestDriver<RTLStepContext, RTLQueries> {
23
25
  protected user: UserEvent;
24
26
  /** The element every query and selector in this driver resolves against. */
25
27
  protected root: HTMLElement;
@@ -39,7 +41,7 @@ export declare class RTLDriver implements TestDriver<RTLStepContext> {
39
41
  */
40
42
  protected findField(label: string): Promise<HTMLElement>;
41
43
  /** Subclasses override this so within() yields a driver of their own type. */
42
- protected scoped(element: HTMLElement): TestDriver<RTLStepContext>;
44
+ protected scoped(element: HTMLElement): TestDriver<RTLStepContext, RTLQueries>;
43
45
  visit(): Promise<void>;
44
46
  click(text: string): Promise<void>;
45
47
  clickLink(text: string): Promise<void>;
@@ -50,7 +52,16 @@ export declare class RTLDriver implements TestDriver<RTLStepContext> {
50
52
  uncheck(label: string): Promise<void>;
51
53
  choose(label: string): Promise<void>;
52
54
  submit(): Promise<void>;
55
+ attachFile(label: string, path: string): Promise<void>;
56
+ /** @deprecated Older name for {@link RTLDriver.attachFile}. */
53
57
  upload(label: string, path: string): Promise<void>;
58
+ /**
59
+ * Keys are named the Playwright way ('Enter', 'Control+A') and translated
60
+ * to user-event's keyboard syntax, so one spec reads the same on both
61
+ * adapters.
62
+ */
63
+ pressKey(key: string): Promise<void>;
64
+ hover(text: string): Promise<void>;
54
65
  dropFile(selector: string, path: string): Promise<void>;
55
66
  assertHas(_selector: string, _opts?: AssertHasOptions): Promise<void>;
56
67
  refuteHas(_selector: string, _opts?: AssertHasOptions): Promise<void>;
@@ -63,8 +74,21 @@ export declare class RTLDriver implements TestDriver<RTLStepContext> {
63
74
  assertOptions(label: string, optionLabels: string[]): Promise<void>;
64
75
  assertPath(): Promise<void>;
65
76
  refutePath(): Promise<void>;
77
+ assertDownload(_expected: string | RegExp, _trigger: () => Promise<void>, _opts?: DownloadOptions): Promise<void>;
78
+ until(description: string, predicate: UntilPredicate<RTLStepContext>, opts?: UntilOptions): Promise<void>;
66
79
  step(fn: (context: RTLStepContext) => Promise<unknown>): Promise<void>;
67
- within(selector: string): Promise<TestDriver<RTLStepContext>>;
80
+ /** raw() hands over the scoped query object — `within(root)`, i.e. screen
81
+ * when the driver is unscoped. */
82
+ raw(fn: (queries: RTLQueries) => unknown | Promise<unknown>): Promise<void>;
83
+ /** The adapter context handed to step(), until(), and friends. */
84
+ protected context(): RTLStepContext;
85
+ within(selector: string): Promise<TestDriver<RTLStepContext, RTLQueries>>;
68
86
  debug(): Promise<void>;
69
87
  }
88
+ /**
89
+ * 'Enter' -> '{Enter}', 'a' -> 'a', 'Control+A' -> '{Control>}A{/Control}'.
90
+ * Printable characters are typed literally with user-event's own `{` and `[`
91
+ * escapes applied, so a key name never turns into a descriptor by accident.
92
+ */
93
+ export declare function toKeyboardSyntax(key: string): string;
70
94
  //# sourceMappingURL=driver.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/rtl/driver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/rtl/driver.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { fireEvent, screen, waitFor, within as rtlWithin, } from "@testing-library/react";
2
2
  import userEvent from "@testing-library/user-event";
3
+ import { BrowserOnlyVerbError } from "../errors.js";
3
4
  /**
4
5
  * RTL adapter implementing the subset of TestDriver that applies in JSDOM.
5
6
  * Navigation methods (visit, assertPath, refutePath) are not supported.
@@ -116,7 +117,7 @@ export class RTLDriver {
116
117
  this.lastFormElement.requestSubmit();
117
118
  }
118
119
  }
119
- async upload(label, path) {
120
+ async attachFile(label, path) {
120
121
  const input = await this.findField(label);
121
122
  // JSDOM has no filesystem access; synthesize a File from the basename.
122
123
  const name = path.split(/[\\/]/).pop() ?? path;
@@ -124,6 +125,22 @@ export class RTLDriver {
124
125
  await this.user.upload(input, file);
125
126
  this.lastFormElement = input.closest("form");
126
127
  }
128
+ /** @deprecated Older name for {@link RTLDriver.attachFile}. */
129
+ async upload(label, path) {
130
+ await this.attachFile(label, path);
131
+ }
132
+ /**
133
+ * Keys are named the Playwright way ('Enter', 'Control+A') and translated
134
+ * to user-event's keyboard syntax, so one spec reads the same on both
135
+ * adapters.
136
+ */
137
+ async pressKey(key) {
138
+ await this.user.keyboard(toKeyboardSyntax(key));
139
+ }
140
+ async hover(text) {
141
+ const element = await this.container.findByText(text, undefined, this.waitOpts());
142
+ await this.user.hover(element);
143
+ }
127
144
  async dropFile(selector, path) {
128
145
  const target = this.rootElement().querySelector(selector);
129
146
  if (!target) {
@@ -207,8 +224,32 @@ export class RTLDriver {
207
224
  async refutePath() {
208
225
  throw new Error("refutePath() is not available in the RTL adapter (no real URL in JSDOM).");
209
226
  }
227
+ async assertDownload(_expected, _trigger, _opts) {
228
+ throw new BrowserOnlyVerbError("assertDownload()", "Assert the request the download would make, or move this case to a " +
229
+ "Playwright spec where a real browser can accept the file.");
230
+ }
231
+ async until(description, predicate, opts) {
232
+ await waitFor(async () => {
233
+ if (!(await predicate(this.context()))) {
234
+ throw new Error(`until: ${description} — condition was still not met.`);
235
+ }
236
+ }, {
237
+ ...this.waitOpts(),
238
+ ...(opts?.timeout === undefined ? {} : { timeout: opts.timeout }),
239
+ ...(opts?.interval === undefined ? {} : { interval: opts.interval }),
240
+ });
241
+ }
210
242
  async step(fn) {
211
- await fn({ user: this.user, container: this.container });
243
+ await fn(this.context());
244
+ }
245
+ /** raw() hands over the scoped query object — `within(root)`, i.e. screen
246
+ * when the driver is unscoped. */
247
+ async raw(fn) {
248
+ await fn(this.container);
249
+ }
250
+ /** The adapter context handed to step(), until(), and friends. */
251
+ context() {
252
+ return { user: this.user, container: this.container };
212
253
  }
213
254
  async within(selector) {
214
255
  const element = this.rootElement().querySelector(selector);
@@ -220,3 +261,38 @@ export class RTLDriver {
220
261
  screen.debug(this.rootElement());
221
262
  }
222
263
  }
264
+ /** Playwright key names -> user-event keyboard syntax. */
265
+ const KEY_ALIASES = {
266
+ Ctrl: "Control",
267
+ Cmd: "Meta",
268
+ Command: "Meta",
269
+ Space: " ",
270
+ };
271
+ const MODIFIERS = new Set(["Control", "Shift", "Alt", "Meta"]);
272
+ /**
273
+ * 'Enter' -> '{Enter}', 'a' -> 'a', 'Control+A' -> '{Control>}A{/Control}'.
274
+ * Printable characters are typed literally with user-event's own `{` and `[`
275
+ * escapes applied, so a key name never turns into a descriptor by accident.
276
+ */
277
+ export function toKeyboardSyntax(key) {
278
+ const parts = key.split("+").map((p) => KEY_ALIASES[p] ?? p);
279
+ const target = parts.pop();
280
+ if (target === undefined || target === "") {
281
+ throw new Error(`pressKey('${key}'): no key to press.`);
282
+ }
283
+ const held = parts.filter((p) => MODIFIERS.has(p));
284
+ if (held.length !== parts.length) {
285
+ throw new Error(`pressKey('${key}'): '${parts.find((p) => !MODIFIERS.has(p))}' is not a ` +
286
+ "modifier. Use Control, Shift, Alt, or Meta.");
287
+ }
288
+ const pressed = target.length === 1
289
+ ? target.replace(/[{[]/g, (c) => c + c)
290
+ : `{${target}}`;
291
+ return (held.map((m) => `{${m}>}`).join("") +
292
+ pressed +
293
+ held
294
+ .slice()
295
+ .reverse()
296
+ .map((m) => `{/${m}}`)
297
+ .join(""));
298
+ }
@@ -1,7 +1,8 @@
1
1
  import { Session } from "../session.js";
2
- import { type RTLStepContext } from "./driver.js";
2
+ import { type RTLQueries, type RTLStepContext } from "./driver.js";
3
3
  export { Session } from "../session.js";
4
- export { StepError } from "../errors.js";
5
- export { RTLDriver, type RTLStepContext } from "./driver.js";
6
- export declare function createSession(): Session<RTLStepContext>;
4
+ export { StepError, BrowserOnlyVerbError } from "../errors.js";
5
+ export { RTLDriver, type RTLQueries, type RTLStepContext, } from "./driver.js";
6
+ export type { AssertHasOptions, DownloadOptions, TestDriver, UntilOptions, UntilPredicate, } from "../types.js";
7
+ export declare function createSession(): Session<RTLStepContext, RTLQueries>;
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rtl/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7D,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7D,wBAAgB,aAAa,IAAI,OAAO,CAAC,cAAc,CAAC,CAEvD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rtl/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAa,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAE9E,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EACL,SAAS,EACT,KAAK,UAAU,EACf,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,UAAU,EACV,YAAY,EACZ,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,wBAAgB,aAAa,IAAI,OAAO,CAAC,cAAc,EAAE,UAAU,CAAC,CAEnE"}
package/dist/rtl/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Session } from "../session.js";
2
2
  import { RTLDriver } from "./driver.js";
3
3
  export { Session } from "../session.js";
4
- export { StepError } from "../errors.js";
5
- export { RTLDriver } from "./driver.js";
4
+ export { StepError, BrowserOnlyVerbError } from "../errors.js";
5
+ export { RTLDriver, } from "./driver.js";
6
6
  export function createSession() {
7
7
  return new Session(new RTLDriver());
8
8
  }
package/dist/session.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import type { AssertHasOptions, AssertPathOptions, TestDriver } from "./types.js";
2
- export declare class Session<TContext = unknown> implements PromiseLike<void> {
1
+ import type { AssertHasOptions, AssertPathOptions, DownloadOptions, TestDriver, UntilOptions, UntilPredicate } from "./types.js";
2
+ export declare class Session<TContext = unknown, TNative = unknown> implements PromiseLike<void> {
3
3
  private driver;
4
4
  private steps;
5
5
  private executedSteps;
6
6
  private stepIndex;
7
- constructor(driver: TestDriver<TContext>);
7
+ constructor(driver: TestDriver<TContext, TNative>);
8
8
  then<TResult1 = void, TResult2 = never>(onfulfilled?: ((value: void) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
9
9
  private executeSteps;
10
10
  private enqueue;
@@ -18,8 +18,15 @@ export declare class Session<TContext = unknown> implements PromiseLike<void> {
18
18
  uncheck(label: string): this;
19
19
  choose(label: string): this;
20
20
  submit(): this;
21
+ /** Set a file input, found by its label, to the file at `path`. */
22
+ attachFile(label: string, path: string): this;
23
+ /** @deprecated Older name for {@link Session.attachFile}. */
21
24
  upload(label: string, path: string): this;
22
25
  dropFile(selector: string, path: string): this;
26
+ /** Press a key on the focused element, e.g. 'Enter' or 'Control+A'. */
27
+ pressKey(key: string): this;
28
+ /** Hover the element with this text. */
29
+ hover(text: string): this;
23
30
  assertText(text: string): this;
24
31
  refuteText(text: string): this;
25
32
  assertValue(label: string, value: string): this;
@@ -31,6 +38,24 @@ export declare class Session<TContext = unknown> implements PromiseLike<void> {
31
38
  refuteHas(selector: string, opts?: AssertHasOptions): this;
32
39
  assertPath(path: string, opts?: AssertPathOptions): this;
33
40
  refutePath(path: string): this;
41
+ /**
42
+ * Assert that running `trigger` makes the browser offer a download whose
43
+ * suggested filename matches `expected`. The trigger is a callback because
44
+ * the wait has to be armed before the click that starts the download.
45
+ *
46
+ * Browser-only: the RTL adapter throws a BrowserOnlyVerbError.
47
+ */
48
+ assertDownload(expected: string | RegExp, trigger: (scoped: Session<TContext, TNative>) => Session<TContext, TNative> | PromiseLike<unknown>, opts?: DownloadOptions): this;
49
+ /**
50
+ * Wait for a condition instead of sleeping. `description` is mandatory: it
51
+ * is what the chain trace prints, so a timeout reads
52
+ * `[FAILED] until: the export finishes` rather than naming a mechanism.
53
+ *
54
+ * The predicate receives the adapter's context ({ page, scope } for
55
+ * Playwright, { user, container } for RTL) and may be sync or async; it
56
+ * is polled until it returns something truthy or the budget is spent.
57
+ */
58
+ until(description: string, predicate: UntilPredicate<TContext>, opts?: UntilOptions): this;
34
59
  /**
35
60
  * Queue a named custom step. `fn` receives the adapter's context
36
61
  * ({ page, scope } for Playwright, { user, container } for RTL), so a
@@ -38,12 +63,19 @@ export declare class Session<TContext = unknown> implements PromiseLike<void> {
38
63
  * StepError output like any built-in step.
39
64
  */
40
65
  step(name: string, fn: (context: TContext) => Promise<unknown>): this;
66
+ /**
67
+ * Drop to the driver itself — Playwright's `page`, RTL's scoped queries —
68
+ * without leaving the chain. `label` is mandatory and registers as a named
69
+ * step, so an escape hatch still names intent in the trace instead of
70
+ * ending it: `[FAILED] raw('drag the card to Done')`.
71
+ */
72
+ raw(label: string, fn: (native: TNative) => unknown | Promise<unknown>): this;
41
73
  /**
42
74
  * `fn` must either return the scoped session — so its queued steps run — or
43
75
  * a promise it already awaited. Returning anything else would silently drop
44
76
  * the scoped chain, which is why the callback's return type is not `unknown`.
45
77
  */
46
- within(selector: string, fn: (scoped: Session<TContext>) => Session<TContext> | PromiseLike<unknown>): this;
78
+ within(selector: string, fn: (scoped: Session<TContext, TNative>) => Session<TContext, TNative> | PromiseLike<unknown>): this;
47
79
  debug(): this;
48
80
  }
49
81
  //# sourceMappingURL=session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}