@harnessed-ts/resolve 0.0.0-stage → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/LICENSE +21 -0
  3. package/README.md +106 -2
  4. package/dist/box.d.ts +27 -0
  5. package/dist/box.d.ts.map +1 -0
  6. package/dist/chunk-BYG4HMLR.js +61 -0
  7. package/dist/chunk-BYG4HMLR.js.map +1 -0
  8. package/dist/files.d.ts +7 -0
  9. package/dist/files.d.ts.map +1 -0
  10. package/dist/global-name.d.ts +7 -0
  11. package/dist/global-name.d.ts.map +1 -0
  12. package/dist/index.cjs +315 -0
  13. package/dist/index.cjs.map +1 -0
  14. package/dist/index.d.ts +10 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +296 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/inject.cjs +68 -0
  19. package/dist/inject.cjs.map +1 -0
  20. package/dist/inject.d.ts +27 -0
  21. package/dist/inject.d.ts.map +1 -0
  22. package/dist/inject.global.d.ts +2 -0
  23. package/dist/inject.global.d.ts.map +1 -0
  24. package/dist/inject.global.js +70 -0
  25. package/dist/inject.js +3 -0
  26. package/dist/inject.js.map +1 -0
  27. package/dist/page-api.d.ts +57 -0
  28. package/dist/page-api.d.ts.map +1 -0
  29. package/dist/resolve.d.ts +38 -0
  30. package/dist/resolve.d.ts.map +1 -0
  31. package/dist/visible.d.ts +18 -0
  32. package/dist/visible.d.ts.map +1 -0
  33. package/dist/webdriver-page-script.d.ts +48 -0
  34. package/dist/webdriver-page-script.d.ts.map +1 -0
  35. package/dist/webdriver.cjs +272 -0
  36. package/dist/webdriver.cjs.map +1 -0
  37. package/dist/webdriver.d.ts +96 -0
  38. package/dist/webdriver.d.ts.map +1 -0
  39. package/dist/webdriver.js +226 -0
  40. package/dist/webdriver.js.map +1 -0
  41. package/dist/wire.d.ts +16 -0
  42. package/dist/wire.d.ts.map +1 -0
  43. package/package.json +79 -3
package/CHANGELOG.md ADDED
@@ -0,0 +1,95 @@
1
+ # @harnessed-ts/resolve
2
+
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ec06bdf: Vue, Solid, Svelte and Angular join React and Ember as conformance-tested UI frameworks, and node:test, Bun and Nightwatch join the runners.
8
+
9
+ New packages: `@harnessed-ts/nightwatch` drives Nightwatch 3 through the injected resolver, under Nightwatch's own runner and its Mocha runner, in end-to-end and component tests (`@nightwatch/react`, `@nightwatch/vue`), with cross-origin frames. `@harnessed-ts/angular` drives zoneless Angular 21+: every action settles at `fixture.whenStable()`, or at the `ApplicationRef`'s for an app bootstrapped without TestBed, and `goto()` drives a provided router.
10
+
11
+ `@harnessed-ts/core/register` loads TypeScript harnesses under `node --import`: esbuild strips the types, lowers standard decorators and `accessor`, and compiles JSX. `@harnessed-ts/core/bun` does the same as a `bun test` preload, so harnesses keep the standard decorator protocol whatever `experimentalDecorators` the tsconfig sets.
12
+
13
+ `@harnessed-ts/dom/matchers/bun` registers the matchers with `bun:test`. The dom driver runs on happy-dom too: it focuses the frames leading to an interaction's target, finds a frame's `<iframe>` where `frameElement` is unset, and reads a select's selection off `options`.
14
+
15
+ `@harnessed-ts/resolve/webdriver` holds the WebDriver half of injected resolution, shared by the WebdriverIO and Nightwatch drivers. `@harnessed-ts/gherkin/nightwatch` adapts the cucumber-js world to Nightwatch's Cucumber runner.
16
+
17
+ `@harnessed-ts/playwright`, and `@harnessed-ts/cypress` under `realEvents`, give the page a task after every action, so a read straight after it sees what a framework that renders on a timer (zoneless Angular) has rendered. The injectable resolver installs on the page's `window` rather than the script's global, which under Firefox's WebDriver is a sandbox in front of it.
18
+
19
+ `@harnessed-ts/conformance` exports `fixtureParity()`, the per-view parity check a port of the fixture to another framework runs, naming the first place its markup parts from the React original; it detects a simulated DOM from the DOM's own window.
20
+
21
+ `@harnessed-ts/eslint-plugin` recognises Nightwatch's, Angular's and Vue Test Utils' raw queries, and looks in `nightwatch/` by default. `npx @harnessed-ts/claude install` detects Angular, Nightwatch, node:test and Bun.
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies [ec06bdf]
26
+ - @harnessed-ts/core@0.7.0
27
+
28
+ ## 0.6.0
29
+
30
+ ### Minor Changes
31
+
32
+ - e324e94: ember-test-selectors style test ids: `configure({ testIdAttribute: 'data-test-*' })` makes `testId('x')` match any element that has the attribute `data-test-x`, whatever its value, with the same scoping, strictness, `{ global: true }`, frame and absence rules as before under every driver. A plain attribute name keeps matching by value. A presence id is held to letters, digits, `-`, `_`, `.` and `:`, and anything else (or a `RegExp`) is refused rather than spliced into a selector.
33
+
34
+ `Query.boundingBox()` resolves to `{ x, y, width, height }`: the target's border box in CSS pixels, relative to the top-level viewport, offset by every frame crossed. Strict, and on an absent target it rejects after the timeout, as `text()` does. A node that draws no box reads as all zeros, and so does every node under the dom driver: jsdom computes no layout.
35
+
36
+ `Query.upload(files)` sets the files of an `<input type="file">`, or of the input a `<label>` is for, and fires `input` and `change`, replacing any earlier selection. It refuses any other element, and several files for an input that is not `multiple`. Puppeteer, WebdriverIO and TestCafe take only paths, so there each `File` is written to a temporary directory first: its name and bytes arrive, and the browser infers its type from the extension.
37
+
38
+ `Query` gains two abstract members, so a driver outside this repo implements both to keep compiling; the conformance catalog pins all three changes.
39
+
40
+ `@harnessed-ts/eslint-plugin` supports ESLint 8.57 and legacy `.eslintrc`: the peer range is now `>=8.57`, `legacy-recommended` and `legacy-strict` name the rules under `@harnessed-ts/`, and the CJS build exposes `rules` and `configs` at the top level, where eslintrc reads them.
41
+
42
+ `npx @harnessed-ts/claude install` detects ember-test-selectors attributes (in `.gts`, `.gjs` and `.hbs` files too) and writes `testIdAttribute: 'data-test-*'`.
43
+
44
+ `@harnessed-ts/storybook` writes and finds its story-ready marker under a `data-test-*` config, where `StoryPage` waits for it.
45
+
46
+ ### Patch Changes
47
+
48
+ - Updated dependencies [e324e94]
49
+ - @harnessed-ts/core@0.6.0
50
+
51
+ ## 0.5.0
52
+
53
+ ### Patch Changes
54
+
55
+ - @harnessed-ts/core@0.5.0
56
+
57
+ ## 0.4.0
58
+
59
+ ### Minor Changes
60
+
61
+ - 9666a3d: New package `@harnessed-ts/resolve`: the selector resolver the Testing Library driver has always used, extracted so every driver that can run JavaScript in the page shares it — role with `level`, strictness, absence, and frame semantics come from one implementation. Adds non-waiting forms (`resolveOneNow`, `resolveAllNow`) for runtimes that retry on their own, and `@harnessed-ts/resolve/inject`, a self-contained build a remote driver (WebdriverIO, Puppeteer, TestCafe) injects into the page, with `encodeSelector` to carry `RegExp`s across the wire.
62
+
63
+ `@harnessed-ts/dom` now depends on it. No behaviour change.
64
+
65
+ - 9666a3d: Add `isVisibleInLayout(element, home?)`, Playwright's visibility rule — a non-empty box that `visibility` does not hide, inside frames that are visible too — for in-page drivers running in a real browser. The injected page API answers it too, as `visible(element)`, for drivers that resolve from outside the page. The main entry no longer imports Node's `url` and `path` through a chunk shared with `/inject`, so it loads in a browser.
66
+ - 1823546: `isVisible()`, `waitFor('visible')` and `waitFor('hidden')` on a target matching
67
+ several nodes now reject with the shared strict-mode violation, at once, under
68
+ every driver. The dom, Ember, Vitest browser mode and Cypress drivers answered
69
+ `false` from `isVisible()`, waited out the timeout in `waitFor('visible')`, and —
70
+ worse — resolved `waitFor('hidden')` at once, passing a wait for three rendered
71
+ nodes to disappear; Playwright's waits raised its own wording instead of the
72
+ shared one. An `nth()`
73
+ past the last match answers `false` from `isVisible()` and counts as hidden, as it
74
+ does under Playwright — Puppeteer and TestCafe used to reject it as an index out
75
+ of range.
76
+
77
+ Core exports `StrictModeViolation`, the class `strictViolation()` now returns, so a
78
+ driver can tell ambiguity apart from absence. It is the class a caller catches
79
+ under every driver: core also exports `reviveStrictViolation()`, which the
80
+ Puppeteer, WebdriverIO and TestCafe drivers apply to what the page throws, so
81
+ a violation raised in the page arrives as core's class, worded exactly as an
82
+ in-process driver words it. `instanceof StrictModeViolation` checks a brand
83
+ rather than the prototype, so it holds across two copies of core in one page —
84
+ Cypress bundles the support file and each spec separately.
85
+
86
+ `WaitState` now documents the rule it is held to: a state joins only when every
87
+ driver honours it with the same meaning, so a runner's own states — Playwright's
88
+ `attached` and `detached` — stay out of the shared interface.
89
+
90
+ ### Patch Changes
91
+
92
+ - Updated dependencies [9666a3d]
93
+ - Updated dependencies [9666a3d]
94
+ - Updated dependencies [1823546]
95
+ - @harnessed-ts/core@0.4.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joe Gaudet, Jay Seo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,107 @@
1
- # Temporary Holding Version
1
+ # @harnessed-ts/resolve
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The selector resolver behind every Testing Library-family driver for
4
+ `@harnessed-ts/core`. It turns a scope chain plus a `Selector` into DOM nodes with
5
+ the cross-driver guarantees built in: `role` honours `level`, a single-target
6
+ lookup is strict, absence answers at once, and only `frame()` crosses into an
7
+ `<iframe>`.
8
+
9
+ You do not install this to write harnesses — a driver depends on it. Install it
10
+ when you are **writing a driver**.
11
+
12
+ ## In-process drivers
13
+
14
+ A driver that shares a realm with the page (jsdom, Ember's test container,
15
+ Cypress's AUT, Vitest browser mode) calls the resolver directly:
16
+
17
+ ```ts
18
+ import { countAll, resolveOne } from '@harnessed-ts/resolve'
19
+
20
+ const element = await resolveOne(container, scope, selector, timeout)
21
+ const total = await countAll(container, scope, selector)
22
+ ```
23
+
24
+ `resolveOneNow`, `resolveAllNow` and `resolveScopeNow` are the non-waiting forms,
25
+ for a runtime that does its own retrying: "not there yet" is `null`, while
26
+ strictness and frame errors still throw.
27
+
28
+ A driver running in a real browser answers `isVisible()` with
29
+ `isVisibleInLayout(element, home)` — Playwright's rule: a non-empty box that
30
+ `visibility` does not hide, inside frames that are visible too, crossed up to
31
+ `home`. It answers `boundingBox()` with `boundingBoxOf(element, home)`, which adds
32
+ each crossed frame's `frameOrigin()`, and finds an upload's input with
33
+ `fileInputOf(element)` — the element, or a `<label>`'s control — before handing
34
+ the result to core's `checkFileTarget()`.
35
+
36
+ A remote driver asks the same of the page through `box`, `origin` and
37
+ `fileTarget`, and adds the origin of each frame it entered itself.
38
+ `filesOnDisk(files)` from `@harnessed-ts/resolve/inject` writes `File`s to a
39
+ temporary directory for a browser that only takes paths.
40
+
41
+ ## Remote drivers
42
+
43
+ A driver whose test code runs in Node while the page runs elsewhere (WebdriverIO,
44
+ Puppeteer, TestCafe) injects a self-contained build and calls it through the page:
45
+
46
+ ```ts
47
+ import { encodeSelector, injectSource, PAGE_API_GLOBAL } from '@harnessed-ts/resolve/inject'
48
+
49
+ await page.evaluate(injectSource())
50
+ const handle = await page.evaluateHandle(
51
+ (name, scope, selector, options) => window[name].one(null, scope, selector, options),
52
+ PAGE_API_GLOBAL,
53
+ scope.map(encodeSelector),
54
+ encodeSelector(selector),
55
+ { testIdAttribute: 'data-testid', timeout: 5000 },
56
+ )
57
+ ```
58
+
59
+ `encodeSelector` is required: a `RegExp` does not survive WebDriver, CDP or
60
+ TestCafe serialisation, and the page API decodes the tagged form back. The
61
+ runtime config travels with each call because the page cannot see the test
62
+ process's `configure()`. `injectPath()` gives the file's path for tools that inject
63
+ by path.
64
+
65
+ An error the page throws comes back as a plain error: WebDriver, CDP and TestCafe
66
+ keep its message and, at most, its name. Pass it through `reviveStrictViolation()`
67
+ from `@harnessed-ts/core` so a strict-mode violation is `StrictModeViolation`
68
+ again, worded exactly as an in-process driver words it — that is how a caller
69
+ tells ambiguity apart from absence.
70
+
71
+ ## WebDriver drivers
72
+
73
+ A WebDriver client (WebdriverIO, selenium-webdriver under Nightwatch) shares more
74
+ than injection: the page script that resolves and reads in one round-trip, and
75
+ the walk that enters each frame of a scope chain from outside the page.
76
+ `@harnessed-ts/resolve/webdriver` holds both, behind a small `WebDriverLike`
77
+ adapter — run the page script in a document, inject the resolver, wrap an
78
+ element, enter a frame, leave frames:
79
+
80
+ ```ts
81
+ import { callPage, classicScript, walk } from '@harnessed-ts/resolve/webdriver'
82
+ import type { WebDriverLike, WebDriverSite } from '@harnessed-ts/resolve/webdriver'
83
+
84
+ const client: WebDriverLike<WebDriverSite, WebElement> = {
85
+ top: () => ({ top: true, origin: { x: 0, y: 0 } }),
86
+ run: (_site, args) => driver.executeScript(classicScript(), ...args),
87
+ inject: async (_site, source) => void (await driver.executeScript(source)),
88
+ element: async (_site, ref) => ref as unknown as WebElement,
89
+ async enterFrame(_site, iframe, origin) {
90
+ await driver.switchTo().frame(iframe)
91
+ return { top: false, origin }
92
+ },
93
+ leaveFrames: () => driver.switchTo().defaultContent(),
94
+ }
95
+
96
+ const text = await walk(client, scope, { waiting: true, timeout }, (site, rest, remaining) =>
97
+ callPage(client, site, 'text', rest, selector, remaining()),
98
+ )
99
+ ```
100
+
101
+ Errors the page raises come back as values, so a strict violation arrives as
102
+ core's `StrictModeViolation` and a frame that cannot be entered as a
103
+ `PageError` (`isFrameEntryError`).
104
+
105
+ Full guide: the [harnessed README](https://github.com/joegaudet/harnessed#readme).
106
+
107
+ MIT © Joe Gaudet, Jay Seo
package/dist/box.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ import type { BoundingBox } from '@harnessed-ts/core';
2
+ /** A point in a document's viewport, in CSS pixels. */
3
+ export interface Point {
4
+ x: number;
5
+ y: number;
6
+ }
7
+ /**
8
+ * An element's border box in its own document's viewport, or `null` when it
9
+ * renders no box at all (`display: none`, or anything inside it). Every driver
10
+ * reads `null` as all zeros, which is what Playwright and Puppeteer — whose
11
+ * native boxes are `null` there — are normalised to.
12
+ */
13
+ export declare function boxInOwnDocument(element: Element): BoundingBox | null;
14
+ /**
15
+ * Where an iframe's document is drawn, in the iframe's own document: the corner
16
+ * of its content box, inside its border and padding. The same offset Playwright
17
+ * and Puppeteer add when they report a framed element's box.
18
+ */
19
+ export declare function frameOrigin(iframe: Element): Point;
20
+ /**
21
+ * An element's box relative to the viewport of `home` — the document the
22
+ * driver drives — adding the origin of every same-origin frame crossed on the
23
+ * way up. For drivers that resolve inside the page; under jsdom everything is
24
+ * zeros, because jsdom computes no layout.
25
+ */
26
+ export declare function boundingBoxOf(element: Element, home?: Document): BoundingBox;
27
+ //# sourceMappingURL=box.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"box.d.ts","sourceRoot":"","sources":["../src/box.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAErD,uDAAuD;AACvD,MAAM,WAAW,KAAK;IACpB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAIrE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,KAAK,CAQlD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,WAAW,CAc5E"}
@@ -0,0 +1,61 @@
1
+ import { readFileSync, mkdtempSync, rmSync, mkdirSync, writeFileSync } from 'fs';
2
+ import { tmpdir } from 'os';
3
+ import { join, dirname, basename } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ // src/inject.ts
7
+
8
+ // src/global-name.ts
9
+ var PAGE_API_GLOBAL = "__harnessedResolve";
10
+
11
+ // src/wire.ts
12
+ function isRegExp(value) {
13
+ return Object.prototype.toString.call(value) === "[object RegExp]";
14
+ }
15
+ function encodeValue(value) {
16
+ if (isRegExp(value)) return { $regexp: value.source, flags: value.flags };
17
+ if (Array.isArray(value)) return value.map(encodeValue);
18
+ if (typeof value === "object" && value !== null) {
19
+ return Object.fromEntries(
20
+ Object.entries(value).map(([key, inner]) => [key, encodeValue(inner)])
21
+ );
22
+ }
23
+ return value;
24
+ }
25
+ function encodeSelector(selector) {
26
+ return encodeValue(selector);
27
+ }
28
+
29
+ // src/inject.ts
30
+ function injectPath() {
31
+ if (typeof __filename === "string") return join(dirname(__filename), "inject.global.js");
32
+ return fileURLToPath(new URL("./inject.global.js", import.meta.url));
33
+ }
34
+ var cached;
35
+ function injectSource() {
36
+ if (cached === void 0) cached = readFileSync(injectPath(), "utf8");
37
+ return cached;
38
+ }
39
+ var uploads;
40
+ var written = 0;
41
+ async function filesOnDisk(files) {
42
+ if (uploads === void 0) {
43
+ const root2 = mkdtempSync(join(tmpdir(), "harnessed-uploads-"));
44
+ uploads = root2;
45
+ process.once("exit", () => rmSync(root2, { recursive: true, force: true }));
46
+ }
47
+ const root = uploads;
48
+ return Promise.all(
49
+ files.map(async (file) => {
50
+ const dir = join(root, String(written++));
51
+ mkdirSync(dir);
52
+ const path = join(dir, basename(file.name) || "upload");
53
+ writeFileSync(path, new Uint8Array(await file.arrayBuffer()));
54
+ return path;
55
+ })
56
+ );
57
+ }
58
+
59
+ export { PAGE_API_GLOBAL, encodeSelector, filesOnDisk, injectPath, injectSource };
60
+ //# sourceMappingURL=chunk-BYG4HMLR.js.map
61
+ //# sourceMappingURL=chunk-BYG4HMLR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/global-name.ts","../src/wire.ts","../src/inject.ts"],"names":["root"],"mappings":";;;;;;;;AAKO,IAAM,eAAA,GAAkB;;;ACe/B,SAAS,SAAS,KAAA,EAAiC;AACjD,EAAA,OAAO,MAAA,CAAO,SAAA,CAAU,QAAA,CAAS,IAAA,CAAK,KAAK,CAAA,KAAM,iBAAA;AACnD;AAEA,SAAS,YAAY,KAAA,EAAyB;AAC5C,EAAA,IAAI,QAAA,CAAS,KAAK,CAAA,EAAG,OAAO,EAAE,SAAS,KAAA,CAAM,MAAA,EAAQ,KAAA,EAAO,KAAA,CAAM,KAAA,EAAM;AACxE,EAAA,IAAI,MAAM,OAAA,CAAQ,KAAK,GAAG,OAAO,KAAA,CAAM,IAAI,WAAW,CAAA;AACtD,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA,EAAM;AAC/C,IAAA,OAAO,MAAA,CAAO,WAAA;AAAA,MACZ,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,CAAE,IAAI,CAAC,CAAC,GAAA,EAAK,KAAK,MAAM,CAAC,GAAA,EAAK,WAAA,CAAY,KAAK,CAAC,CAAC;AAAA,KACvE;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT;AAuBO,SAAS,eAAe,QAAA,EAAkC;AAC/D,EAAA,OAAO,YAAY,QAAQ,CAAA;AAC7B;;;ACxCO,SAAS,UAAA,GAAqB;AAGnC,EAAA,IAAI,OAAO,eAAe,QAAA,EAAU,OAAO,KAAK,OAAA,CAAQ,UAAU,GAAG,kBAAkB,CAAA;AACvF,EAAA,OAAO,cAAc,IAAI,GAAA,CAAI,oBAAA,EAAsB,MAAA,CAAA,IAAA,CAAY,GAAG,CAAC,CAAA;AACrE;AAEA,IAAI,MAAA;AAMG,SAAS,YAAA,GAAuB;AACrC,EAAA,IAAI,WAAW,MAAA,EAAW,MAAA,GAAS,YAAA,CAAa,UAAA,IAAc,MAAM,CAAA;AACpE,EAAA,OAAO,MAAA;AACT;AAEA,IAAI,OAAA;AACJ,IAAI,OAAA,GAAU,CAAA;AAad,eAAsB,YAAY,KAAA,EAA2C;AAC3E,EAAA,IAAI,YAAY,MAAA,EAAW;AACzB,IAAA,MAAMA,QAAO,WAAA,CAAY,IAAA,CAAK,MAAA,EAAO,EAAG,oBAAoB,CAAC,CAAA;AAC7D,IAAA,OAAA,GAAUA,KAAAA;AACV,IAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,MAAM,MAAA,CAAOA,KAAAA,EAAM,EAAE,SAAA,EAAW,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,CAAC,CAAA;AAAA,EAC3E;AACA,EAAA,MAAM,IAAA,GAAO,OAAA;AACb,EAAA,OAAO,OAAA,CAAQ,GAAA;AAAA,IACb,KAAA,CAAM,GAAA,CAAI,OAAM,IAAA,KAAQ;AACtB,MAAA,MAAM,GAAA,GAAM,IAAA,CAAK,IAAA,EAAM,MAAA,CAAO,SAAS,CAAC,CAAA;AACxC,MAAA,SAAA,CAAU,GAAG,CAAA;AAEb,MAAA,MAAM,OAAO,IAAA,CAAK,GAAA,EAAK,SAAS,IAAA,CAAK,IAAI,KAAK,QAAQ,CAAA;AACtD,MAAA,aAAA,CAAc,MAAM,IAAI,UAAA,CAAW,MAAM,IAAA,CAAK,WAAA,EAAa,CAAC,CAAA;AAC5D,MAAA,OAAO,IAAA;AAAA,IACT,CAAC;AAAA,GACH;AACF","file":"chunk-BYG4HMLR.js","sourcesContent":["/**\n * Where the injected build installs itself on the page's `window`. A leaf\n * module with no imports, so the Node-side `inject` entry can name it without\n * pulling in the resolver and Testing Library, which only run in the page.\n */\nexport const PAGE_API_GLOBAL = '__harnessedResolve'\n","import type { Selector } from '@harnessed-ts/core'\n\n/**\n * A `Selector` as it crosses into a remote page. Everything in one is JSON-safe\n * except a RegExp — `role('button', { name: /save/i })` — which WebDriver,\n * CDP and TestCafe all serialise to `{}`. This encodes them as tagged objects\n * on the way out, and the page API decodes them on the way in.\n */\nexport interface WireRegExp {\n $regexp: string\n flags: string\n}\n\nexport type WireSelector = Record<string, unknown>\n\n/**\n * Not `instanceof`: a selector built in the test process and handed to a page\n * in the same JS engine (jsdom, a same-origin frame) carries the other realm's\n * RegExp, which `instanceof` does not recognise.\n */\nfunction isRegExp(value: unknown): value is RegExp {\n return Object.prototype.toString.call(value) === '[object RegExp]'\n}\n\nfunction encodeValue(value: unknown): unknown {\n if (isRegExp(value)) return { $regexp: value.source, flags: value.flags }\n if (Array.isArray(value)) return value.map(encodeValue)\n if (typeof value === 'object' && value !== null) {\n return Object.fromEntries(\n Object.entries(value).map(([key, inner]) => [key, encodeValue(inner)]),\n )\n }\n return value\n}\n\nfunction isWireRegExp(value: unknown): value is WireRegExp {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as WireRegExp).$regexp === 'string' &&\n typeof (value as WireRegExp).flags === 'string'\n )\n}\n\nfunction decodeValue(value: unknown): unknown {\n if (isRegExp(value)) return new RegExp(value.source, value.flags)\n if (isWireRegExp(value)) return new RegExp(value.$regexp, value.flags)\n if (Array.isArray(value)) return value.map(decodeValue)\n if (typeof value === 'object' && value !== null) {\n return Object.fromEntries(\n Object.entries(value).map(([key, inner]) => [key, decodeValue(inner)]),\n )\n }\n return value\n}\n\nexport function encodeSelector(selector: Selector): WireSelector {\n return encodeValue(selector) as WireSelector\n}\n\n/** Idempotent: a selector that never left the process decodes to itself. */\nexport function decodeSelector(selector: Selector | WireSelector): Selector {\n return decodeValue(selector) as Selector\n}\n","import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { basename, dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n// Leaf modules only: this entry runs in the test process, where the resolver\n// and Testing Library have no business loading.\nexport { PAGE_API_GLOBAL } from './global-name'\nexport { encodeSelector } from './wire'\nexport type { WireSelector } from './wire'\nexport type { PageApi, PageApiOptions } from './page-api'\n\ndeclare const __filename: string | undefined\n\n/**\n * Absolute path to the injectable build, for drivers that inject by path\n * (TestCafe's `clientScripts`, Puppeteer's `addScriptTag`).\n */\nexport function injectPath(): string {\n // A sibling in dist/. The CJS build has __filename; the ESM build has\n // import.meta.url and no __filename, so each takes its own branch.\n if (typeof __filename === 'string') return join(dirname(__filename), 'inject.global.js')\n return fileURLToPath(new URL('./inject.global.js', import.meta.url))\n}\n\nlet cached: string | undefined\n\n/**\n * The injectable build's source, for drivers that inject by evaluating a string\n * (WebdriverIO's `execute`, Puppeteer's `evaluate`). Read once per process.\n */\nexport function injectSource(): string {\n if (cached === undefined) cached = readFileSync(injectPath(), 'utf8')\n return cached\n}\n\nlet uploads: string | undefined\nlet written = 0\n\n/**\n * Writes files to disk for a driver whose browser only takes file paths\n * (Puppeteer's `uploadFile`, WebDriver's file input, TestCafe's\n * `setFilesToUpload`), and returns the paths in order.\n *\n * Each file gets a directory of its own, so its name survives as the path's\n * last segment even when two files share one. The browser reads the file when\n * the page does — after the upload resolves — so nothing is deleted until the\n * process exits. What travels is the name and the bytes: the browser infers\n * `type` from the extension, and `lastModified` is the time of writing.\n */\nexport async function filesOnDisk(files: readonly File[]): Promise<string[]> {\n if (uploads === undefined) {\n const root = mkdtempSync(join(tmpdir(), 'harnessed-uploads-'))\n uploads = root\n process.once('exit', () => rmSync(root, { recursive: true, force: true }))\n }\n const root = uploads\n return Promise.all(\n files.map(async file => {\n const dir = join(root, String(written++))\n mkdirSync(dir)\n // basename: a name is a name, never a path out of the directory.\n const path = join(dir, basename(file.name) || 'upload')\n writeFileSync(path, new Uint8Array(await file.arrayBuffer()))\n return path\n }),\n )\n}\n"]}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The file input an upload sets: the element itself, or the control of a
3
+ * `<label>` — user-event's and Playwright's rule both. Anything else comes back
4
+ * described as markup, for core's `checkFileTarget` to refuse in one wording.
5
+ */
6
+ export declare function fileInputOf(element: Element): HTMLInputElement | string;
7
+ //# sourceMappingURL=files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,gBAAgB,GAAG,MAAM,CAOvE"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Where the injected build installs itself on the page's `window`. A leaf
3
+ * module with no imports, so the Node-side `inject` entry can name it without
4
+ * pulling in the resolver and Testing Library, which only run in the page.
5
+ */
6
+ export declare const PAGE_API_GLOBAL = "__harnessedResolve";
7
+ //# sourceMappingURL=global-name.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global-name.d.ts","sourceRoot":"","sources":["../src/global-name.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,eAAe,uBAAuB,CAAA"}