@atomic-testing/playwright 0.85.0 → 0.88.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/dist/index.cjs CHANGED
@@ -23,6 +23,100 @@ var PlaywrightInteractor = class PlaywrightInteractor {
23
23
  await this.page.locator(cssLocator).selectOption(values);
24
24
  }
25
25
  /**
26
+ * Set the selected files on a `<input type="file">` element.
27
+ *
28
+ * Playwright's native `setInputFiles` reads the given paths from disk and
29
+ * populates the input's `FileList`, firing the change event — the only way to
30
+ * fill a file input, whose value cannot be set programmatically. Following
31
+ * this layer's convention, no `ElementNotFoundError` is fabricated: a missing
32
+ * element surfaces through Playwright's own auto-wait timeout.
33
+ *
34
+ * @param locator - Locator of the `<input type="file">` element
35
+ * @param files - One or more filesystem paths to upload
36
+ */
37
+ async setInputFiles(locator, files) {
38
+ const cssLocator = await _atomic_testing_core.locatorUtil.toCssSelector(locator, this);
39
+ await this.page.locator(cssLocator).setInputFiles(files);
40
+ }
41
+ /**
42
+ * Scroll the located element into view, no-op if already visible.
43
+ *
44
+ * Delegates to Playwright's `scrollIntoViewIfNeeded`, which performs a real
45
+ * layout-aware scroll in the browser. Per this layer's convention, no
46
+ * `ElementNotFoundError` is fabricated: a missing element surfaces through
47
+ * Playwright's own auto-wait timeout.
48
+ *
49
+ * @param locator - Locator of the element to scroll into view
50
+ */
51
+ async scrollIntoView(locator) {
52
+ const css = await _atomic_testing_core.locatorUtil.toCssSelector(locator, this);
53
+ await this.page.locator(css).scrollIntoViewIfNeeded();
54
+ }
55
+ /**
56
+ * Scroll the located element by the given pixel delta.
57
+ *
58
+ * The scroll is performed by evaluating `el.scrollBy(dx, dy)` on the element
59
+ * itself rather than `page.mouse.wheel`. A wheel event scrolls whatever sits
60
+ * under the pointer and is non-deterministic across chromium/firefox/webkit,
61
+ * whereas evaluating `scrollBy` on the resolved element scrolls exactly that
62
+ * element. This is a deliberate deviation from ADR 0001's per-engine table
63
+ * (which lists `page.mouse.wheel`), taking the alternative the step-5 prompt
64
+ * permits ("or evaluate el.scrollBy") for cross-browser determinism. As with
65
+ * {@link scrollIntoView}, no `ElementNotFoundError` is fabricated; a missing
66
+ * element surfaces through Playwright's own auto-wait timeout.
67
+ *
68
+ * @param locator - Locator of the scrollable element
69
+ * @param delta - Pixel offset to scroll by
70
+ */
71
+ async scrollBy(locator, delta) {
72
+ const css = await _atomic_testing_core.locatorUtil.toCssSelector(locator, this);
73
+ await this.page.locator(css).evaluate((el, d) => el.scrollBy(d.x, d.y), {
74
+ x: delta.x,
75
+ y: delta.y
76
+ });
77
+ }
78
+ /**
79
+ * Drag the source element and drop it onto the target element.
80
+ *
81
+ * Delegates to Playwright's native `Locator.dragTo`, which performs a real,
82
+ * layout-aware drag gesture in the browser. Per this layer's convention, no
83
+ * `ElementNotFoundError` is fabricated: a missing element surfaces through
84
+ * Playwright's own auto-wait timeout.
85
+ *
86
+ * @param source - Locator of the element to drag
87
+ * @param target - Locator of the drop target
88
+ */
89
+ async dragTo(source, target) {
90
+ const srcCss = await _atomic_testing_core.locatorUtil.toCssSelector(source, this);
91
+ const tgtCss = await _atomic_testing_core.locatorUtil.toCssSelector(target, this);
92
+ await this.page.locator(srcCss).dragTo(this.page.locator(tgtCss));
93
+ }
94
+ /**
95
+ * Drag the located element by the given pixel delta from its center.
96
+ *
97
+ * The gesture is a single uninterrupted `move → down → move → up` sequence
98
+ * computed from the element's center. It deliberately does NOT reuse
99
+ * {@link mouseMove}/{@link mouseDown} — `mouseMove` resets the pointer with
100
+ * `page.mouse.move(0, 0)` after hovering, which would corrupt the drag path
101
+ * (see ADR 0001, option 5). The center comes from {@link getBoundingRect},
102
+ * which throws `ElementNotFoundError` when the element has no box
103
+ * (detached/invisible) rather than auto-waiting — so this shares that
104
+ * "element not found" contract instead of re-deriving the box + guard here.
105
+ *
106
+ * @param locator - Locator of the element to drag
107
+ * @param delta - Pixel offset to drag by
108
+ * @throws {ElementNotFoundError} If the element has no bounding box
109
+ */
110
+ async drag(locator, delta) {
111
+ const rect = await this.getBoundingRect(locator);
112
+ const cx = rect.x + rect.width / 2;
113
+ const cy = rect.y + rect.height / 2;
114
+ await this.page.mouse.move(cx, cy);
115
+ await this.page.mouse.down();
116
+ await this.page.mouse.move(cx + delta.x, cy + delta.y, { steps: 8 });
117
+ await this.page.mouse.up();
118
+ }
119
+ /**
26
120
  * Get the value of an `<input>` element.
27
121
  *
28
122
  * @param locator - Locator pointing to the input element.
@@ -82,6 +176,20 @@ var PlaywrightInteractor = class PlaywrightInteractor {
82
176
  const cssLocator = await _atomic_testing_core.locatorUtil.toCssSelector(locator, this);
83
177
  await this.page.locator(cssLocator).click({ position: option?.position });
84
178
  }
179
+ /**
180
+ * Dispatch a right-click on the located element to open its context menu.
181
+ *
182
+ * Delegates to Playwright's native right-button click, which fires a real
183
+ * `contextmenu` event in the browser. Per this layer's convention, no
184
+ * `ElementNotFoundError` is fabricated: a missing element surfaces through
185
+ * Playwright's own auto-wait timeout.
186
+ *
187
+ * @param locator - Locator of the element to right-click
188
+ */
189
+ async contextMenu(locator) {
190
+ const css = await _atomic_testing_core.locatorUtil.toCssSelector(locator, this);
191
+ await this.page.locator(css).click({ button: "right" });
192
+ }
85
193
  async hover(locator, option) {
86
194
  const cssLocator = await _atomic_testing_core.locatorUtil.toCssSelector(locator, this);
87
195
  await this.page.locator(cssLocator).hover({ position: option?.position });
@@ -122,6 +230,20 @@ var PlaywrightInteractor = class PlaywrightInteractor {
122
230
  const cssLocator = await _atomic_testing_core.locatorUtil.toCssSelector(locator, this);
123
231
  await this.page.locator(cssLocator).blur();
124
232
  }
233
+ async pressKey(locator, key, option) {
234
+ const cssLocator = await _atomic_testing_core.locatorUtil.toCssSelector(locator, this);
235
+ const modifiers = [];
236
+ if (option?.ctrl) modifiers.push("Control");
237
+ if (option?.alt) modifiers.push("Alt");
238
+ if (option?.shift) modifiers.push("Shift");
239
+ if (option?.meta) modifiers.push("Meta");
240
+ const chord = modifiers.length > 0 ? `${modifiers.join("+")}+${key}` : key;
241
+ await this.page.locator(cssLocator).press(chord);
242
+ }
243
+ async activate(locator) {
244
+ const cssLocator = await _atomic_testing_core.locatorUtil.toCssSelector(locator, this);
245
+ await this.page.locator(cssLocator).dispatchEvent("click");
246
+ }
125
247
  wait(ms) {
126
248
  return _atomic_testing_core.timingUtil.wait(ms);
127
249
  }
@@ -149,6 +271,29 @@ var PlaywrightInteractor = class PlaywrightInteractor {
149
271
  const cssLocator = await _atomic_testing_core.locatorUtil.toCssSelector(locator, this);
150
272
  return await this.page.locator(cssLocator).textContent() ?? void 0;
151
273
  }
274
+ /**
275
+ * Get the located element's bounding rectangle.
276
+ *
277
+ * `boundingBox()` returns `null` for a detached/invisible element rather than
278
+ * auto-waiting, so this is one of the few Playwright methods that throws
279
+ * `ElementNotFoundError` — matching the house "element not found" contract
280
+ * (ADR 0001).
281
+ *
282
+ * @param locator - Locator of the element to measure
283
+ * @returns The element's bounding rectangle in CSS pixels
284
+ * @throws {ElementNotFoundError} If the element has no bounding box
285
+ */
286
+ async getBoundingRect(locator) {
287
+ const css = await _atomic_testing_core.locatorUtil.toCssSelector(locator, this);
288
+ const box = await this.page.locator(css).boundingBox();
289
+ if (box == null) throw new _atomic_testing_core.ElementNotFoundError(locator, "getBoundingRect");
290
+ return {
291
+ x: box.x,
292
+ y: box.y,
293
+ width: box.width,
294
+ height: box.height
295
+ };
296
+ }
152
297
  async exists(locator) {
153
298
  const cssLocator = await _atomic_testing_core.locatorUtil.toCssSelector(locator, this);
154
299
  return await this.page.locator(cssLocator).count() > 0;
@@ -238,7 +383,8 @@ const playWrightTestFrameworkMapper = {
238
383
  beforeAll: _playwright_test.test.beforeAll,
239
384
  afterAll: _playwright_test.test.afterAll,
240
385
  test: _playwright_test.test,
241
- it: _playwright_test.test
386
+ it: _playwright_test.test,
387
+ hasLayout: true
242
388
  };
243
389
  /**
244
390
  * Get a typed interface for running end-to-end tests with Playwright.
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["locatorUtil","dateUtil","timingUtil","defaultWaitForOption","interactorUtil","TestEngine","test"],"sources":["../src/PlaywrightInteractor.ts","../src/createTestEngine.ts","../src/testRunnerAdapter.ts"],"sourcesContent":["import {\n BlurOption,\n byCssSelector,\n ClickOption,\n CssProperty,\n dateUtil,\n defaultWaitForOption,\n EnterTextOption,\n FocusOption,\n HoverOption,\n Interactor,\n interactorUtil,\n locatorUtil,\n MouseEnterOption,\n MouseLeaveOption,\n MouseOutOption,\n MouseDownOption,\n MouseMoveOption,\n MouseUpOption,\n Optional,\n PartLocator,\n timingUtil,\n WaitForOption,\n WaitUntilOption,\n} from '@atomic-testing/core';\nimport { Page } from '@playwright/test';\n\n/**\n * Implementation of the {@link Interactor} interface using Playwright.\n */\nexport class PlaywrightInteractor implements Interactor {\n /**\n * @param page - Playwright page instance used to drive the browser.\n */\n constructor(public readonly page: Page) {}\n\n /**\n * Select the given option values on a `<select>` element.\n *\n * @param locator - Locator to the `<select>` element.\n * @param values - Values to select.\n */\n async selectOptionValue(locator: PartLocator, values: string[]): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).selectOption(values);\n }\n\n /**\n * Get the value of an `<input>` element.\n *\n * @param locator - Locator pointing to the input element.\n * @returns The current value of the input or `undefined` if not present.\n */\n async getInputValue(locator: PartLocator): Promise<Optional<string>> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n return this.page.locator(cssLocator).inputValue();\n }\n\n /**\n * Retrieve the values of selected options within a `<select>` element.\n *\n * @param locator - Locator to the `<select>` element.\n * @returns Array of selected option values or `undefined` when no option is selected.\n */\n async getSelectValues(locator: PartLocator): Promise<Optional<readonly string[]>> {\n const optionLocator: PartLocator = byCssSelector('option:checked');\n const selectedOptionLocator = locatorUtil.append(locator, optionLocator);\n const cssLocator = await locatorUtil.toCssSelector(selectedOptionLocator, this);\n const allOptions = await this.page.locator(cssLocator).all();\n const values: string[] = [];\n for (const option of allOptions) {\n const value = await option.getAttribute('value');\n if (value != null) {\n values.push(value);\n }\n }\n return values;\n }\n\n async getSelectLabels(locator: PartLocator): Promise<Optional<readonly string[]>> {\n const optionLocator: PartLocator = byCssSelector('option:checked');\n const selectedOptionLocator = locatorUtil.append(locator, optionLocator);\n const cssLocator = await locatorUtil.toCssSelector(selectedOptionLocator, this);\n const allOptions = await this.page.locator(cssLocator).all();\n const labels: string[] = [];\n for (const option of allOptions) {\n const label = await option.textContent();\n if (label != null) {\n labels.push(label);\n }\n }\n return labels;\n }\n\n async getStyleValue(locator: PartLocator, propertyName: CssProperty): Promise<Optional<string>> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const elLocator = this.page.locator(cssLocator);\n const value = await elLocator.evaluate((element, prop) => {\n return window.getComputedStyle(element).getPropertyValue(prop as string);\n }, propertyName);\n return value;\n }\n\n async enterText(locator: PartLocator, text: string, option?: Optional<Partial<EnterTextOption>>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n if (!option?.append) {\n await this.page.locator(cssLocator).clear();\n }\n\n // If it is a date, time or datetime-local input, validate the date format\n const type = (await this.getAttribute(locator, 'type')) ?? '';\n if (dateUtil.isHtmlDateInputType(type)) {\n const result = dateUtil.validateHtmlDateInput(type, text);\n if (!result.valid) {\n throw new Error(\n `Invalid date format for type: ${type}, expected format: ${result.format}, example: ${result.example}`\n );\n }\n }\n await this.page.locator(cssLocator).fill(text);\n }\n\n async click(locator: PartLocator, option?: Partial<ClickOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).click({ position: option?.position });\n }\n\n async hover(locator: PartLocator, option?: Partial<HoverOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).hover({ position: option?.position });\n }\n\n async mouseMove(locator: PartLocator, option?: Partial<MouseMoveOption>): Promise<void> {\n await this.hover(locator, {\n position: option?.position,\n });\n await this.page.mouse.move(0, 0);\n }\n\n async mouseDown(locator: PartLocator, option?: Partial<MouseDownOption>): Promise<void> {\n await this.hover(locator, {\n position: option?.position,\n });\n await this.page.mouse.down();\n }\n\n async mouseUp(locator: PartLocator, option?: Partial<MouseUpOption>): Promise<void> {\n await this.hover(locator, {\n position: option?.position,\n });\n await this.page.mouse.up();\n }\n\n async mouseOver(locator: PartLocator, option?: Partial<HoverOption>): Promise<void> {\n return this.hover(locator, option);\n }\n\n async mouseOut(locator: PartLocator, _option?: Partial<MouseOutOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n // First hover over the element to trigger mouseenter/mouseover\n await this.page.locator(cssLocator).hover();\n // Then dispatch mouseout event directly for cross-browser reliability\n await this.page.locator(cssLocator).dispatchEvent('mouseout');\n }\n\n async mouseEnter(locator: PartLocator, _option?: Partial<MouseEnterOption>): Promise<void> {\n return this.hover(locator);\n }\n\n async mouseLeave(locator: PartLocator, _option?: Partial<MouseLeaveOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n // First hover over the element to trigger mouseenter/mouseover\n await this.page.locator(cssLocator).hover();\n // Dispatch mouseout which triggers both mouseout and mouseleave handlers in React\n await this.page.locator(cssLocator).dispatchEvent('mouseout');\n }\n\n async focus(locator: PartLocator, _option?: Partial<FocusOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n return this.page.focus(cssLocator);\n }\n\n async blur(locator: PartLocator, _option?: Partial<BlurOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).blur();\n }\n\n //#region wait conditions\n wait(ms: number): Promise<void> {\n return timingUtil.wait(ms);\n }\n\n async waitUntilComponentState(\n locator: PartLocator,\n option: Partial<Readonly<WaitForOption>> = defaultWaitForOption\n ): Promise<void> {\n return interactorUtil.interactorWaitUtil(locator, this, option);\n }\n\n waitUntil<T>(option: WaitUntilOption<T>): Promise<T> {\n return timingUtil.waitUntil(option);\n }\n //#endregion\n\n async getAttribute(locator: PartLocator, name: string, isMultiple: true): Promise<readonly string[]>;\n async getAttribute(locator: PartLocator, name: string, isMultiple: false): Promise<Optional<string>>;\n async getAttribute(locator: PartLocator, name: string): Promise<Optional<string>>;\n async getAttribute(\n locator: PartLocator,\n name: string,\n isMultiple?: boolean\n ): Promise<Optional<string> | readonly string[]> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const elLocator = this.page.locator(cssLocator);\n if (isMultiple) {\n const locators = await elLocator.all();\n const values: string[] = [];\n for (const locator of locators) {\n const value = await locator.getAttribute(name);\n if (value != null) {\n values.push(value);\n }\n }\n return values;\n }\n const value = await elLocator.getAttribute(name);\n return value ?? undefined;\n }\n\n async getText(locator: PartLocator): Promise<Optional<string>> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const text = await this.page.locator(cssLocator).textContent();\n return text ?? undefined;\n }\n\n async exists(locator: PartLocator): Promise<boolean> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const count = await this.page.locator(cssLocator).count();\n return count > 0;\n }\n\n async isChecked(locator: PartLocator): Promise<boolean> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const checked = await this.page.locator(cssLocator).isChecked();\n return checked;\n }\n\n async isDisabled(locator: PartLocator): Promise<boolean> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const isDisabled = await this.page.locator(cssLocator).isDisabled();\n return isDisabled;\n }\n\n async isReadonly(locator: PartLocator): Promise<boolean> {\n const readonly = await this.getAttribute(locator, 'readonly');\n return readonly != null;\n }\n\n async isVisible(locator: PartLocator): Promise<boolean> {\n const exists = await this.exists(locator);\n if (!exists) {\n return false;\n }\n\n async function checkCssVisibility(\n prop: CssProperty,\n invisibleValue: string,\n interactor: PlaywrightInteractor\n ): Promise<boolean> {\n try {\n const value = await interactor.getStyleValue(locator, prop);\n return value !== invisibleValue;\n } catch (e) {\n // Element may disappear or detached while being checked because of animation\n // when it happens, an error is thrown. In this case, if indeed the element\n // is not visible, we return false. Otherwise, we re-throw the error.\n if ((await interactor.exists(locator)) === false) {\n return false;\n }\n throw e;\n }\n }\n\n if ((await checkCssVisibility('opacity', '0', this)) === false) {\n return false;\n }\n\n if ((await checkCssVisibility('visibility', 'hidden', this)) === false) {\n return false;\n }\n\n if ((await checkCssVisibility('display', 'none', this)) === false) {\n return false;\n }\n\n return true;\n }\n\n async hasCssClass(locator: PartLocator, className: string): Promise<boolean> {\n const classNames = await this.getAttribute(locator, 'class');\n if (classNames == null) {\n return false;\n }\n\n const names = classNames.split(/\\s+/);\n return names.includes(className);\n }\n\n async hasAttribute(locator: PartLocator, name: string): Promise<boolean> {\n const attrValue = await this.getAttribute(locator, name);\n return attrValue != null;\n }\n\n //#region\n async innerHTML(locator: PartLocator): Promise<string> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n return this.page.locator(cssLocator).innerHTML();\n }\n //#endregion\n\n clone(): Interactor {\n return new PlaywrightInteractor(this.page);\n }\n}\n","import { ScenePart, TestEngine } from '@atomic-testing/core';\nimport { Page } from '@playwright/test';\n\nimport { PlaywrightInteractor } from './PlaywrightInteractor';\n\n/**\n * Create a {@link TestEngine} instance backed by Playwright.\n *\n * @param page - Playwright page used for interaction.\n * @param partDefinitions - Scene part definitions describing the scene\n * structure for the engine.\n * @returns A configured {@link TestEngine} ready for use.\n */\nexport function createTestEngine<T extends ScenePart>(page: Page, partDefinitions: T): TestEngine<T> {\n const engine = new TestEngine([], new PlaywrightInteractor(page), {\n parts: partDefinitions,\n });\n\n return engine;\n}\n","import { ScenePart, TestEngine } from '@atomic-testing/core';\nimport {\n E2eTestInterface,\n E2eTestRunEnvironmentFixture,\n TestFrameworkMapper,\n} from '@atomic-testing/internal-test-runner';\nimport { expect, Page, test } from '@playwright/test';\n\nimport { createTestEngine } from './createTestEngine';\n\n/**\n * Navigate the current Playwright page to the provided URL.\n *\n * @param url - Destination URL to load.\n * @param fixture - Optional test fixture supplying the Playwright page.\n */\nexport async function goto(url: string): Promise<void>;\nexport async function goto(url: string, fixture: E2eTestRunEnvironmentFixture): Promise<void>;\nexport async function goto(url: string, fixture?: E2eTestRunEnvironmentFixture): Promise<void> {\n const page = fixture!.page as Page;\n await page.goto(url);\n}\n\n/**\n * Create a {@link TestEngine} bound to the Playwright page in the given fixture.\n *\n * @param scenePart - Scene definition to drive.\n * @param fixture - Fixture providing the Playwright page.\n */\nexport function playwrightGetTestEngine<T extends ScenePart>(\n scenePart: T,\n fixture: E2eTestRunEnvironmentFixture\n): TestEngine<T> {\n const page = fixture.page as Page;\n return createTestEngine(page, scenePart);\n}\n\n/**\n * Playwright adapter for the TestFrameworkMapper interface.\n */\nexport const playWrightTestFrameworkMapper: TestFrameworkMapper = {\n /*\n * INTENTIONAL @ts-expect-error comments: Playwright's test functions have different type\n * signatures than the normalized TestFrameworkMapper interface. Playwright uses fixture-based\n * callbacks with destructuring ({ page, browser }) while our interface uses a union type for\n * Jest compatibility (done callback or fixture object). The functions are compatible at runtime\n * but TypeScript cannot verify this due to these fundamental signature differences.\n */\n\n assertEqual: (a, b) => expect(a).toEqual(b),\n assertNotEqual: (a, b) => expect(a).not.toEqual(b),\n assertTrue: value => expect(value).toBe(true),\n assertFalse: value => expect(value).toBe(false),\n assertApproxEqual: (actual, expected, tolerance) =>\n expect(Math.abs(actual - expected)).toBeLessThanOrEqual(tolerance),\n // @ts-expect-error - Playwright describe signature differs from TestFrameworkMapper.Describe\n describe: test.describe,\n\n beforeEach: test.beforeEach,\n afterEach: test.afterEach,\n beforeAll: test.beforeAll,\n afterAll: test.afterAll,\n\n // @ts-expect-error - Playwright test signature differs from TestFrameworkMapper.Test\n test: test,\n\n // @ts-expect-error - Playwright test signature differs from TestFrameworkMapper.Test\n it: test,\n};\n\n/**\n * Get a typed interface for running end-to-end tests with Playwright.\n */\nexport function getTestRunnerInterface<T extends ScenePart>(): E2eTestInterface<T> {\n return {\n getTestEngine: playwrightGetTestEngine,\n goto,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["locatorUtil","dateUtil","timingUtil","defaultWaitForOption","interactorUtil","ElementNotFoundError","TestEngine","test"],"sources":["../src/PlaywrightInteractor.ts","../src/createTestEngine.ts","../src/testRunnerAdapter.ts"],"sourcesContent":["import {\n BlurOption,\n BoundingRect,\n byCssSelector,\n ClickOption,\n CssProperty,\n dateUtil,\n defaultWaitForOption,\n ElementNotFoundError,\n EnterTextOption,\n FocusOption,\n HoverOption,\n Interactor,\n interactorUtil,\n locatorUtil,\n MouseEnterOption,\n MouseLeaveOption,\n MouseOutOption,\n MouseDownOption,\n MouseMoveOption,\n MouseUpOption,\n Optional,\n PartLocator,\n Point,\n PressKeyOption,\n timingUtil,\n WaitForOption,\n WaitUntilOption,\n} from '@atomic-testing/core';\nimport { Page } from '@playwright/test';\n\n/**\n * Implementation of the {@link Interactor} interface using Playwright.\n */\nexport class PlaywrightInteractor implements Interactor {\n /**\n * @param page - Playwright page instance used to drive the browser.\n */\n constructor(public readonly page: Page) {}\n\n /**\n * Select the given option values on a `<select>` element.\n *\n * @param locator - Locator to the `<select>` element.\n * @param values - Values to select.\n */\n async selectOptionValue(locator: PartLocator, values: string[]): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).selectOption(values);\n }\n\n /**\n * Set the selected files on a `<input type=\"file\">` element.\n *\n * Playwright's native `setInputFiles` reads the given paths from disk and\n * populates the input's `FileList`, firing the change event — the only way to\n * fill a file input, whose value cannot be set programmatically. Following\n * this layer's convention, no `ElementNotFoundError` is fabricated: a missing\n * element surfaces through Playwright's own auto-wait timeout.\n *\n * @param locator - Locator of the `<input type=\"file\">` element\n * @param files - One or more filesystem paths to upload\n */\n async setInputFiles(locator: PartLocator, files: string | string[]): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).setInputFiles(files);\n }\n\n /**\n * Scroll the located element into view, no-op if already visible.\n *\n * Delegates to Playwright's `scrollIntoViewIfNeeded`, which performs a real\n * layout-aware scroll in the browser. Per this layer's convention, no\n * `ElementNotFoundError` is fabricated: a missing element surfaces through\n * Playwright's own auto-wait timeout.\n *\n * @param locator - Locator of the element to scroll into view\n */\n async scrollIntoView(locator: PartLocator): Promise<void> {\n const css = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(css).scrollIntoViewIfNeeded();\n }\n\n /**\n * Scroll the located element by the given pixel delta.\n *\n * The scroll is performed by evaluating `el.scrollBy(dx, dy)` on the element\n * itself rather than `page.mouse.wheel`. A wheel event scrolls whatever sits\n * under the pointer and is non-deterministic across chromium/firefox/webkit,\n * whereas evaluating `scrollBy` on the resolved element scrolls exactly that\n * element. This is a deliberate deviation from ADR 0001's per-engine table\n * (which lists `page.mouse.wheel`), taking the alternative the step-5 prompt\n * permits (\"or evaluate el.scrollBy\") for cross-browser determinism. As with\n * {@link scrollIntoView}, no `ElementNotFoundError` is fabricated; a missing\n * element surfaces through Playwright's own auto-wait timeout.\n *\n * @param locator - Locator of the scrollable element\n * @param delta - Pixel offset to scroll by\n */\n async scrollBy(locator: PartLocator, delta: Point): Promise<void> {\n const css = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(css).evaluate((el, d) => el.scrollBy(d.x, d.y), { x: delta.x, y: delta.y });\n }\n\n /**\n * Drag the source element and drop it onto the target element.\n *\n * Delegates to Playwright's native `Locator.dragTo`, which performs a real,\n * layout-aware drag gesture in the browser. Per this layer's convention, no\n * `ElementNotFoundError` is fabricated: a missing element surfaces through\n * Playwright's own auto-wait timeout.\n *\n * @param source - Locator of the element to drag\n * @param target - Locator of the drop target\n */\n async dragTo(source: PartLocator, target: PartLocator): Promise<void> {\n const srcCss = await locatorUtil.toCssSelector(source, this);\n const tgtCss = await locatorUtil.toCssSelector(target, this);\n await this.page.locator(srcCss).dragTo(this.page.locator(tgtCss));\n }\n\n /**\n * Drag the located element by the given pixel delta from its center.\n *\n * The gesture is a single uninterrupted `move → down → move → up` sequence\n * computed from the element's center. It deliberately does NOT reuse\n * {@link mouseMove}/{@link mouseDown} — `mouseMove` resets the pointer with\n * `page.mouse.move(0, 0)` after hovering, which would corrupt the drag path\n * (see ADR 0001, option 5). The center comes from {@link getBoundingRect},\n * which throws `ElementNotFoundError` when the element has no box\n * (detached/invisible) rather than auto-waiting — so this shares that\n * \"element not found\" contract instead of re-deriving the box + guard here.\n *\n * @param locator - Locator of the element to drag\n * @param delta - Pixel offset to drag by\n * @throws {ElementNotFoundError} If the element has no bounding box\n */\n async drag(locator: PartLocator, delta: Point): Promise<void> {\n const rect = await this.getBoundingRect(locator);\n const cx = rect.x + rect.width / 2;\n const cy = rect.y + rect.height / 2;\n await this.page.mouse.move(cx, cy);\n await this.page.mouse.down();\n await this.page.mouse.move(cx + delta.x, cy + delta.y, { steps: 8 });\n await this.page.mouse.up();\n }\n\n /**\n * Get the value of an `<input>` element.\n *\n * @param locator - Locator pointing to the input element.\n * @returns The current value of the input or `undefined` if not present.\n */\n async getInputValue(locator: PartLocator): Promise<Optional<string>> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n return this.page.locator(cssLocator).inputValue();\n }\n\n /**\n * Retrieve the values of selected options within a `<select>` element.\n *\n * @param locator - Locator to the `<select>` element.\n * @returns Array of selected option values or `undefined` when no option is selected.\n */\n async getSelectValues(locator: PartLocator): Promise<Optional<readonly string[]>> {\n const optionLocator: PartLocator = byCssSelector('option:checked');\n const selectedOptionLocator = locatorUtil.append(locator, optionLocator);\n const cssLocator = await locatorUtil.toCssSelector(selectedOptionLocator, this);\n const allOptions = await this.page.locator(cssLocator).all();\n const values: string[] = [];\n for (const option of allOptions) {\n const value = await option.getAttribute('value');\n if (value != null) {\n values.push(value);\n }\n }\n return values;\n }\n\n async getSelectLabels(locator: PartLocator): Promise<Optional<readonly string[]>> {\n const optionLocator: PartLocator = byCssSelector('option:checked');\n const selectedOptionLocator = locatorUtil.append(locator, optionLocator);\n const cssLocator = await locatorUtil.toCssSelector(selectedOptionLocator, this);\n const allOptions = await this.page.locator(cssLocator).all();\n const labels: string[] = [];\n for (const option of allOptions) {\n const label = await option.textContent();\n if (label != null) {\n labels.push(label);\n }\n }\n return labels;\n }\n\n async getStyleValue(locator: PartLocator, propertyName: CssProperty): Promise<Optional<string>> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const elLocator = this.page.locator(cssLocator);\n const value = await elLocator.evaluate((element, prop) => {\n return window.getComputedStyle(element).getPropertyValue(prop as string);\n }, propertyName);\n return value;\n }\n\n async enterText(locator: PartLocator, text: string, option?: Optional<Partial<EnterTextOption>>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n if (!option?.append) {\n await this.page.locator(cssLocator).clear();\n }\n\n // If it is a date, time or datetime-local input, validate the date format\n const type = (await this.getAttribute(locator, 'type')) ?? '';\n if (dateUtil.isHtmlDateInputType(type)) {\n const result = dateUtil.validateHtmlDateInput(type, text);\n if (!result.valid) {\n throw new Error(\n `Invalid date format for type: ${type}, expected format: ${result.format}, example: ${result.example}`\n );\n }\n }\n await this.page.locator(cssLocator).fill(text);\n }\n\n async click(locator: PartLocator, option?: Partial<ClickOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).click({ position: option?.position });\n }\n\n /**\n * Dispatch a right-click on the located element to open its context menu.\n *\n * Delegates to Playwright's native right-button click, which fires a real\n * `contextmenu` event in the browser. Per this layer's convention, no\n * `ElementNotFoundError` is fabricated: a missing element surfaces through\n * Playwright's own auto-wait timeout.\n *\n * @param locator - Locator of the element to right-click\n */\n async contextMenu(locator: PartLocator): Promise<void> {\n const css = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(css).click({ button: 'right' });\n }\n\n async hover(locator: PartLocator, option?: Partial<HoverOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).hover({ position: option?.position });\n }\n\n async mouseMove(locator: PartLocator, option?: Partial<MouseMoveOption>): Promise<void> {\n await this.hover(locator, {\n position: option?.position,\n });\n await this.page.mouse.move(0, 0);\n }\n\n async mouseDown(locator: PartLocator, option?: Partial<MouseDownOption>): Promise<void> {\n await this.hover(locator, {\n position: option?.position,\n });\n await this.page.mouse.down();\n }\n\n async mouseUp(locator: PartLocator, option?: Partial<MouseUpOption>): Promise<void> {\n await this.hover(locator, {\n position: option?.position,\n });\n await this.page.mouse.up();\n }\n\n async mouseOver(locator: PartLocator, option?: Partial<HoverOption>): Promise<void> {\n return this.hover(locator, option);\n }\n\n async mouseOut(locator: PartLocator, _option?: Partial<MouseOutOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n // First hover over the element to trigger mouseenter/mouseover\n await this.page.locator(cssLocator).hover();\n // Then dispatch mouseout event directly for cross-browser reliability\n await this.page.locator(cssLocator).dispatchEvent('mouseout');\n }\n\n async mouseEnter(locator: PartLocator, _option?: Partial<MouseEnterOption>): Promise<void> {\n return this.hover(locator);\n }\n\n async mouseLeave(locator: PartLocator, _option?: Partial<MouseLeaveOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n // First hover over the element to trigger mouseenter/mouseover\n await this.page.locator(cssLocator).hover();\n // Dispatch mouseout which triggers both mouseout and mouseleave handlers in React\n await this.page.locator(cssLocator).dispatchEvent('mouseout');\n }\n\n async focus(locator: PartLocator, _option?: Partial<FocusOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n return this.page.focus(cssLocator);\n }\n\n async blur(locator: PartLocator, _option?: Partial<BlurOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).blur();\n }\n\n async pressKey(locator: PartLocator, key: string, option?: Partial<PressKeyOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n // Compose Playwright's chord syntax — modifiers joined to the key by `+`, in\n // Playwright's accepted Control+Alt+Shift+Meta order — so the browser holds\n // those modifiers across the keypress and the event carries ctrlKey/etc.\n const modifiers: string[] = [];\n if (option?.ctrl) {\n modifiers.push('Control');\n }\n if (option?.alt) {\n modifiers.push('Alt');\n }\n if (option?.shift) {\n modifiers.push('Shift');\n }\n if (option?.meta) {\n modifiers.push('Meta');\n }\n const chord = modifiers.length > 0 ? `${modifiers.join('+')}+${key}` : key;\n // locator.press auto-focuses the element, then dispatches a real, trusted\n // KeyboardEvent — the browser equivalent of the DOM focus-first keyDown/keyUp.\n // Caveat: for Shift + a printable key the browser case-folds `event.key`\n // (`Shift+a` → `'A'`) whereas the jsdom path keeps `'a'` — only the modifier\n // flags are delivered identically across engines (see #924).\n await this.page.locator(cssLocator).press(chord);\n }\n\n async activate(locator: PartLocator): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n // Geometry-free activation mirrors the mouseout dispatch precedent above: it\n // bypasses hit-testing to actuate a covered or zero-size input that\n // locator.click() (a real geometry hit-test) cannot reach.\n await this.page.locator(cssLocator).dispatchEvent('click');\n }\n\n //#region wait conditions\n wait(ms: number): Promise<void> {\n return timingUtil.wait(ms);\n }\n\n async waitUntilComponentState(\n locator: PartLocator,\n option: Partial<Readonly<WaitForOption>> = defaultWaitForOption\n ): Promise<void> {\n return interactorUtil.interactorWaitUtil(locator, this, option);\n }\n\n waitUntil<T>(option: WaitUntilOption<T>): Promise<T> {\n return timingUtil.waitUntil(option);\n }\n //#endregion\n\n async getAttribute(locator: PartLocator, name: string, isMultiple: true): Promise<readonly string[]>;\n async getAttribute(locator: PartLocator, name: string, isMultiple: false): Promise<Optional<string>>;\n async getAttribute(locator: PartLocator, name: string): Promise<Optional<string>>;\n async getAttribute(\n locator: PartLocator,\n name: string,\n isMultiple?: boolean\n ): Promise<Optional<string> | readonly string[]> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const elLocator = this.page.locator(cssLocator);\n if (isMultiple) {\n const locators = await elLocator.all();\n const values: string[] = [];\n for (const locator of locators) {\n const value = await locator.getAttribute(name);\n if (value != null) {\n values.push(value);\n }\n }\n return values;\n }\n const value = await elLocator.getAttribute(name);\n return value ?? undefined;\n }\n\n async getText(locator: PartLocator): Promise<Optional<string>> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const text = await this.page.locator(cssLocator).textContent();\n return text ?? undefined;\n }\n\n /**\n * Get the located element's bounding rectangle.\n *\n * `boundingBox()` returns `null` for a detached/invisible element rather than\n * auto-waiting, so this is one of the few Playwright methods that throws\n * `ElementNotFoundError` — matching the house \"element not found\" contract\n * (ADR 0001).\n *\n * @param locator - Locator of the element to measure\n * @returns The element's bounding rectangle in CSS pixels\n * @throws {ElementNotFoundError} If the element has no bounding box\n */\n async getBoundingRect(locator: PartLocator): Promise<BoundingRect> {\n const css = await locatorUtil.toCssSelector(locator, this);\n const box = await this.page.locator(css).boundingBox();\n if (box == null) {\n throw new ElementNotFoundError(locator, 'getBoundingRect');\n }\n return { x: box.x, y: box.y, width: box.width, height: box.height };\n }\n\n async exists(locator: PartLocator): Promise<boolean> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const count = await this.page.locator(cssLocator).count();\n return count > 0;\n }\n\n async isChecked(locator: PartLocator): Promise<boolean> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const checked = await this.page.locator(cssLocator).isChecked();\n return checked;\n }\n\n async isDisabled(locator: PartLocator): Promise<boolean> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const isDisabled = await this.page.locator(cssLocator).isDisabled();\n return isDisabled;\n }\n\n async isReadonly(locator: PartLocator): Promise<boolean> {\n const readonly = await this.getAttribute(locator, 'readonly');\n return readonly != null;\n }\n\n async isVisible(locator: PartLocator): Promise<boolean> {\n const exists = await this.exists(locator);\n if (!exists) {\n return false;\n }\n\n async function checkCssVisibility(\n prop: CssProperty,\n invisibleValue: string,\n interactor: PlaywrightInteractor\n ): Promise<boolean> {\n try {\n const value = await interactor.getStyleValue(locator, prop);\n return value !== invisibleValue;\n } catch (e) {\n // Element may disappear or detached while being checked because of animation\n // when it happens, an error is thrown. In this case, if indeed the element\n // is not visible, we return false. Otherwise, we re-throw the error.\n if ((await interactor.exists(locator)) === false) {\n return false;\n }\n throw e;\n }\n }\n\n if ((await checkCssVisibility('opacity', '0', this)) === false) {\n return false;\n }\n\n if ((await checkCssVisibility('visibility', 'hidden', this)) === false) {\n return false;\n }\n\n if ((await checkCssVisibility('display', 'none', this)) === false) {\n return false;\n }\n\n return true;\n }\n\n async hasCssClass(locator: PartLocator, className: string): Promise<boolean> {\n const classNames = await this.getAttribute(locator, 'class');\n if (classNames == null) {\n return false;\n }\n\n const names = classNames.split(/\\s+/);\n return names.includes(className);\n }\n\n async hasAttribute(locator: PartLocator, name: string): Promise<boolean> {\n const attrValue = await this.getAttribute(locator, name);\n return attrValue != null;\n }\n\n //#region\n async innerHTML(locator: PartLocator): Promise<string> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n return this.page.locator(cssLocator).innerHTML();\n }\n //#endregion\n\n clone(): Interactor {\n return new PlaywrightInteractor(this.page);\n }\n}\n","import { ScenePart, TestEngine } from '@atomic-testing/core';\nimport { Page } from '@playwright/test';\n\nimport { PlaywrightInteractor } from './PlaywrightInteractor';\n\n/**\n * Create a {@link TestEngine} instance backed by Playwright.\n *\n * @param page - Playwright page used for interaction.\n * @param partDefinitions - Scene part definitions describing the scene\n * structure for the engine.\n * @returns A configured {@link TestEngine} ready for use.\n */\nexport function createTestEngine<T extends ScenePart>(page: Page, partDefinitions: T): TestEngine<T> {\n const engine = new TestEngine([], new PlaywrightInteractor(page), {\n parts: partDefinitions,\n });\n\n return engine;\n}\n","import { ScenePart, TestEngine } from '@atomic-testing/core';\nimport {\n E2eTestInterface,\n E2eTestRunEnvironmentFixture,\n TestFrameworkMapper,\n} from '@atomic-testing/internal-test-runner';\nimport { expect, Page, test } from '@playwright/test';\n\nimport { createTestEngine } from './createTestEngine';\n\n/**\n * Navigate the current Playwright page to the provided URL.\n *\n * @param url - Destination URL to load.\n * @param fixture - Optional test fixture supplying the Playwright page.\n */\nexport async function goto(url: string): Promise<void>;\nexport async function goto(url: string, fixture: E2eTestRunEnvironmentFixture): Promise<void>;\nexport async function goto(url: string, fixture?: E2eTestRunEnvironmentFixture): Promise<void> {\n const page = fixture!.page as Page;\n await page.goto(url);\n}\n\n/**\n * Create a {@link TestEngine} bound to the Playwright page in the given fixture.\n *\n * @param scenePart - Scene definition to drive.\n * @param fixture - Fixture providing the Playwright page.\n */\nexport function playwrightGetTestEngine<T extends ScenePart>(\n scenePart: T,\n fixture: E2eTestRunEnvironmentFixture\n): TestEngine<T> {\n const page = fixture.page as Page;\n return createTestEngine(page, scenePart);\n}\n\n/**\n * Playwright adapter for the TestFrameworkMapper interface.\n */\nexport const playWrightTestFrameworkMapper: TestFrameworkMapper = {\n /*\n * INTENTIONAL @ts-expect-error comments: Playwright's test functions have different type\n * signatures than the normalized TestFrameworkMapper interface. Playwright uses fixture-based\n * callbacks with destructuring ({ page, browser }) while our interface uses a union type for\n * Jest compatibility (done callback or fixture object). The functions are compatible at runtime\n * but TypeScript cannot verify this due to these fundamental signature differences.\n */\n\n assertEqual: (a, b) => expect(a).toEqual(b),\n assertNotEqual: (a, b) => expect(a).not.toEqual(b),\n assertTrue: value => expect(value).toBe(true),\n assertFalse: value => expect(value).toBe(false),\n assertApproxEqual: (actual, expected, tolerance) =>\n expect(Math.abs(actual - expected)).toBeLessThanOrEqual(tolerance),\n // @ts-expect-error - Playwright describe signature differs from TestFrameworkMapper.Describe\n describe: test.describe,\n\n beforeEach: test.beforeEach,\n afterEach: test.afterEach,\n beforeAll: test.beforeAll,\n afterAll: test.afterAll,\n\n // @ts-expect-error - Playwright test signature differs from TestFrameworkMapper.Test\n test: test,\n\n // @ts-expect-error - Playwright test signature differs from TestFrameworkMapper.Test\n it: test,\n\n hasLayout: true,\n};\n\n/**\n * Get a typed interface for running end-to-end tests with Playwright.\n */\nexport function getTestRunnerInterface<T extends ScenePart>(): E2eTestInterface<T> {\n return {\n getTestEngine: playwrightGetTestEngine,\n goto,\n };\n}\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { BlurOption, ClickOption, CssProperty, EnterTextOption, FocusOption, HoverOption, Interactor, MouseDownOption, MouseEnterOption, MouseLeaveOption, MouseMoveOption, MouseOutOption, MouseUpOption, Optional, PartLocator, ScenePart, TestEngine, WaitForOption, WaitUntilOption } from "@atomic-testing/core";
1
+ import { BlurOption, BoundingRect, ClickOption, CssProperty, EnterTextOption, FocusOption, HoverOption, Interactor, MouseDownOption, MouseEnterOption, MouseLeaveOption, MouseMoveOption, MouseOutOption, MouseUpOption, Optional, PartLocator, Point, PressKeyOption, ScenePart, TestEngine, WaitForOption, WaitUntilOption } from "@atomic-testing/core";
2
2
  import { Page } from "@playwright/test";
3
3
  import { E2eTestInterface, E2eTestRunEnvironmentFixture, TestFrameworkMapper } from "@atomic-testing/internal-test-runner";
4
4
 
@@ -30,6 +30,76 @@ declare class PlaywrightInteractor implements Interactor {
30
30
  * @param values - Values to select.
31
31
  */
32
32
  selectOptionValue(locator: PartLocator, values: string[]): Promise<void>;
33
+ /**
34
+ * Set the selected files on a `<input type="file">` element.
35
+ *
36
+ * Playwright's native `setInputFiles` reads the given paths from disk and
37
+ * populates the input's `FileList`, firing the change event — the only way to
38
+ * fill a file input, whose value cannot be set programmatically. Following
39
+ * this layer's convention, no `ElementNotFoundError` is fabricated: a missing
40
+ * element surfaces through Playwright's own auto-wait timeout.
41
+ *
42
+ * @param locator - Locator of the `<input type="file">` element
43
+ * @param files - One or more filesystem paths to upload
44
+ */
45
+ setInputFiles(locator: PartLocator, files: string | string[]): Promise<void>;
46
+ /**
47
+ * Scroll the located element into view, no-op if already visible.
48
+ *
49
+ * Delegates to Playwright's `scrollIntoViewIfNeeded`, which performs a real
50
+ * layout-aware scroll in the browser. Per this layer's convention, no
51
+ * `ElementNotFoundError` is fabricated: a missing element surfaces through
52
+ * Playwright's own auto-wait timeout.
53
+ *
54
+ * @param locator - Locator of the element to scroll into view
55
+ */
56
+ scrollIntoView(locator: PartLocator): Promise<void>;
57
+ /**
58
+ * Scroll the located element by the given pixel delta.
59
+ *
60
+ * The scroll is performed by evaluating `el.scrollBy(dx, dy)` on the element
61
+ * itself rather than `page.mouse.wheel`. A wheel event scrolls whatever sits
62
+ * under the pointer and is non-deterministic across chromium/firefox/webkit,
63
+ * whereas evaluating `scrollBy` on the resolved element scrolls exactly that
64
+ * element. This is a deliberate deviation from ADR 0001's per-engine table
65
+ * (which lists `page.mouse.wheel`), taking the alternative the step-5 prompt
66
+ * permits ("or evaluate el.scrollBy") for cross-browser determinism. As with
67
+ * {@link scrollIntoView}, no `ElementNotFoundError` is fabricated; a missing
68
+ * element surfaces through Playwright's own auto-wait timeout.
69
+ *
70
+ * @param locator - Locator of the scrollable element
71
+ * @param delta - Pixel offset to scroll by
72
+ */
73
+ scrollBy(locator: PartLocator, delta: Point): Promise<void>;
74
+ /**
75
+ * Drag the source element and drop it onto the target element.
76
+ *
77
+ * Delegates to Playwright's native `Locator.dragTo`, which performs a real,
78
+ * layout-aware drag gesture in the browser. Per this layer's convention, no
79
+ * `ElementNotFoundError` is fabricated: a missing element surfaces through
80
+ * Playwright's own auto-wait timeout.
81
+ *
82
+ * @param source - Locator of the element to drag
83
+ * @param target - Locator of the drop target
84
+ */
85
+ dragTo(source: PartLocator, target: PartLocator): Promise<void>;
86
+ /**
87
+ * Drag the located element by the given pixel delta from its center.
88
+ *
89
+ * The gesture is a single uninterrupted `move → down → move → up` sequence
90
+ * computed from the element's center. It deliberately does NOT reuse
91
+ * {@link mouseMove}/{@link mouseDown} — `mouseMove` resets the pointer with
92
+ * `page.mouse.move(0, 0)` after hovering, which would corrupt the drag path
93
+ * (see ADR 0001, option 5). The center comes from {@link getBoundingRect},
94
+ * which throws `ElementNotFoundError` when the element has no box
95
+ * (detached/invisible) rather than auto-waiting — so this shares that
96
+ * "element not found" contract instead of re-deriving the box + guard here.
97
+ *
98
+ * @param locator - Locator of the element to drag
99
+ * @param delta - Pixel offset to drag by
100
+ * @throws {ElementNotFoundError} If the element has no bounding box
101
+ */
102
+ drag(locator: PartLocator, delta: Point): Promise<void>;
33
103
  /**
34
104
  * Get the value of an `<input>` element.
35
105
  *
@@ -48,6 +118,17 @@ declare class PlaywrightInteractor implements Interactor {
48
118
  getStyleValue(locator: PartLocator, propertyName: CssProperty): Promise<Optional<string>>;
49
119
  enterText(locator: PartLocator, text: string, option?: Optional<Partial<EnterTextOption>>): Promise<void>;
50
120
  click(locator: PartLocator, option?: Partial<ClickOption>): Promise<void>;
121
+ /**
122
+ * Dispatch a right-click on the located element to open its context menu.
123
+ *
124
+ * Delegates to Playwright's native right-button click, which fires a real
125
+ * `contextmenu` event in the browser. Per this layer's convention, no
126
+ * `ElementNotFoundError` is fabricated: a missing element surfaces through
127
+ * Playwright's own auto-wait timeout.
128
+ *
129
+ * @param locator - Locator of the element to right-click
130
+ */
131
+ contextMenu(locator: PartLocator): Promise<void>;
51
132
  hover(locator: PartLocator, option?: Partial<HoverOption>): Promise<void>;
52
133
  mouseMove(locator: PartLocator, option?: Partial<MouseMoveOption>): Promise<void>;
53
134
  mouseDown(locator: PartLocator, option?: Partial<MouseDownOption>): Promise<void>;
@@ -58,6 +139,8 @@ declare class PlaywrightInteractor implements Interactor {
58
139
  mouseLeave(locator: PartLocator, _option?: Partial<MouseLeaveOption>): Promise<void>;
59
140
  focus(locator: PartLocator, _option?: Partial<FocusOption>): Promise<void>;
60
141
  blur(locator: PartLocator, _option?: Partial<BlurOption>): Promise<void>;
142
+ pressKey(locator: PartLocator, key: string, option?: Partial<PressKeyOption>): Promise<void>;
143
+ activate(locator: PartLocator): Promise<void>;
61
144
  wait(ms: number): Promise<void>;
62
145
  waitUntilComponentState(locator: PartLocator, option?: Partial<Readonly<WaitForOption>>): Promise<void>;
63
146
  waitUntil<T>(option: WaitUntilOption<T>): Promise<T>;
@@ -65,6 +148,19 @@ declare class PlaywrightInteractor implements Interactor {
65
148
  getAttribute(locator: PartLocator, name: string, isMultiple: false): Promise<Optional<string>>;
66
149
  getAttribute(locator: PartLocator, name: string): Promise<Optional<string>>;
67
150
  getText(locator: PartLocator): Promise<Optional<string>>;
151
+ /**
152
+ * Get the located element's bounding rectangle.
153
+ *
154
+ * `boundingBox()` returns `null` for a detached/invisible element rather than
155
+ * auto-waiting, so this is one of the few Playwright methods that throws
156
+ * `ElementNotFoundError` — matching the house "element not found" contract
157
+ * (ADR 0001).
158
+ *
159
+ * @param locator - Locator of the element to measure
160
+ * @returns The element's bounding rectangle in CSS pixels
161
+ * @throws {ElementNotFoundError} If the element has no bounding box
162
+ */
163
+ getBoundingRect(locator: PartLocator): Promise<BoundingRect>;
68
164
  exists(locator: PartLocator): Promise<boolean>;
69
165
  isChecked(locator: PartLocator): Promise<boolean>;
70
166
  isDisabled(locator: PartLocator): Promise<boolean>;
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { BlurOption, ClickOption, CssProperty, EnterTextOption, FocusOption, HoverOption, Interactor, MouseDownOption, MouseEnterOption, MouseLeaveOption, MouseMoveOption, MouseOutOption, MouseUpOption, Optional, PartLocator, ScenePart, TestEngine, WaitForOption, WaitUntilOption } from "@atomic-testing/core";
1
+ import { BlurOption, BoundingRect, ClickOption, CssProperty, EnterTextOption, FocusOption, HoverOption, Interactor, MouseDownOption, MouseEnterOption, MouseLeaveOption, MouseMoveOption, MouseOutOption, MouseUpOption, Optional, PartLocator, Point, PressKeyOption, ScenePart, TestEngine, WaitForOption, WaitUntilOption } from "@atomic-testing/core";
2
2
  import { Page } from "@playwright/test";
3
3
  import { E2eTestInterface, E2eTestRunEnvironmentFixture, TestFrameworkMapper } from "@atomic-testing/internal-test-runner";
4
4
 
@@ -30,6 +30,76 @@ declare class PlaywrightInteractor implements Interactor {
30
30
  * @param values - Values to select.
31
31
  */
32
32
  selectOptionValue(locator: PartLocator, values: string[]): Promise<void>;
33
+ /**
34
+ * Set the selected files on a `<input type="file">` element.
35
+ *
36
+ * Playwright's native `setInputFiles` reads the given paths from disk and
37
+ * populates the input's `FileList`, firing the change event — the only way to
38
+ * fill a file input, whose value cannot be set programmatically. Following
39
+ * this layer's convention, no `ElementNotFoundError` is fabricated: a missing
40
+ * element surfaces through Playwright's own auto-wait timeout.
41
+ *
42
+ * @param locator - Locator of the `<input type="file">` element
43
+ * @param files - One or more filesystem paths to upload
44
+ */
45
+ setInputFiles(locator: PartLocator, files: string | string[]): Promise<void>;
46
+ /**
47
+ * Scroll the located element into view, no-op if already visible.
48
+ *
49
+ * Delegates to Playwright's `scrollIntoViewIfNeeded`, which performs a real
50
+ * layout-aware scroll in the browser. Per this layer's convention, no
51
+ * `ElementNotFoundError` is fabricated: a missing element surfaces through
52
+ * Playwright's own auto-wait timeout.
53
+ *
54
+ * @param locator - Locator of the element to scroll into view
55
+ */
56
+ scrollIntoView(locator: PartLocator): Promise<void>;
57
+ /**
58
+ * Scroll the located element by the given pixel delta.
59
+ *
60
+ * The scroll is performed by evaluating `el.scrollBy(dx, dy)` on the element
61
+ * itself rather than `page.mouse.wheel`. A wheel event scrolls whatever sits
62
+ * under the pointer and is non-deterministic across chromium/firefox/webkit,
63
+ * whereas evaluating `scrollBy` on the resolved element scrolls exactly that
64
+ * element. This is a deliberate deviation from ADR 0001's per-engine table
65
+ * (which lists `page.mouse.wheel`), taking the alternative the step-5 prompt
66
+ * permits ("or evaluate el.scrollBy") for cross-browser determinism. As with
67
+ * {@link scrollIntoView}, no `ElementNotFoundError` is fabricated; a missing
68
+ * element surfaces through Playwright's own auto-wait timeout.
69
+ *
70
+ * @param locator - Locator of the scrollable element
71
+ * @param delta - Pixel offset to scroll by
72
+ */
73
+ scrollBy(locator: PartLocator, delta: Point): Promise<void>;
74
+ /**
75
+ * Drag the source element and drop it onto the target element.
76
+ *
77
+ * Delegates to Playwright's native `Locator.dragTo`, which performs a real,
78
+ * layout-aware drag gesture in the browser. Per this layer's convention, no
79
+ * `ElementNotFoundError` is fabricated: a missing element surfaces through
80
+ * Playwright's own auto-wait timeout.
81
+ *
82
+ * @param source - Locator of the element to drag
83
+ * @param target - Locator of the drop target
84
+ */
85
+ dragTo(source: PartLocator, target: PartLocator): Promise<void>;
86
+ /**
87
+ * Drag the located element by the given pixel delta from its center.
88
+ *
89
+ * The gesture is a single uninterrupted `move → down → move → up` sequence
90
+ * computed from the element's center. It deliberately does NOT reuse
91
+ * {@link mouseMove}/{@link mouseDown} — `mouseMove` resets the pointer with
92
+ * `page.mouse.move(0, 0)` after hovering, which would corrupt the drag path
93
+ * (see ADR 0001, option 5). The center comes from {@link getBoundingRect},
94
+ * which throws `ElementNotFoundError` when the element has no box
95
+ * (detached/invisible) rather than auto-waiting — so this shares that
96
+ * "element not found" contract instead of re-deriving the box + guard here.
97
+ *
98
+ * @param locator - Locator of the element to drag
99
+ * @param delta - Pixel offset to drag by
100
+ * @throws {ElementNotFoundError} If the element has no bounding box
101
+ */
102
+ drag(locator: PartLocator, delta: Point): Promise<void>;
33
103
  /**
34
104
  * Get the value of an `<input>` element.
35
105
  *
@@ -48,6 +118,17 @@ declare class PlaywrightInteractor implements Interactor {
48
118
  getStyleValue(locator: PartLocator, propertyName: CssProperty): Promise<Optional<string>>;
49
119
  enterText(locator: PartLocator, text: string, option?: Optional<Partial<EnterTextOption>>): Promise<void>;
50
120
  click(locator: PartLocator, option?: Partial<ClickOption>): Promise<void>;
121
+ /**
122
+ * Dispatch a right-click on the located element to open its context menu.
123
+ *
124
+ * Delegates to Playwright's native right-button click, which fires a real
125
+ * `contextmenu` event in the browser. Per this layer's convention, no
126
+ * `ElementNotFoundError` is fabricated: a missing element surfaces through
127
+ * Playwright's own auto-wait timeout.
128
+ *
129
+ * @param locator - Locator of the element to right-click
130
+ */
131
+ contextMenu(locator: PartLocator): Promise<void>;
51
132
  hover(locator: PartLocator, option?: Partial<HoverOption>): Promise<void>;
52
133
  mouseMove(locator: PartLocator, option?: Partial<MouseMoveOption>): Promise<void>;
53
134
  mouseDown(locator: PartLocator, option?: Partial<MouseDownOption>): Promise<void>;
@@ -58,6 +139,8 @@ declare class PlaywrightInteractor implements Interactor {
58
139
  mouseLeave(locator: PartLocator, _option?: Partial<MouseLeaveOption>): Promise<void>;
59
140
  focus(locator: PartLocator, _option?: Partial<FocusOption>): Promise<void>;
60
141
  blur(locator: PartLocator, _option?: Partial<BlurOption>): Promise<void>;
142
+ pressKey(locator: PartLocator, key: string, option?: Partial<PressKeyOption>): Promise<void>;
143
+ activate(locator: PartLocator): Promise<void>;
61
144
  wait(ms: number): Promise<void>;
62
145
  waitUntilComponentState(locator: PartLocator, option?: Partial<Readonly<WaitForOption>>): Promise<void>;
63
146
  waitUntil<T>(option: WaitUntilOption<T>): Promise<T>;
@@ -65,6 +148,19 @@ declare class PlaywrightInteractor implements Interactor {
65
148
  getAttribute(locator: PartLocator, name: string, isMultiple: false): Promise<Optional<string>>;
66
149
  getAttribute(locator: PartLocator, name: string): Promise<Optional<string>>;
67
150
  getText(locator: PartLocator): Promise<Optional<string>>;
151
+ /**
152
+ * Get the located element's bounding rectangle.
153
+ *
154
+ * `boundingBox()` returns `null` for a detached/invisible element rather than
155
+ * auto-waiting, so this is one of the few Playwright methods that throws
156
+ * `ElementNotFoundError` — matching the house "element not found" contract
157
+ * (ADR 0001).
158
+ *
159
+ * @param locator - Locator of the element to measure
160
+ * @returns The element's bounding rectangle in CSS pixels
161
+ * @throws {ElementNotFoundError} If the element has no bounding box
162
+ */
163
+ getBoundingRect(locator: PartLocator): Promise<BoundingRect>;
68
164
  exists(locator: PartLocator): Promise<boolean>;
69
165
  isChecked(locator: PartLocator): Promise<boolean>;
70
166
  isDisabled(locator: PartLocator): Promise<boolean>;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { TestEngine, byCssSelector, dateUtil, defaultWaitForOption, interactorUtil, locatorUtil, timingUtil } from "@atomic-testing/core";
1
+ import { ElementNotFoundError, TestEngine, byCssSelector, dateUtil, defaultWaitForOption, interactorUtil, locatorUtil, timingUtil } from "@atomic-testing/core";
2
2
  import { expect, test } from "@playwright/test";
3
3
  //#region src/PlaywrightInteractor.ts
4
4
  /**
@@ -22,6 +22,100 @@ var PlaywrightInteractor = class PlaywrightInteractor {
22
22
  await this.page.locator(cssLocator).selectOption(values);
23
23
  }
24
24
  /**
25
+ * Set the selected files on a `<input type="file">` element.
26
+ *
27
+ * Playwright's native `setInputFiles` reads the given paths from disk and
28
+ * populates the input's `FileList`, firing the change event — the only way to
29
+ * fill a file input, whose value cannot be set programmatically. Following
30
+ * this layer's convention, no `ElementNotFoundError` is fabricated: a missing
31
+ * element surfaces through Playwright's own auto-wait timeout.
32
+ *
33
+ * @param locator - Locator of the `<input type="file">` element
34
+ * @param files - One or more filesystem paths to upload
35
+ */
36
+ async setInputFiles(locator, files) {
37
+ const cssLocator = await locatorUtil.toCssSelector(locator, this);
38
+ await this.page.locator(cssLocator).setInputFiles(files);
39
+ }
40
+ /**
41
+ * Scroll the located element into view, no-op if already visible.
42
+ *
43
+ * Delegates to Playwright's `scrollIntoViewIfNeeded`, which performs a real
44
+ * layout-aware scroll in the browser. Per this layer's convention, no
45
+ * `ElementNotFoundError` is fabricated: a missing element surfaces through
46
+ * Playwright's own auto-wait timeout.
47
+ *
48
+ * @param locator - Locator of the element to scroll into view
49
+ */
50
+ async scrollIntoView(locator) {
51
+ const css = await locatorUtil.toCssSelector(locator, this);
52
+ await this.page.locator(css).scrollIntoViewIfNeeded();
53
+ }
54
+ /**
55
+ * Scroll the located element by the given pixel delta.
56
+ *
57
+ * The scroll is performed by evaluating `el.scrollBy(dx, dy)` on the element
58
+ * itself rather than `page.mouse.wheel`. A wheel event scrolls whatever sits
59
+ * under the pointer and is non-deterministic across chromium/firefox/webkit,
60
+ * whereas evaluating `scrollBy` on the resolved element scrolls exactly that
61
+ * element. This is a deliberate deviation from ADR 0001's per-engine table
62
+ * (which lists `page.mouse.wheel`), taking the alternative the step-5 prompt
63
+ * permits ("or evaluate el.scrollBy") for cross-browser determinism. As with
64
+ * {@link scrollIntoView}, no `ElementNotFoundError` is fabricated; a missing
65
+ * element surfaces through Playwright's own auto-wait timeout.
66
+ *
67
+ * @param locator - Locator of the scrollable element
68
+ * @param delta - Pixel offset to scroll by
69
+ */
70
+ async scrollBy(locator, delta) {
71
+ const css = await locatorUtil.toCssSelector(locator, this);
72
+ await this.page.locator(css).evaluate((el, d) => el.scrollBy(d.x, d.y), {
73
+ x: delta.x,
74
+ y: delta.y
75
+ });
76
+ }
77
+ /**
78
+ * Drag the source element and drop it onto the target element.
79
+ *
80
+ * Delegates to Playwright's native `Locator.dragTo`, which performs a real,
81
+ * layout-aware drag gesture in the browser. Per this layer's convention, no
82
+ * `ElementNotFoundError` is fabricated: a missing element surfaces through
83
+ * Playwright's own auto-wait timeout.
84
+ *
85
+ * @param source - Locator of the element to drag
86
+ * @param target - Locator of the drop target
87
+ */
88
+ async dragTo(source, target) {
89
+ const srcCss = await locatorUtil.toCssSelector(source, this);
90
+ const tgtCss = await locatorUtil.toCssSelector(target, this);
91
+ await this.page.locator(srcCss).dragTo(this.page.locator(tgtCss));
92
+ }
93
+ /**
94
+ * Drag the located element by the given pixel delta from its center.
95
+ *
96
+ * The gesture is a single uninterrupted `move → down → move → up` sequence
97
+ * computed from the element's center. It deliberately does NOT reuse
98
+ * {@link mouseMove}/{@link mouseDown} — `mouseMove` resets the pointer with
99
+ * `page.mouse.move(0, 0)` after hovering, which would corrupt the drag path
100
+ * (see ADR 0001, option 5). The center comes from {@link getBoundingRect},
101
+ * which throws `ElementNotFoundError` when the element has no box
102
+ * (detached/invisible) rather than auto-waiting — so this shares that
103
+ * "element not found" contract instead of re-deriving the box + guard here.
104
+ *
105
+ * @param locator - Locator of the element to drag
106
+ * @param delta - Pixel offset to drag by
107
+ * @throws {ElementNotFoundError} If the element has no bounding box
108
+ */
109
+ async drag(locator, delta) {
110
+ const rect = await this.getBoundingRect(locator);
111
+ const cx = rect.x + rect.width / 2;
112
+ const cy = rect.y + rect.height / 2;
113
+ await this.page.mouse.move(cx, cy);
114
+ await this.page.mouse.down();
115
+ await this.page.mouse.move(cx + delta.x, cy + delta.y, { steps: 8 });
116
+ await this.page.mouse.up();
117
+ }
118
+ /**
25
119
  * Get the value of an `<input>` element.
26
120
  *
27
121
  * @param locator - Locator pointing to the input element.
@@ -81,6 +175,20 @@ var PlaywrightInteractor = class PlaywrightInteractor {
81
175
  const cssLocator = await locatorUtil.toCssSelector(locator, this);
82
176
  await this.page.locator(cssLocator).click({ position: option?.position });
83
177
  }
178
+ /**
179
+ * Dispatch a right-click on the located element to open its context menu.
180
+ *
181
+ * Delegates to Playwright's native right-button click, which fires a real
182
+ * `contextmenu` event in the browser. Per this layer's convention, no
183
+ * `ElementNotFoundError` is fabricated: a missing element surfaces through
184
+ * Playwright's own auto-wait timeout.
185
+ *
186
+ * @param locator - Locator of the element to right-click
187
+ */
188
+ async contextMenu(locator) {
189
+ const css = await locatorUtil.toCssSelector(locator, this);
190
+ await this.page.locator(css).click({ button: "right" });
191
+ }
84
192
  async hover(locator, option) {
85
193
  const cssLocator = await locatorUtil.toCssSelector(locator, this);
86
194
  await this.page.locator(cssLocator).hover({ position: option?.position });
@@ -121,6 +229,20 @@ var PlaywrightInteractor = class PlaywrightInteractor {
121
229
  const cssLocator = await locatorUtil.toCssSelector(locator, this);
122
230
  await this.page.locator(cssLocator).blur();
123
231
  }
232
+ async pressKey(locator, key, option) {
233
+ const cssLocator = await locatorUtil.toCssSelector(locator, this);
234
+ const modifiers = [];
235
+ if (option?.ctrl) modifiers.push("Control");
236
+ if (option?.alt) modifiers.push("Alt");
237
+ if (option?.shift) modifiers.push("Shift");
238
+ if (option?.meta) modifiers.push("Meta");
239
+ const chord = modifiers.length > 0 ? `${modifiers.join("+")}+${key}` : key;
240
+ await this.page.locator(cssLocator).press(chord);
241
+ }
242
+ async activate(locator) {
243
+ const cssLocator = await locatorUtil.toCssSelector(locator, this);
244
+ await this.page.locator(cssLocator).dispatchEvent("click");
245
+ }
124
246
  wait(ms) {
125
247
  return timingUtil.wait(ms);
126
248
  }
@@ -148,6 +270,29 @@ var PlaywrightInteractor = class PlaywrightInteractor {
148
270
  const cssLocator = await locatorUtil.toCssSelector(locator, this);
149
271
  return await this.page.locator(cssLocator).textContent() ?? void 0;
150
272
  }
273
+ /**
274
+ * Get the located element's bounding rectangle.
275
+ *
276
+ * `boundingBox()` returns `null` for a detached/invisible element rather than
277
+ * auto-waiting, so this is one of the few Playwright methods that throws
278
+ * `ElementNotFoundError` — matching the house "element not found" contract
279
+ * (ADR 0001).
280
+ *
281
+ * @param locator - Locator of the element to measure
282
+ * @returns The element's bounding rectangle in CSS pixels
283
+ * @throws {ElementNotFoundError} If the element has no bounding box
284
+ */
285
+ async getBoundingRect(locator) {
286
+ const css = await locatorUtil.toCssSelector(locator, this);
287
+ const box = await this.page.locator(css).boundingBox();
288
+ if (box == null) throw new ElementNotFoundError(locator, "getBoundingRect");
289
+ return {
290
+ x: box.x,
291
+ y: box.y,
292
+ width: box.width,
293
+ height: box.height
294
+ };
295
+ }
151
296
  async exists(locator) {
152
297
  const cssLocator = await locatorUtil.toCssSelector(locator, this);
153
298
  return await this.page.locator(cssLocator).count() > 0;
@@ -237,7 +382,8 @@ const playWrightTestFrameworkMapper = {
237
382
  beforeAll: test.beforeAll,
238
383
  afterAll: test.afterAll,
239
384
  test,
240
- it: test
385
+ it: test,
386
+ hasLayout: true
241
387
  };
242
388
  /**
243
389
  * Get a typed interface for running end-to-end tests with Playwright.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/PlaywrightInteractor.ts","../src/createTestEngine.ts","../src/testRunnerAdapter.ts"],"sourcesContent":["import {\n BlurOption,\n byCssSelector,\n ClickOption,\n CssProperty,\n dateUtil,\n defaultWaitForOption,\n EnterTextOption,\n FocusOption,\n HoverOption,\n Interactor,\n interactorUtil,\n locatorUtil,\n MouseEnterOption,\n MouseLeaveOption,\n MouseOutOption,\n MouseDownOption,\n MouseMoveOption,\n MouseUpOption,\n Optional,\n PartLocator,\n timingUtil,\n WaitForOption,\n WaitUntilOption,\n} from '@atomic-testing/core';\nimport { Page } from '@playwright/test';\n\n/**\n * Implementation of the {@link Interactor} interface using Playwright.\n */\nexport class PlaywrightInteractor implements Interactor {\n /**\n * @param page - Playwright page instance used to drive the browser.\n */\n constructor(public readonly page: Page) {}\n\n /**\n * Select the given option values on a `<select>` element.\n *\n * @param locator - Locator to the `<select>` element.\n * @param values - Values to select.\n */\n async selectOptionValue(locator: PartLocator, values: string[]): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).selectOption(values);\n }\n\n /**\n * Get the value of an `<input>` element.\n *\n * @param locator - Locator pointing to the input element.\n * @returns The current value of the input or `undefined` if not present.\n */\n async getInputValue(locator: PartLocator): Promise<Optional<string>> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n return this.page.locator(cssLocator).inputValue();\n }\n\n /**\n * Retrieve the values of selected options within a `<select>` element.\n *\n * @param locator - Locator to the `<select>` element.\n * @returns Array of selected option values or `undefined` when no option is selected.\n */\n async getSelectValues(locator: PartLocator): Promise<Optional<readonly string[]>> {\n const optionLocator: PartLocator = byCssSelector('option:checked');\n const selectedOptionLocator = locatorUtil.append(locator, optionLocator);\n const cssLocator = await locatorUtil.toCssSelector(selectedOptionLocator, this);\n const allOptions = await this.page.locator(cssLocator).all();\n const values: string[] = [];\n for (const option of allOptions) {\n const value = await option.getAttribute('value');\n if (value != null) {\n values.push(value);\n }\n }\n return values;\n }\n\n async getSelectLabels(locator: PartLocator): Promise<Optional<readonly string[]>> {\n const optionLocator: PartLocator = byCssSelector('option:checked');\n const selectedOptionLocator = locatorUtil.append(locator, optionLocator);\n const cssLocator = await locatorUtil.toCssSelector(selectedOptionLocator, this);\n const allOptions = await this.page.locator(cssLocator).all();\n const labels: string[] = [];\n for (const option of allOptions) {\n const label = await option.textContent();\n if (label != null) {\n labels.push(label);\n }\n }\n return labels;\n }\n\n async getStyleValue(locator: PartLocator, propertyName: CssProperty): Promise<Optional<string>> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const elLocator = this.page.locator(cssLocator);\n const value = await elLocator.evaluate((element, prop) => {\n return window.getComputedStyle(element).getPropertyValue(prop as string);\n }, propertyName);\n return value;\n }\n\n async enterText(locator: PartLocator, text: string, option?: Optional<Partial<EnterTextOption>>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n if (!option?.append) {\n await this.page.locator(cssLocator).clear();\n }\n\n // If it is a date, time or datetime-local input, validate the date format\n const type = (await this.getAttribute(locator, 'type')) ?? '';\n if (dateUtil.isHtmlDateInputType(type)) {\n const result = dateUtil.validateHtmlDateInput(type, text);\n if (!result.valid) {\n throw new Error(\n `Invalid date format for type: ${type}, expected format: ${result.format}, example: ${result.example}`\n );\n }\n }\n await this.page.locator(cssLocator).fill(text);\n }\n\n async click(locator: PartLocator, option?: Partial<ClickOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).click({ position: option?.position });\n }\n\n async hover(locator: PartLocator, option?: Partial<HoverOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).hover({ position: option?.position });\n }\n\n async mouseMove(locator: PartLocator, option?: Partial<MouseMoveOption>): Promise<void> {\n await this.hover(locator, {\n position: option?.position,\n });\n await this.page.mouse.move(0, 0);\n }\n\n async mouseDown(locator: PartLocator, option?: Partial<MouseDownOption>): Promise<void> {\n await this.hover(locator, {\n position: option?.position,\n });\n await this.page.mouse.down();\n }\n\n async mouseUp(locator: PartLocator, option?: Partial<MouseUpOption>): Promise<void> {\n await this.hover(locator, {\n position: option?.position,\n });\n await this.page.mouse.up();\n }\n\n async mouseOver(locator: PartLocator, option?: Partial<HoverOption>): Promise<void> {\n return this.hover(locator, option);\n }\n\n async mouseOut(locator: PartLocator, _option?: Partial<MouseOutOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n // First hover over the element to trigger mouseenter/mouseover\n await this.page.locator(cssLocator).hover();\n // Then dispatch mouseout event directly for cross-browser reliability\n await this.page.locator(cssLocator).dispatchEvent('mouseout');\n }\n\n async mouseEnter(locator: PartLocator, _option?: Partial<MouseEnterOption>): Promise<void> {\n return this.hover(locator);\n }\n\n async mouseLeave(locator: PartLocator, _option?: Partial<MouseLeaveOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n // First hover over the element to trigger mouseenter/mouseover\n await this.page.locator(cssLocator).hover();\n // Dispatch mouseout which triggers both mouseout and mouseleave handlers in React\n await this.page.locator(cssLocator).dispatchEvent('mouseout');\n }\n\n async focus(locator: PartLocator, _option?: Partial<FocusOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n return this.page.focus(cssLocator);\n }\n\n async blur(locator: PartLocator, _option?: Partial<BlurOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).blur();\n }\n\n //#region wait conditions\n wait(ms: number): Promise<void> {\n return timingUtil.wait(ms);\n }\n\n async waitUntilComponentState(\n locator: PartLocator,\n option: Partial<Readonly<WaitForOption>> = defaultWaitForOption\n ): Promise<void> {\n return interactorUtil.interactorWaitUtil(locator, this, option);\n }\n\n waitUntil<T>(option: WaitUntilOption<T>): Promise<T> {\n return timingUtil.waitUntil(option);\n }\n //#endregion\n\n async getAttribute(locator: PartLocator, name: string, isMultiple: true): Promise<readonly string[]>;\n async getAttribute(locator: PartLocator, name: string, isMultiple: false): Promise<Optional<string>>;\n async getAttribute(locator: PartLocator, name: string): Promise<Optional<string>>;\n async getAttribute(\n locator: PartLocator,\n name: string,\n isMultiple?: boolean\n ): Promise<Optional<string> | readonly string[]> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const elLocator = this.page.locator(cssLocator);\n if (isMultiple) {\n const locators = await elLocator.all();\n const values: string[] = [];\n for (const locator of locators) {\n const value = await locator.getAttribute(name);\n if (value != null) {\n values.push(value);\n }\n }\n return values;\n }\n const value = await elLocator.getAttribute(name);\n return value ?? undefined;\n }\n\n async getText(locator: PartLocator): Promise<Optional<string>> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const text = await this.page.locator(cssLocator).textContent();\n return text ?? undefined;\n }\n\n async exists(locator: PartLocator): Promise<boolean> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const count = await this.page.locator(cssLocator).count();\n return count > 0;\n }\n\n async isChecked(locator: PartLocator): Promise<boolean> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const checked = await this.page.locator(cssLocator).isChecked();\n return checked;\n }\n\n async isDisabled(locator: PartLocator): Promise<boolean> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const isDisabled = await this.page.locator(cssLocator).isDisabled();\n return isDisabled;\n }\n\n async isReadonly(locator: PartLocator): Promise<boolean> {\n const readonly = await this.getAttribute(locator, 'readonly');\n return readonly != null;\n }\n\n async isVisible(locator: PartLocator): Promise<boolean> {\n const exists = await this.exists(locator);\n if (!exists) {\n return false;\n }\n\n async function checkCssVisibility(\n prop: CssProperty,\n invisibleValue: string,\n interactor: PlaywrightInteractor\n ): Promise<boolean> {\n try {\n const value = await interactor.getStyleValue(locator, prop);\n return value !== invisibleValue;\n } catch (e) {\n // Element may disappear or detached while being checked because of animation\n // when it happens, an error is thrown. In this case, if indeed the element\n // is not visible, we return false. Otherwise, we re-throw the error.\n if ((await interactor.exists(locator)) === false) {\n return false;\n }\n throw e;\n }\n }\n\n if ((await checkCssVisibility('opacity', '0', this)) === false) {\n return false;\n }\n\n if ((await checkCssVisibility('visibility', 'hidden', this)) === false) {\n return false;\n }\n\n if ((await checkCssVisibility('display', 'none', this)) === false) {\n return false;\n }\n\n return true;\n }\n\n async hasCssClass(locator: PartLocator, className: string): Promise<boolean> {\n const classNames = await this.getAttribute(locator, 'class');\n if (classNames == null) {\n return false;\n }\n\n const names = classNames.split(/\\s+/);\n return names.includes(className);\n }\n\n async hasAttribute(locator: PartLocator, name: string): Promise<boolean> {\n const attrValue = await this.getAttribute(locator, name);\n return attrValue != null;\n }\n\n //#region\n async innerHTML(locator: PartLocator): Promise<string> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n return this.page.locator(cssLocator).innerHTML();\n }\n //#endregion\n\n clone(): Interactor {\n return new PlaywrightInteractor(this.page);\n }\n}\n","import { ScenePart, TestEngine } from '@atomic-testing/core';\nimport { Page } from '@playwright/test';\n\nimport { PlaywrightInteractor } from './PlaywrightInteractor';\n\n/**\n * Create a {@link TestEngine} instance backed by Playwright.\n *\n * @param page - Playwright page used for interaction.\n * @param partDefinitions - Scene part definitions describing the scene\n * structure for the engine.\n * @returns A configured {@link TestEngine} ready for use.\n */\nexport function createTestEngine<T extends ScenePart>(page: Page, partDefinitions: T): TestEngine<T> {\n const engine = new TestEngine([], new PlaywrightInteractor(page), {\n parts: partDefinitions,\n });\n\n return engine;\n}\n","import { ScenePart, TestEngine } from '@atomic-testing/core';\nimport {\n E2eTestInterface,\n E2eTestRunEnvironmentFixture,\n TestFrameworkMapper,\n} from '@atomic-testing/internal-test-runner';\nimport { expect, Page, test } from '@playwright/test';\n\nimport { createTestEngine } from './createTestEngine';\n\n/**\n * Navigate the current Playwright page to the provided URL.\n *\n * @param url - Destination URL to load.\n * @param fixture - Optional test fixture supplying the Playwright page.\n */\nexport async function goto(url: string): Promise<void>;\nexport async function goto(url: string, fixture: E2eTestRunEnvironmentFixture): Promise<void>;\nexport async function goto(url: string, fixture?: E2eTestRunEnvironmentFixture): Promise<void> {\n const page = fixture!.page as Page;\n await page.goto(url);\n}\n\n/**\n * Create a {@link TestEngine} bound to the Playwright page in the given fixture.\n *\n * @param scenePart - Scene definition to drive.\n * @param fixture - Fixture providing the Playwright page.\n */\nexport function playwrightGetTestEngine<T extends ScenePart>(\n scenePart: T,\n fixture: E2eTestRunEnvironmentFixture\n): TestEngine<T> {\n const page = fixture.page as Page;\n return createTestEngine(page, scenePart);\n}\n\n/**\n * Playwright adapter for the TestFrameworkMapper interface.\n */\nexport const playWrightTestFrameworkMapper: TestFrameworkMapper = {\n /*\n * INTENTIONAL @ts-expect-error comments: Playwright's test functions have different type\n * signatures than the normalized TestFrameworkMapper interface. Playwright uses fixture-based\n * callbacks with destructuring ({ page, browser }) while our interface uses a union type for\n * Jest compatibility (done callback or fixture object). The functions are compatible at runtime\n * but TypeScript cannot verify this due to these fundamental signature differences.\n */\n\n assertEqual: (a, b) => expect(a).toEqual(b),\n assertNotEqual: (a, b) => expect(a).not.toEqual(b),\n assertTrue: value => expect(value).toBe(true),\n assertFalse: value => expect(value).toBe(false),\n assertApproxEqual: (actual, expected, tolerance) =>\n expect(Math.abs(actual - expected)).toBeLessThanOrEqual(tolerance),\n // @ts-expect-error - Playwright describe signature differs from TestFrameworkMapper.Describe\n describe: test.describe,\n\n beforeEach: test.beforeEach,\n afterEach: test.afterEach,\n beforeAll: test.beforeAll,\n afterAll: test.afterAll,\n\n // @ts-expect-error - Playwright test signature differs from TestFrameworkMapper.Test\n test: test,\n\n // @ts-expect-error - Playwright test signature differs from TestFrameworkMapper.Test\n it: test,\n};\n\n/**\n * Get a typed interface for running end-to-end tests with Playwright.\n */\nexport function getTestRunnerInterface<T extends ScenePart>(): E2eTestInterface<T> {\n return {\n getTestEngine: playwrightGetTestEngine,\n goto,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/PlaywrightInteractor.ts","../src/createTestEngine.ts","../src/testRunnerAdapter.ts"],"sourcesContent":["import {\n BlurOption,\n BoundingRect,\n byCssSelector,\n ClickOption,\n CssProperty,\n dateUtil,\n defaultWaitForOption,\n ElementNotFoundError,\n EnterTextOption,\n FocusOption,\n HoverOption,\n Interactor,\n interactorUtil,\n locatorUtil,\n MouseEnterOption,\n MouseLeaveOption,\n MouseOutOption,\n MouseDownOption,\n MouseMoveOption,\n MouseUpOption,\n Optional,\n PartLocator,\n Point,\n PressKeyOption,\n timingUtil,\n WaitForOption,\n WaitUntilOption,\n} from '@atomic-testing/core';\nimport { Page } from '@playwright/test';\n\n/**\n * Implementation of the {@link Interactor} interface using Playwright.\n */\nexport class PlaywrightInteractor implements Interactor {\n /**\n * @param page - Playwright page instance used to drive the browser.\n */\n constructor(public readonly page: Page) {}\n\n /**\n * Select the given option values on a `<select>` element.\n *\n * @param locator - Locator to the `<select>` element.\n * @param values - Values to select.\n */\n async selectOptionValue(locator: PartLocator, values: string[]): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).selectOption(values);\n }\n\n /**\n * Set the selected files on a `<input type=\"file\">` element.\n *\n * Playwright's native `setInputFiles` reads the given paths from disk and\n * populates the input's `FileList`, firing the change event — the only way to\n * fill a file input, whose value cannot be set programmatically. Following\n * this layer's convention, no `ElementNotFoundError` is fabricated: a missing\n * element surfaces through Playwright's own auto-wait timeout.\n *\n * @param locator - Locator of the `<input type=\"file\">` element\n * @param files - One or more filesystem paths to upload\n */\n async setInputFiles(locator: PartLocator, files: string | string[]): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).setInputFiles(files);\n }\n\n /**\n * Scroll the located element into view, no-op if already visible.\n *\n * Delegates to Playwright's `scrollIntoViewIfNeeded`, which performs a real\n * layout-aware scroll in the browser. Per this layer's convention, no\n * `ElementNotFoundError` is fabricated: a missing element surfaces through\n * Playwright's own auto-wait timeout.\n *\n * @param locator - Locator of the element to scroll into view\n */\n async scrollIntoView(locator: PartLocator): Promise<void> {\n const css = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(css).scrollIntoViewIfNeeded();\n }\n\n /**\n * Scroll the located element by the given pixel delta.\n *\n * The scroll is performed by evaluating `el.scrollBy(dx, dy)` on the element\n * itself rather than `page.mouse.wheel`. A wheel event scrolls whatever sits\n * under the pointer and is non-deterministic across chromium/firefox/webkit,\n * whereas evaluating `scrollBy` on the resolved element scrolls exactly that\n * element. This is a deliberate deviation from ADR 0001's per-engine table\n * (which lists `page.mouse.wheel`), taking the alternative the step-5 prompt\n * permits (\"or evaluate el.scrollBy\") for cross-browser determinism. As with\n * {@link scrollIntoView}, no `ElementNotFoundError` is fabricated; a missing\n * element surfaces through Playwright's own auto-wait timeout.\n *\n * @param locator - Locator of the scrollable element\n * @param delta - Pixel offset to scroll by\n */\n async scrollBy(locator: PartLocator, delta: Point): Promise<void> {\n const css = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(css).evaluate((el, d) => el.scrollBy(d.x, d.y), { x: delta.x, y: delta.y });\n }\n\n /**\n * Drag the source element and drop it onto the target element.\n *\n * Delegates to Playwright's native `Locator.dragTo`, which performs a real,\n * layout-aware drag gesture in the browser. Per this layer's convention, no\n * `ElementNotFoundError` is fabricated: a missing element surfaces through\n * Playwright's own auto-wait timeout.\n *\n * @param source - Locator of the element to drag\n * @param target - Locator of the drop target\n */\n async dragTo(source: PartLocator, target: PartLocator): Promise<void> {\n const srcCss = await locatorUtil.toCssSelector(source, this);\n const tgtCss = await locatorUtil.toCssSelector(target, this);\n await this.page.locator(srcCss).dragTo(this.page.locator(tgtCss));\n }\n\n /**\n * Drag the located element by the given pixel delta from its center.\n *\n * The gesture is a single uninterrupted `move → down → move → up` sequence\n * computed from the element's center. It deliberately does NOT reuse\n * {@link mouseMove}/{@link mouseDown} — `mouseMove` resets the pointer with\n * `page.mouse.move(0, 0)` after hovering, which would corrupt the drag path\n * (see ADR 0001, option 5). The center comes from {@link getBoundingRect},\n * which throws `ElementNotFoundError` when the element has no box\n * (detached/invisible) rather than auto-waiting — so this shares that\n * \"element not found\" contract instead of re-deriving the box + guard here.\n *\n * @param locator - Locator of the element to drag\n * @param delta - Pixel offset to drag by\n * @throws {ElementNotFoundError} If the element has no bounding box\n */\n async drag(locator: PartLocator, delta: Point): Promise<void> {\n const rect = await this.getBoundingRect(locator);\n const cx = rect.x + rect.width / 2;\n const cy = rect.y + rect.height / 2;\n await this.page.mouse.move(cx, cy);\n await this.page.mouse.down();\n await this.page.mouse.move(cx + delta.x, cy + delta.y, { steps: 8 });\n await this.page.mouse.up();\n }\n\n /**\n * Get the value of an `<input>` element.\n *\n * @param locator - Locator pointing to the input element.\n * @returns The current value of the input or `undefined` if not present.\n */\n async getInputValue(locator: PartLocator): Promise<Optional<string>> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n return this.page.locator(cssLocator).inputValue();\n }\n\n /**\n * Retrieve the values of selected options within a `<select>` element.\n *\n * @param locator - Locator to the `<select>` element.\n * @returns Array of selected option values or `undefined` when no option is selected.\n */\n async getSelectValues(locator: PartLocator): Promise<Optional<readonly string[]>> {\n const optionLocator: PartLocator = byCssSelector('option:checked');\n const selectedOptionLocator = locatorUtil.append(locator, optionLocator);\n const cssLocator = await locatorUtil.toCssSelector(selectedOptionLocator, this);\n const allOptions = await this.page.locator(cssLocator).all();\n const values: string[] = [];\n for (const option of allOptions) {\n const value = await option.getAttribute('value');\n if (value != null) {\n values.push(value);\n }\n }\n return values;\n }\n\n async getSelectLabels(locator: PartLocator): Promise<Optional<readonly string[]>> {\n const optionLocator: PartLocator = byCssSelector('option:checked');\n const selectedOptionLocator = locatorUtil.append(locator, optionLocator);\n const cssLocator = await locatorUtil.toCssSelector(selectedOptionLocator, this);\n const allOptions = await this.page.locator(cssLocator).all();\n const labels: string[] = [];\n for (const option of allOptions) {\n const label = await option.textContent();\n if (label != null) {\n labels.push(label);\n }\n }\n return labels;\n }\n\n async getStyleValue(locator: PartLocator, propertyName: CssProperty): Promise<Optional<string>> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const elLocator = this.page.locator(cssLocator);\n const value = await elLocator.evaluate((element, prop) => {\n return window.getComputedStyle(element).getPropertyValue(prop as string);\n }, propertyName);\n return value;\n }\n\n async enterText(locator: PartLocator, text: string, option?: Optional<Partial<EnterTextOption>>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n if (!option?.append) {\n await this.page.locator(cssLocator).clear();\n }\n\n // If it is a date, time or datetime-local input, validate the date format\n const type = (await this.getAttribute(locator, 'type')) ?? '';\n if (dateUtil.isHtmlDateInputType(type)) {\n const result = dateUtil.validateHtmlDateInput(type, text);\n if (!result.valid) {\n throw new Error(\n `Invalid date format for type: ${type}, expected format: ${result.format}, example: ${result.example}`\n );\n }\n }\n await this.page.locator(cssLocator).fill(text);\n }\n\n async click(locator: PartLocator, option?: Partial<ClickOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).click({ position: option?.position });\n }\n\n /**\n * Dispatch a right-click on the located element to open its context menu.\n *\n * Delegates to Playwright's native right-button click, which fires a real\n * `contextmenu` event in the browser. Per this layer's convention, no\n * `ElementNotFoundError` is fabricated: a missing element surfaces through\n * Playwright's own auto-wait timeout.\n *\n * @param locator - Locator of the element to right-click\n */\n async contextMenu(locator: PartLocator): Promise<void> {\n const css = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(css).click({ button: 'right' });\n }\n\n async hover(locator: PartLocator, option?: Partial<HoverOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).hover({ position: option?.position });\n }\n\n async mouseMove(locator: PartLocator, option?: Partial<MouseMoveOption>): Promise<void> {\n await this.hover(locator, {\n position: option?.position,\n });\n await this.page.mouse.move(0, 0);\n }\n\n async mouseDown(locator: PartLocator, option?: Partial<MouseDownOption>): Promise<void> {\n await this.hover(locator, {\n position: option?.position,\n });\n await this.page.mouse.down();\n }\n\n async mouseUp(locator: PartLocator, option?: Partial<MouseUpOption>): Promise<void> {\n await this.hover(locator, {\n position: option?.position,\n });\n await this.page.mouse.up();\n }\n\n async mouseOver(locator: PartLocator, option?: Partial<HoverOption>): Promise<void> {\n return this.hover(locator, option);\n }\n\n async mouseOut(locator: PartLocator, _option?: Partial<MouseOutOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n // First hover over the element to trigger mouseenter/mouseover\n await this.page.locator(cssLocator).hover();\n // Then dispatch mouseout event directly for cross-browser reliability\n await this.page.locator(cssLocator).dispatchEvent('mouseout');\n }\n\n async mouseEnter(locator: PartLocator, _option?: Partial<MouseEnterOption>): Promise<void> {\n return this.hover(locator);\n }\n\n async mouseLeave(locator: PartLocator, _option?: Partial<MouseLeaveOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n // First hover over the element to trigger mouseenter/mouseover\n await this.page.locator(cssLocator).hover();\n // Dispatch mouseout which triggers both mouseout and mouseleave handlers in React\n await this.page.locator(cssLocator).dispatchEvent('mouseout');\n }\n\n async focus(locator: PartLocator, _option?: Partial<FocusOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n return this.page.focus(cssLocator);\n }\n\n async blur(locator: PartLocator, _option?: Partial<BlurOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n await this.page.locator(cssLocator).blur();\n }\n\n async pressKey(locator: PartLocator, key: string, option?: Partial<PressKeyOption>): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n // Compose Playwright's chord syntax — modifiers joined to the key by `+`, in\n // Playwright's accepted Control+Alt+Shift+Meta order — so the browser holds\n // those modifiers across the keypress and the event carries ctrlKey/etc.\n const modifiers: string[] = [];\n if (option?.ctrl) {\n modifiers.push('Control');\n }\n if (option?.alt) {\n modifiers.push('Alt');\n }\n if (option?.shift) {\n modifiers.push('Shift');\n }\n if (option?.meta) {\n modifiers.push('Meta');\n }\n const chord = modifiers.length > 0 ? `${modifiers.join('+')}+${key}` : key;\n // locator.press auto-focuses the element, then dispatches a real, trusted\n // KeyboardEvent — the browser equivalent of the DOM focus-first keyDown/keyUp.\n // Caveat: for Shift + a printable key the browser case-folds `event.key`\n // (`Shift+a` → `'A'`) whereas the jsdom path keeps `'a'` — only the modifier\n // flags are delivered identically across engines (see #924).\n await this.page.locator(cssLocator).press(chord);\n }\n\n async activate(locator: PartLocator): Promise<void> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n // Geometry-free activation mirrors the mouseout dispatch precedent above: it\n // bypasses hit-testing to actuate a covered or zero-size input that\n // locator.click() (a real geometry hit-test) cannot reach.\n await this.page.locator(cssLocator).dispatchEvent('click');\n }\n\n //#region wait conditions\n wait(ms: number): Promise<void> {\n return timingUtil.wait(ms);\n }\n\n async waitUntilComponentState(\n locator: PartLocator,\n option: Partial<Readonly<WaitForOption>> = defaultWaitForOption\n ): Promise<void> {\n return interactorUtil.interactorWaitUtil(locator, this, option);\n }\n\n waitUntil<T>(option: WaitUntilOption<T>): Promise<T> {\n return timingUtil.waitUntil(option);\n }\n //#endregion\n\n async getAttribute(locator: PartLocator, name: string, isMultiple: true): Promise<readonly string[]>;\n async getAttribute(locator: PartLocator, name: string, isMultiple: false): Promise<Optional<string>>;\n async getAttribute(locator: PartLocator, name: string): Promise<Optional<string>>;\n async getAttribute(\n locator: PartLocator,\n name: string,\n isMultiple?: boolean\n ): Promise<Optional<string> | readonly string[]> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const elLocator = this.page.locator(cssLocator);\n if (isMultiple) {\n const locators = await elLocator.all();\n const values: string[] = [];\n for (const locator of locators) {\n const value = await locator.getAttribute(name);\n if (value != null) {\n values.push(value);\n }\n }\n return values;\n }\n const value = await elLocator.getAttribute(name);\n return value ?? undefined;\n }\n\n async getText(locator: PartLocator): Promise<Optional<string>> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const text = await this.page.locator(cssLocator).textContent();\n return text ?? undefined;\n }\n\n /**\n * Get the located element's bounding rectangle.\n *\n * `boundingBox()` returns `null` for a detached/invisible element rather than\n * auto-waiting, so this is one of the few Playwright methods that throws\n * `ElementNotFoundError` — matching the house \"element not found\" contract\n * (ADR 0001).\n *\n * @param locator - Locator of the element to measure\n * @returns The element's bounding rectangle in CSS pixels\n * @throws {ElementNotFoundError} If the element has no bounding box\n */\n async getBoundingRect(locator: PartLocator): Promise<BoundingRect> {\n const css = await locatorUtil.toCssSelector(locator, this);\n const box = await this.page.locator(css).boundingBox();\n if (box == null) {\n throw new ElementNotFoundError(locator, 'getBoundingRect');\n }\n return { x: box.x, y: box.y, width: box.width, height: box.height };\n }\n\n async exists(locator: PartLocator): Promise<boolean> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const count = await this.page.locator(cssLocator).count();\n return count > 0;\n }\n\n async isChecked(locator: PartLocator): Promise<boolean> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const checked = await this.page.locator(cssLocator).isChecked();\n return checked;\n }\n\n async isDisabled(locator: PartLocator): Promise<boolean> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n const isDisabled = await this.page.locator(cssLocator).isDisabled();\n return isDisabled;\n }\n\n async isReadonly(locator: PartLocator): Promise<boolean> {\n const readonly = await this.getAttribute(locator, 'readonly');\n return readonly != null;\n }\n\n async isVisible(locator: PartLocator): Promise<boolean> {\n const exists = await this.exists(locator);\n if (!exists) {\n return false;\n }\n\n async function checkCssVisibility(\n prop: CssProperty,\n invisibleValue: string,\n interactor: PlaywrightInteractor\n ): Promise<boolean> {\n try {\n const value = await interactor.getStyleValue(locator, prop);\n return value !== invisibleValue;\n } catch (e) {\n // Element may disappear or detached while being checked because of animation\n // when it happens, an error is thrown. In this case, if indeed the element\n // is not visible, we return false. Otherwise, we re-throw the error.\n if ((await interactor.exists(locator)) === false) {\n return false;\n }\n throw e;\n }\n }\n\n if ((await checkCssVisibility('opacity', '0', this)) === false) {\n return false;\n }\n\n if ((await checkCssVisibility('visibility', 'hidden', this)) === false) {\n return false;\n }\n\n if ((await checkCssVisibility('display', 'none', this)) === false) {\n return false;\n }\n\n return true;\n }\n\n async hasCssClass(locator: PartLocator, className: string): Promise<boolean> {\n const classNames = await this.getAttribute(locator, 'class');\n if (classNames == null) {\n return false;\n }\n\n const names = classNames.split(/\\s+/);\n return names.includes(className);\n }\n\n async hasAttribute(locator: PartLocator, name: string): Promise<boolean> {\n const attrValue = await this.getAttribute(locator, name);\n return attrValue != null;\n }\n\n //#region\n async innerHTML(locator: PartLocator): Promise<string> {\n const cssLocator = await locatorUtil.toCssSelector(locator, this);\n return this.page.locator(cssLocator).innerHTML();\n }\n //#endregion\n\n clone(): Interactor {\n return new PlaywrightInteractor(this.page);\n }\n}\n","import { ScenePart, TestEngine } from '@atomic-testing/core';\nimport { Page } from '@playwright/test';\n\nimport { PlaywrightInteractor } from './PlaywrightInteractor';\n\n/**\n * Create a {@link TestEngine} instance backed by Playwright.\n *\n * @param page - Playwright page used for interaction.\n * @param partDefinitions - Scene part definitions describing the scene\n * structure for the engine.\n * @returns A configured {@link TestEngine} ready for use.\n */\nexport function createTestEngine<T extends ScenePart>(page: Page, partDefinitions: T): TestEngine<T> {\n const engine = new TestEngine([], new PlaywrightInteractor(page), {\n parts: partDefinitions,\n });\n\n return engine;\n}\n","import { ScenePart, TestEngine } from '@atomic-testing/core';\nimport {\n E2eTestInterface,\n E2eTestRunEnvironmentFixture,\n TestFrameworkMapper,\n} from '@atomic-testing/internal-test-runner';\nimport { expect, Page, test } from '@playwright/test';\n\nimport { createTestEngine } from './createTestEngine';\n\n/**\n * Navigate the current Playwright page to the provided URL.\n *\n * @param url - Destination URL to load.\n * @param fixture - Optional test fixture supplying the Playwright page.\n */\nexport async function goto(url: string): Promise<void>;\nexport async function goto(url: string, fixture: E2eTestRunEnvironmentFixture): Promise<void>;\nexport async function goto(url: string, fixture?: E2eTestRunEnvironmentFixture): Promise<void> {\n const page = fixture!.page as Page;\n await page.goto(url);\n}\n\n/**\n * Create a {@link TestEngine} bound to the Playwright page in the given fixture.\n *\n * @param scenePart - Scene definition to drive.\n * @param fixture - Fixture providing the Playwright page.\n */\nexport function playwrightGetTestEngine<T extends ScenePart>(\n scenePart: T,\n fixture: E2eTestRunEnvironmentFixture\n): TestEngine<T> {\n const page = fixture.page as Page;\n return createTestEngine(page, scenePart);\n}\n\n/**\n * Playwright adapter for the TestFrameworkMapper interface.\n */\nexport const playWrightTestFrameworkMapper: TestFrameworkMapper = {\n /*\n * INTENTIONAL @ts-expect-error comments: Playwright's test functions have different type\n * signatures than the normalized TestFrameworkMapper interface. Playwright uses fixture-based\n * callbacks with destructuring ({ page, browser }) while our interface uses a union type for\n * Jest compatibility (done callback or fixture object). The functions are compatible at runtime\n * but TypeScript cannot verify this due to these fundamental signature differences.\n */\n\n assertEqual: (a, b) => expect(a).toEqual(b),\n assertNotEqual: (a, b) => expect(a).not.toEqual(b),\n assertTrue: value => expect(value).toBe(true),\n assertFalse: value => expect(value).toBe(false),\n assertApproxEqual: (actual, expected, tolerance) =>\n expect(Math.abs(actual - expected)).toBeLessThanOrEqual(tolerance),\n // @ts-expect-error - Playwright describe signature differs from TestFrameworkMapper.Describe\n describe: test.describe,\n\n beforeEach: test.beforeEach,\n afterEach: test.afterEach,\n beforeAll: test.beforeAll,\n afterAll: test.afterAll,\n\n // @ts-expect-error - Playwright test signature differs from TestFrameworkMapper.Test\n test: test,\n\n // @ts-expect-error - Playwright test signature differs from TestFrameworkMapper.Test\n it: test,\n\n hasLayout: true,\n};\n\n/**\n * Get a typed interface for running end-to-end tests with Playwright.\n */\nexport function getTestRunnerInterface<T extends ScenePart>(): E2eTestInterface<T> {\n return {\n getTestEngine: playwrightGetTestEngine,\n goto,\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomic-testing/playwright",
3
- "version": "0.85.0",
3
+ "version": "0.88.0",
4
4
  "description": "Atomic Testing Playwright Adapter",
5
5
  "keywords": [],
6
6
  "license": "MIT",
@@ -25,8 +25,8 @@
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "@atomic-testing/core": "0.85.0",
29
- "@atomic-testing/internal-test-runner": "0.85.0"
28
+ "@atomic-testing/core": "0.88.0",
29
+ "@atomic-testing/internal-test-runner": "0.88.0"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "@playwright/test": ">=1.50.0"
@@ -1,10 +1,12 @@
1
1
  import {
2
2
  BlurOption,
3
+ BoundingRect,
3
4
  byCssSelector,
4
5
  ClickOption,
5
6
  CssProperty,
6
7
  dateUtil,
7
8
  defaultWaitForOption,
9
+ ElementNotFoundError,
8
10
  EnterTextOption,
9
11
  FocusOption,
10
12
  HoverOption,
@@ -19,6 +21,8 @@ import {
19
21
  MouseUpOption,
20
22
  Optional,
21
23
  PartLocator,
24
+ Point,
25
+ PressKeyOption,
22
26
  timingUtil,
23
27
  WaitForOption,
24
28
  WaitUntilOption,
@@ -45,6 +49,102 @@ export class PlaywrightInteractor implements Interactor {
45
49
  await this.page.locator(cssLocator).selectOption(values);
46
50
  }
47
51
 
52
+ /**
53
+ * Set the selected files on a `<input type="file">` element.
54
+ *
55
+ * Playwright's native `setInputFiles` reads the given paths from disk and
56
+ * populates the input's `FileList`, firing the change event — the only way to
57
+ * fill a file input, whose value cannot be set programmatically. Following
58
+ * this layer's convention, no `ElementNotFoundError` is fabricated: a missing
59
+ * element surfaces through Playwright's own auto-wait timeout.
60
+ *
61
+ * @param locator - Locator of the `<input type="file">` element
62
+ * @param files - One or more filesystem paths to upload
63
+ */
64
+ async setInputFiles(locator: PartLocator, files: string | string[]): Promise<void> {
65
+ const cssLocator = await locatorUtil.toCssSelector(locator, this);
66
+ await this.page.locator(cssLocator).setInputFiles(files);
67
+ }
68
+
69
+ /**
70
+ * Scroll the located element into view, no-op if already visible.
71
+ *
72
+ * Delegates to Playwright's `scrollIntoViewIfNeeded`, which performs a real
73
+ * layout-aware scroll in the browser. Per this layer's convention, no
74
+ * `ElementNotFoundError` is fabricated: a missing element surfaces through
75
+ * Playwright's own auto-wait timeout.
76
+ *
77
+ * @param locator - Locator of the element to scroll into view
78
+ */
79
+ async scrollIntoView(locator: PartLocator): Promise<void> {
80
+ const css = await locatorUtil.toCssSelector(locator, this);
81
+ await this.page.locator(css).scrollIntoViewIfNeeded();
82
+ }
83
+
84
+ /**
85
+ * Scroll the located element by the given pixel delta.
86
+ *
87
+ * The scroll is performed by evaluating `el.scrollBy(dx, dy)` on the element
88
+ * itself rather than `page.mouse.wheel`. A wheel event scrolls whatever sits
89
+ * under the pointer and is non-deterministic across chromium/firefox/webkit,
90
+ * whereas evaluating `scrollBy` on the resolved element scrolls exactly that
91
+ * element. This is a deliberate deviation from ADR 0001's per-engine table
92
+ * (which lists `page.mouse.wheel`), taking the alternative the step-5 prompt
93
+ * permits ("or evaluate el.scrollBy") for cross-browser determinism. As with
94
+ * {@link scrollIntoView}, no `ElementNotFoundError` is fabricated; a missing
95
+ * element surfaces through Playwright's own auto-wait timeout.
96
+ *
97
+ * @param locator - Locator of the scrollable element
98
+ * @param delta - Pixel offset to scroll by
99
+ */
100
+ async scrollBy(locator: PartLocator, delta: Point): Promise<void> {
101
+ const css = await locatorUtil.toCssSelector(locator, this);
102
+ await this.page.locator(css).evaluate((el, d) => el.scrollBy(d.x, d.y), { x: delta.x, y: delta.y });
103
+ }
104
+
105
+ /**
106
+ * Drag the source element and drop it onto the target element.
107
+ *
108
+ * Delegates to Playwright's native `Locator.dragTo`, which performs a real,
109
+ * layout-aware drag gesture in the browser. Per this layer's convention, no
110
+ * `ElementNotFoundError` is fabricated: a missing element surfaces through
111
+ * Playwright's own auto-wait timeout.
112
+ *
113
+ * @param source - Locator of the element to drag
114
+ * @param target - Locator of the drop target
115
+ */
116
+ async dragTo(source: PartLocator, target: PartLocator): Promise<void> {
117
+ const srcCss = await locatorUtil.toCssSelector(source, this);
118
+ const tgtCss = await locatorUtil.toCssSelector(target, this);
119
+ await this.page.locator(srcCss).dragTo(this.page.locator(tgtCss));
120
+ }
121
+
122
+ /**
123
+ * Drag the located element by the given pixel delta from its center.
124
+ *
125
+ * The gesture is a single uninterrupted `move → down → move → up` sequence
126
+ * computed from the element's center. It deliberately does NOT reuse
127
+ * {@link mouseMove}/{@link mouseDown} — `mouseMove` resets the pointer with
128
+ * `page.mouse.move(0, 0)` after hovering, which would corrupt the drag path
129
+ * (see ADR 0001, option 5). The center comes from {@link getBoundingRect},
130
+ * which throws `ElementNotFoundError` when the element has no box
131
+ * (detached/invisible) rather than auto-waiting — so this shares that
132
+ * "element not found" contract instead of re-deriving the box + guard here.
133
+ *
134
+ * @param locator - Locator of the element to drag
135
+ * @param delta - Pixel offset to drag by
136
+ * @throws {ElementNotFoundError} If the element has no bounding box
137
+ */
138
+ async drag(locator: PartLocator, delta: Point): Promise<void> {
139
+ const rect = await this.getBoundingRect(locator);
140
+ const cx = rect.x + rect.width / 2;
141
+ const cy = rect.y + rect.height / 2;
142
+ await this.page.mouse.move(cx, cy);
143
+ await this.page.mouse.down();
144
+ await this.page.mouse.move(cx + delta.x, cy + delta.y, { steps: 8 });
145
+ await this.page.mouse.up();
146
+ }
147
+
48
148
  /**
49
149
  * Get the value of an `<input>` element.
50
150
  *
@@ -125,6 +225,21 @@ export class PlaywrightInteractor implements Interactor {
125
225
  await this.page.locator(cssLocator).click({ position: option?.position });
126
226
  }
127
227
 
228
+ /**
229
+ * Dispatch a right-click on the located element to open its context menu.
230
+ *
231
+ * Delegates to Playwright's native right-button click, which fires a real
232
+ * `contextmenu` event in the browser. Per this layer's convention, no
233
+ * `ElementNotFoundError` is fabricated: a missing element surfaces through
234
+ * Playwright's own auto-wait timeout.
235
+ *
236
+ * @param locator - Locator of the element to right-click
237
+ */
238
+ async contextMenu(locator: PartLocator): Promise<void> {
239
+ const css = await locatorUtil.toCssSelector(locator, this);
240
+ await this.page.locator(css).click({ button: 'right' });
241
+ }
242
+
128
243
  async hover(locator: PartLocator, option?: Partial<HoverOption>): Promise<void> {
129
244
  const cssLocator = await locatorUtil.toCssSelector(locator, this);
130
245
  await this.page.locator(cssLocator).hover({ position: option?.position });
@@ -185,6 +300,41 @@ export class PlaywrightInteractor implements Interactor {
185
300
  await this.page.locator(cssLocator).blur();
186
301
  }
187
302
 
303
+ async pressKey(locator: PartLocator, key: string, option?: Partial<PressKeyOption>): Promise<void> {
304
+ const cssLocator = await locatorUtil.toCssSelector(locator, this);
305
+ // Compose Playwright's chord syntax — modifiers joined to the key by `+`, in
306
+ // Playwright's accepted Control+Alt+Shift+Meta order — so the browser holds
307
+ // those modifiers across the keypress and the event carries ctrlKey/etc.
308
+ const modifiers: string[] = [];
309
+ if (option?.ctrl) {
310
+ modifiers.push('Control');
311
+ }
312
+ if (option?.alt) {
313
+ modifiers.push('Alt');
314
+ }
315
+ if (option?.shift) {
316
+ modifiers.push('Shift');
317
+ }
318
+ if (option?.meta) {
319
+ modifiers.push('Meta');
320
+ }
321
+ const chord = modifiers.length > 0 ? `${modifiers.join('+')}+${key}` : key;
322
+ // locator.press auto-focuses the element, then dispatches a real, trusted
323
+ // KeyboardEvent — the browser equivalent of the DOM focus-first keyDown/keyUp.
324
+ // Caveat: for Shift + a printable key the browser case-folds `event.key`
325
+ // (`Shift+a` → `'A'`) whereas the jsdom path keeps `'a'` — only the modifier
326
+ // flags are delivered identically across engines (see #924).
327
+ await this.page.locator(cssLocator).press(chord);
328
+ }
329
+
330
+ async activate(locator: PartLocator): Promise<void> {
331
+ const cssLocator = await locatorUtil.toCssSelector(locator, this);
332
+ // Geometry-free activation mirrors the mouseout dispatch precedent above: it
333
+ // bypasses hit-testing to actuate a covered or zero-size input that
334
+ // locator.click() (a real geometry hit-test) cannot reach.
335
+ await this.page.locator(cssLocator).dispatchEvent('click');
336
+ }
337
+
188
338
  //#region wait conditions
189
339
  wait(ms: number): Promise<void> {
190
340
  return timingUtil.wait(ms);
@@ -233,6 +383,27 @@ export class PlaywrightInteractor implements Interactor {
233
383
  return text ?? undefined;
234
384
  }
235
385
 
386
+ /**
387
+ * Get the located element's bounding rectangle.
388
+ *
389
+ * `boundingBox()` returns `null` for a detached/invisible element rather than
390
+ * auto-waiting, so this is one of the few Playwright methods that throws
391
+ * `ElementNotFoundError` — matching the house "element not found" contract
392
+ * (ADR 0001).
393
+ *
394
+ * @param locator - Locator of the element to measure
395
+ * @returns The element's bounding rectangle in CSS pixels
396
+ * @throws {ElementNotFoundError} If the element has no bounding box
397
+ */
398
+ async getBoundingRect(locator: PartLocator): Promise<BoundingRect> {
399
+ const css = await locatorUtil.toCssSelector(locator, this);
400
+ const box = await this.page.locator(css).boundingBox();
401
+ if (box == null) {
402
+ throw new ElementNotFoundError(locator, 'getBoundingRect');
403
+ }
404
+ return { x: box.x, y: box.y, width: box.width, height: box.height };
405
+ }
406
+
236
407
  async exists(locator: PartLocator): Promise<boolean> {
237
408
  const cssLocator = await locatorUtil.toCssSelector(locator, this);
238
409
  const count = await this.page.locator(cssLocator).count();
@@ -66,6 +66,8 @@ export const playWrightTestFrameworkMapper: TestFrameworkMapper = {
66
66
 
67
67
  // @ts-expect-error - Playwright test signature differs from TestFrameworkMapper.Test
68
68
  it: test,
69
+
70
+ hasLayout: true,
69
71
  };
70
72
 
71
73
  /**