@harnessed-ts/resolve 0.6.0 → 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @harnessed-ts/resolve
2
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
+
3
28
  ## 0.6.0
4
29
 
5
30
  ### Minor Changes
package/README.md CHANGED
@@ -68,6 +68,40 @@ from `@harnessed-ts/core` so a strict-mode violation is `StrictModeViolation`
68
68
  again, worded exactly as an in-process driver words it — that is how a caller
69
69
  tells ambiguity apart from absence.
70
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
+
71
105
  Full guide: the [harnessed README](https://github.com/joegaudet/harnessed#readme).
72
106
 
73
107
  MIT © Joe Gaudet, Jay Seo
@@ -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"]}
@@ -55,7 +55,7 @@ Unable to find an `+(l===!1?"accessible ":"")+'element with the role "'+t+'"'+c+
55
55
  `+u).trim()},PC=W(Co,Co.name,"queryAll"),dt=ee(Co,RC,CC),wC=dt[0],xC=dt[1],TC=dt[2],OC=dt[3],AC=dt[4],Mo=()=>A().testIdAttribute,Po=function(){for(var e=arguments.length,t=new Array(e),r=0;r<e;r++)t[r]=arguments[r];return Z(t[0]),Ma(Mo(),...t)},SC=(e,t)=>"Found multiple elements by: ["+Mo()+'="'+t+'"]',MC=(e,t)=>"Unable to find an element by: ["+Mo()+'="'+t+'"]',IC=W(Po,Po.name,"queryAll"),ct=ee(Po,SC,MC),NC=ct[0],BC=ct[1],LC=ct[2],jC=ct[3],FC=ct[4],wo=Object.freeze({__proto__:null,queryAllByLabelText:IR,queryByLabelText:TR,getAllByLabelText:SR,getByLabelText:MR,findAllByLabelText:OR,findByLabelText:AR,queryByPlaceholderText:jR,queryAllByPlaceholderText:LR,getByPlaceholderText:kR,getAllByPlaceholderText:FR,findAllByPlaceholderText:HR,findByPlaceholderText:DR,queryByText:WR,queryAllByText:VR,getByText:GR,getAllByText:zR,findAllByText:QR,findByText:XR,queryByDisplayValue:ZR,queryAllByDisplayValue:YR,getByDisplayValue:tC,getAllByDisplayValue:eC,findAllByDisplayValue:rC,findByDisplayValue:nC,queryByAltText:sC,queryAllByAltText:iC,getByAltText:dC,getAllByAltText:uC,findAllByAltText:cC,findByAltText:pC,queryByTitle:yC,queryAllByTitle:vC,getByTitle:gC,getAllByTitle:hC,findAllByTitle:qC,findByTitle:_C,queryByRole:wC,queryAllByRole:PC,getAllByRole:xC,getByRole:TC,findAllByRole:OC,findByRole:AC,queryByTestId:NC,queryAllByTestId:IC,getByTestId:LC,getAllByTestId:BC,findAllByTestId:jC,findByTestId:FC});function Ia(e,t,r){return t===void 0&&(t=wo),r===void 0&&(r={}),Object.keys(t).reduce((n,a)=>{let l=t[a];return n[a]=l.bind(null,e),n},r)}var $d={copy:{EventType:"ClipboardEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},cut:{EventType:"ClipboardEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},paste:{EventType:"ClipboardEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},compositionEnd:{EventType:"CompositionEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},compositionStart:{EventType:"CompositionEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},compositionUpdate:{EventType:"CompositionEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},keyDown:{EventType:"KeyboardEvent",defaultInit:{bubbles:!0,cancelable:!0,charCode:0,composed:!0}},keyPress:{EventType:"KeyboardEvent",defaultInit:{bubbles:!0,cancelable:!0,charCode:0,composed:!0}},keyUp:{EventType:"KeyboardEvent",defaultInit:{bubbles:!0,cancelable:!0,charCode:0,composed:!0}},focus:{EventType:"FocusEvent",defaultInit:{bubbles:!1,cancelable:!1,composed:!0}},blur:{EventType:"FocusEvent",defaultInit:{bubbles:!1,cancelable:!1,composed:!0}},focusIn:{EventType:"FocusEvent",defaultInit:{bubbles:!0,cancelable:!1,composed:!0}},focusOut:{EventType:"FocusEvent",defaultInit:{bubbles:!0,cancelable:!1,composed:!0}},change:{EventType:"Event",defaultInit:{bubbles:!0,cancelable:!1}},input:{EventType:"InputEvent",defaultInit:{bubbles:!0,cancelable:!1,composed:!0}},invalid:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!0}},submit:{EventType:"Event",defaultInit:{bubbles:!0,cancelable:!0}},reset:{EventType:"Event",defaultInit:{bubbles:!0,cancelable:!0}},click:{EventType:"MouseEvent",defaultInit:{bubbles:!0,cancelable:!0,button:0,composed:!0}},contextMenu:{EventType:"MouseEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},dblClick:{EventType:"MouseEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},drag:{EventType:"DragEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},dragEnd:{EventType:"DragEvent",defaultInit:{bubbles:!0,cancelable:!1,composed:!0}},dragEnter:{EventType:"DragEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},dragExit:{EventType:"DragEvent",defaultInit:{bubbles:!0,cancelable:!1,composed:!0}},dragLeave:{EventType:"DragEvent",defaultInit:{bubbles:!0,cancelable:!1,composed:!0}},dragOver:{EventType:"DragEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},dragStart:{EventType:"DragEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},drop:{EventType:"DragEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},mouseDown:{EventType:"MouseEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},mouseEnter:{EventType:"MouseEvent",defaultInit:{bubbles:!1,cancelable:!1,composed:!0}},mouseLeave:{EventType:"MouseEvent",defaultInit:{bubbles:!1,cancelable:!1,composed:!0}},mouseMove:{EventType:"MouseEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},mouseOut:{EventType:"MouseEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},mouseOver:{EventType:"MouseEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},mouseUp:{EventType:"MouseEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},select:{EventType:"Event",defaultInit:{bubbles:!0,cancelable:!1}},touchCancel:{EventType:"TouchEvent",defaultInit:{bubbles:!0,cancelable:!1,composed:!0}},touchEnd:{EventType:"TouchEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},touchMove:{EventType:"TouchEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},touchStart:{EventType:"TouchEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},resize:{EventType:"UIEvent",defaultInit:{bubbles:!1,cancelable:!1}},scroll:{EventType:"UIEvent",defaultInit:{bubbles:!1,cancelable:!1}},wheel:{EventType:"WheelEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},abort:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},canPlay:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},canPlayThrough:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},durationChange:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},emptied:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},encrypted:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},ended:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},loadedData:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},loadedMetadata:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},loadStart:{EventType:"ProgressEvent",defaultInit:{bubbles:!1,cancelable:!1}},pause:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},play:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},playing:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},progress:{EventType:"ProgressEvent",defaultInit:{bubbles:!1,cancelable:!1}},rateChange:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},seeked:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},seeking:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},stalled:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},suspend:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},timeUpdate:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},volumeChange:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},waiting:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},load:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},error:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},animationStart:{EventType:"AnimationEvent",defaultInit:{bubbles:!0,cancelable:!1}},animationEnd:{EventType:"AnimationEvent",defaultInit:{bubbles:!0,cancelable:!1}},animationIteration:{EventType:"AnimationEvent",defaultInit:{bubbles:!0,cancelable:!1}},transitionCancel:{EventType:"TransitionEvent",defaultInit:{bubbles:!0,cancelable:!1}},transitionEnd:{EventType:"TransitionEvent",defaultInit:{bubbles:!0,cancelable:!0}},transitionRun:{EventType:"TransitionEvent",defaultInit:{bubbles:!0,cancelable:!1}},transitionStart:{EventType:"TransitionEvent",defaultInit:{bubbles:!0,cancelable:!1}},pointerOver:{EventType:"PointerEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},pointerEnter:{EventType:"PointerEvent",defaultInit:{bubbles:!1,cancelable:!1}},pointerDown:{EventType:"PointerEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},pointerMove:{EventType:"PointerEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},pointerUp:{EventType:"PointerEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},pointerCancel:{EventType:"PointerEvent",defaultInit:{bubbles:!0,cancelable:!1,composed:!0}},pointerOut:{EventType:"PointerEvent",defaultInit:{bubbles:!0,cancelable:!0,composed:!0}},pointerLeave:{EventType:"PointerEvent",defaultInit:{bubbles:!1,cancelable:!1}},gotPointerCapture:{EventType:"PointerEvent",defaultInit:{bubbles:!0,cancelable:!1,composed:!0}},lostPointerCapture:{EventType:"PointerEvent",defaultInit:{bubbles:!0,cancelable:!1,composed:!0}},popState:{EventType:"PopStateEvent",defaultInit:{bubbles:!0,cancelable:!1}},offline:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},online:{EventType:"Event",defaultInit:{bubbles:!1,cancelable:!1}},pageHide:{EventType:"PageTransitionEvent",defaultInit:{bubbles:!0,cancelable:!0}},pageShow:{EventType:"PageTransitionEvent",defaultInit:{bubbles:!0,cancelable:!0}}},Ud={doubleClick:"dblClick"},kC=["value","files"],HC=["bubbles","cancelable","detail"];function Oa(e,t){return A().eventWrapper(()=>{if(!t)throw new Error("Unable to fire an event - please provide an event object.");if(!e)throw new Error('Unable to fire a "'+t.type+'" event - please provide a DOM element.');return e.dispatchEvent(t)})}function vo(e,t,r,n){let a=n===void 0?{}:n,l=a.EventType,o=l===void 0?"Event":l,i=a.defaultInit,s=i===void 0?{}:i;if(!t)throw new Error('Unable to fire a "'+e+'" event - please provide a DOM element.');let u={...s,...r},c=u.target,f=c===void 0?{}:c,h=f.value,_=f.files,C=bt(f,kC);h!==void 0&&DC(t,h),_!==void 0&&Object.defineProperty(t,"files",{configurable:!0,enumerable:!0,writable:!0,value:_}),Object.assign(t,C);let E=ec(t),R=E[o]||E.Event,p;if(typeof R=="function")p=new R(e,u);else{p=E.document.createEvent(o);let g=u.bubbles,m=u.cancelable,b=u.detail,w=bt(u,HC);p.initEvent(e,g,m,b),Object.keys(w).forEach(q=>{p[q]=w[q]})}return["dataTransfer","clipboardData"].forEach(g=>{let m=u[g];typeof m=="object"&&(typeof E.DataTransfer=="function"?Object.defineProperty(p,g,{value:Object.getOwnPropertyNames(m).reduce((b,w)=>(Object.defineProperty(b,w,{value:m[w]}),b),new E.DataTransfer)}):Object.defineProperty(p,g,{value:m}))}),p}Object.keys($d).forEach(e=>{let t=$d[e],r=t.EventType,n=t.defaultInit,a=e.toLowerCase();vo[e]=(l,o)=>vo(a,l,o,{EventType:r,defaultInit:n}),Oa[e]=(l,o)=>Oa(l,vo[e](l,o))});function DC(e,t){let r=Object.getOwnPropertyDescriptor(e,"value")||{},n=r.set,a=Object.getPrototypeOf(e),l=Object.getOwnPropertyDescriptor(a,"value")||{},o=l.set;if(o&&n!==o)o.call(e,t);else if(n)n.call(e,t);else throw new Error("The given element does not have a value setter")}Object.keys(Ud).forEach(e=>{let t=Ud[e];Oa[e]=function(){return Oa[t](...arguments)}});function $C(e){return e.replace(/[ \t]*[\n][ \t]*/g,`
56
56
  `)}function UC(e){return Wd.default.compressToEncodedURIComponent($C(e))}function VC(e){return"https://testing-playground.com/#markup="+UC(e)}var WC=(e,t,r)=>Array.isArray(e)?e.forEach(n=>kd(n,t,r)):kd(e,t,r),zC=function(e){if(e===void 0&&(e=Oo().body),!e||!("innerHTML"in e)){console.log("The element you're providing isn't a valid DOM element.");return}if(!e.innerHTML){console.log("The provided element doesn't have any children.");return}let t=VC(e.innerHTML);return console.log(`Open this URL in your browser
57
57
 
58
- `+t),t},Vd={debug:WC,logTestingPlaygroundURL:zC},NT=typeof document<"u"&&document.body?Ia(document.body,wo,Vd):Object.keys(wo).reduce((e,t)=>(e[t]=()=>{throw new TypeError("For queries bound to document.body a global document has to be available... Learn more: https://testing-library.com/s/screen-global-error")},e),Vd);var Io=Xo(e=>tc({testIdAttribute:e}));function mc(e,t){return[...e.querySelectorAll(t)]}var[,,,GC]=ee(mc,(e,t)=>`Found multiple elements by: ${t}`,(e,t)=>`Unable to find an element by: ${t}`);function bc(e){return Qo(mt().testIdAttribute,e.testId)}function Me(e,t){Io();let r=Ia(e);switch(t.type){case"role":return r.queryAllByRole(t.role,t.options);case"label":return r.queryAllByLabelText(t.text);case"testId":{let n=bc(t);return n===void 0?r.queryAllByTestId(t.testId):mc(e,n)}case"text":return r.queryAllByText(t.text);case"placeholder":return r.queryAllByPlaceholderText(t.text)}}async function vc(e,t,r,n){Io();let a=Me(e,r),l=a.length>0?a:await QC(e,r,n);return No(l,t,r)}function No(e,t,r){if(r.nth!==void 0){let n=e[r.nth];if(n===void 0)throw Uo(r.nth,e.length,t,r);return n}if(e.length>1)throw $o(e.length,t,r);return e[0]}async function QC(e,t,r){Io();let n=Ia(e),a={timeout:Go(r)};switch(t.type){case"role":return n.findAllByRole(t.role,t.options,a);case"label":return n.findAllByLabelText(t.text,void 0,a);case"testId":{let l=bc(t);return l===void 0?n.findAllByTestId(t.testId,void 0,a):GC(e,l,void 0,a)}case"text":return n.findAllByText(t.text,void 0,a);case"placeholder":return n.findAllByPlaceholderText(t.text,void 0,a)}}async function Bo(e,t,r){let n=e;for(let[a,l]of t.entries()){let o=t.slice(0,a),i=await vc(n,o,l,r);n=l.frame===!0?yc(i,o,l):i}return n}var Se=class extends Error{constructor(){super(...arguments);pt(this,"name","FrameEntryError")}};function yc(e,t,r){if(e.localName!=="iframe")throw new Se(`harnessed: ${Le(t,r)} is marked as a frame but is a <${e.localName}>, not an <iframe>.`);let n=e.contentDocument?.body;if(n==null)throw new Se(`harnessed: ${Le(t,r)} has no reachable document: it is cross-origin, or its document has no body. The dom driver enters same-origin frames only; drive a cross-origin one with a browser driver.`);return n}async function hc(e,t,r,n){let a=await Bo(e,t,n);return vc(a,t,r,n)}async function gc(e,t,r,n){let a;try{a=await Bo(e,t,n)}catch(o){if(o instanceof Se)throw o;return 0}let l=Me(a,r);return r.nth===void 0?l.length:l[r.nth]===void 0?0:1}function qc(e,t){let r=e;for(let[n,a]of t.entries()){let l=t.slice(0,n),o=Me(r,a);if(o.length===0)return null;let i=No(o,l,a);r=a.frame===!0?yc(i,l,a):i}return r}function _c(e,t,r){let n=qc(e,t);if(n===null)return null;let a=Me(n,r);return a.length===0?null:No(a,t,r)}function Ec(e,t,r){let n;try{n=qc(e,t)}catch(o){if(o instanceof Se)throw o;return[]}if(n===null)return[];let a=Me(n,r);if(r.nth===void 0)return a;let l=a[r.nth];return l===void 0?[]:[l]}async function Rc(e,t,r,n){return Me(await Bo(e,t,n),r)}function Lo(e,t){if(!Cc(e))return!1;let r=e.ownerDocument;if(r===t)return!0;let n=r.defaultView?.frameElement;return n==null||Lo(n,t)}function Cc(e){let t=e.ownerDocument.defaultView;if(t==null)return!1;let r=t.getComputedStyle(e);if(r.display==="contents")return[...e.children].some(a=>Cc(a));if(r.visibility!=="visible")return!1;let n=e.getBoundingClientRect();return n.width>0&&n.height>0}function XC(e){return Object.prototype.toString.call(e)==="[object RegExp]"}function KC(e){return typeof e=="object"&&e!==null&&typeof e.$regexp=="string"&&typeof e.flags=="string"}function jo(e){return XC(e)?new RegExp(e.source,e.flags):KC(e)?new RegExp(e.$regexp,e.flags):Array.isArray(e)?e.map(jo):typeof e=="object"&&e!==null?Object.fromEntries(Object.entries(e).map(([t,r])=>[t,jo(r)])):e}function Fo(e){return jo(e)}function Pc(e){let t=a=>a??e.body,r=a=>zo({testIdAttribute:a.testIdAttribute}),n=(a,l)=>[a.map(Fo),Fo(l)];return{async count(a,l,o,i){r(i);let[s,u]=n(l,o);return gc(t(a),s,u,i.timeout)},async one(a,l,o,i){r(i);let[s,u]=n(l,o);return hc(t(a),s,u,i.timeout)},async all(a,l,o,i){r(i);let[s,u]=n(l,o);return Rc(t(a),s,u,i.timeout)},oneNow(a,l,o,i){r(i);let[s,u]=n(l,o);return _c(t(a),s,u)},allNow(a,l,o,i){r(i);let[s,u]=n(l,o);return Ec(t(a),s,u)},visible(a){return Lo(a,a.ownerDocument)},box:Ko,origin:Jo,fileTarget(a){let l=Yo(a);return typeof l=="string"?l:{multiple:l.multiple}}}}var wc=globalThis;ft in wc||(wc[ft]=Pc(document));})();
58
+ `+t),t},Vd={debug:WC,logTestingPlaygroundURL:zC},NT=typeof document<"u"&&document.body?Ia(document.body,wo,Vd):Object.keys(wo).reduce((e,t)=>(e[t]=()=>{throw new TypeError("For queries bound to document.body a global document has to be available... Learn more: https://testing-library.com/s/screen-global-error")},e),Vd);var Io=Xo(e=>tc({testIdAttribute:e}));function mc(e,t){return[...e.querySelectorAll(t)]}var[,,,GC]=ee(mc,(e,t)=>`Found multiple elements by: ${t}`,(e,t)=>`Unable to find an element by: ${t}`);function bc(e){return Qo(mt().testIdAttribute,e.testId)}function Me(e,t){Io();let r=Ia(e);switch(t.type){case"role":return r.queryAllByRole(t.role,t.options);case"label":return r.queryAllByLabelText(t.text);case"testId":{let n=bc(t);return n===void 0?r.queryAllByTestId(t.testId):mc(e,n)}case"text":return r.queryAllByText(t.text);case"placeholder":return r.queryAllByPlaceholderText(t.text)}}async function vc(e,t,r,n){Io();let a=Me(e,r),l=a.length>0?a:await QC(e,r,n);return No(l,t,r)}function No(e,t,r){if(r.nth!==void 0){let n=e[r.nth];if(n===void 0)throw Uo(r.nth,e.length,t,r);return n}if(e.length>1)throw $o(e.length,t,r);return e[0]}async function QC(e,t,r){Io();let n=Ia(e),a={timeout:Go(r)};switch(t.type){case"role":return n.findAllByRole(t.role,t.options,a);case"label":return n.findAllByLabelText(t.text,void 0,a);case"testId":{let l=bc(t);return l===void 0?n.findAllByTestId(t.testId,void 0,a):GC(e,l,void 0,a)}case"text":return n.findAllByText(t.text,void 0,a);case"placeholder":return n.findAllByPlaceholderText(t.text,void 0,a)}}async function Bo(e,t,r){let n=e;for(let[a,l]of t.entries()){let o=t.slice(0,a),i=await vc(n,o,l,r);n=l.frame===!0?yc(i,o,l):i}return n}var Se=class extends Error{constructor(){super(...arguments);pt(this,"name","FrameEntryError")}};function yc(e,t,r){if(e.localName!=="iframe")throw new Se(`harnessed: ${Le(t,r)} is marked as a frame but is a <${e.localName}>, not an <iframe>.`);let n=e.contentDocument?.body;if(n==null)throw new Se(`harnessed: ${Le(t,r)} has no reachable document: it is cross-origin, or its document has no body. The dom driver enters same-origin frames only; drive a cross-origin one with a browser driver.`);return n}async function hc(e,t,r,n){let a=await Bo(e,t,n);return vc(a,t,r,n)}async function gc(e,t,r,n){let a;try{a=await Bo(e,t,n)}catch(o){if(o instanceof Se)throw o;return 0}let l=Me(a,r);return r.nth===void 0?l.length:l[r.nth]===void 0?0:1}function qc(e,t){let r=e;for(let[n,a]of t.entries()){let l=t.slice(0,n),o=Me(r,a);if(o.length===0)return null;let i=No(o,l,a);r=a.frame===!0?yc(i,l,a):i}return r}function _c(e,t,r){let n=qc(e,t);if(n===null)return null;let a=Me(n,r);return a.length===0?null:No(a,t,r)}function Ec(e,t,r){let n;try{n=qc(e,t)}catch(o){if(o instanceof Se)throw o;return[]}if(n===null)return[];let a=Me(n,r);if(r.nth===void 0)return a;let l=a[r.nth];return l===void 0?[]:[l]}async function Rc(e,t,r,n){return Me(await Bo(e,t,n),r)}function Lo(e,t){if(!Cc(e))return!1;let r=e.ownerDocument;if(r===t)return!0;let n=r.defaultView?.frameElement;return n==null||Lo(n,t)}function Cc(e){let t=e.ownerDocument.defaultView;if(t==null)return!1;let r=t.getComputedStyle(e);if(r.display==="contents")return[...e.children].some(a=>Cc(a));if(r.visibility!=="visible")return!1;let n=e.getBoundingClientRect();return n.width>0&&n.height>0}function XC(e){return Object.prototype.toString.call(e)==="[object RegExp]"}function KC(e){return typeof e=="object"&&e!==null&&typeof e.$regexp=="string"&&typeof e.flags=="string"}function jo(e){return XC(e)?new RegExp(e.source,e.flags):KC(e)?new RegExp(e.$regexp,e.flags):Array.isArray(e)?e.map(jo):typeof e=="object"&&e!==null?Object.fromEntries(Object.entries(e).map(([t,r])=>[t,jo(r)])):e}function Fo(e){return jo(e)}function Pc(e){let t=a=>a??e.body,r=a=>zo({testIdAttribute:a.testIdAttribute}),n=(a,l)=>[a.map(Fo),Fo(l)];return{async count(a,l,o,i){r(i);let[s,u]=n(l,o);return gc(t(a),s,u,i.timeout)},async one(a,l,o,i){r(i);let[s,u]=n(l,o);return hc(t(a),s,u,i.timeout)},async all(a,l,o,i){r(i);let[s,u]=n(l,o);return Rc(t(a),s,u,i.timeout)},oneNow(a,l,o,i){r(i);let[s,u]=n(l,o);return _c(t(a),s,u)},allNow(a,l,o,i){r(i);let[s,u]=n(l,o);return Ec(t(a),s,u)},visible(a){return Lo(a,a.ownerDocument)},box:Ko,origin:Jo,fileTarget(a){let l=Yo(a);return typeof l=="string"?l:{multiple:l.multiple}}}}var wc=typeof window>"u"?globalThis:window;ft in wc||(wc[ft]=Pc(document));})();
59
59
  /*! Bundled license information:
60
60
 
61
61
  react-is/cjs/react-is.production.min.js:
package/dist/inject.js CHANGED
@@ -1,61 +1,3 @@
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 };
1
+ export { PAGE_API_GLOBAL, encodeSelector, filesOnDisk, injectPath, injectSource } from './chunk-BYG4HMLR.js';
60
2
  //# sourceMappingURL=inject.js.map
61
3
  //# sourceMappingURL=inject.js.map
@@ -1 +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":"inject.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"]}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"inject.js"}
@@ -0,0 +1,48 @@
1
+ import type { PageApiOptions } from './page-api';
2
+ import type { WireSelector } from './wire';
3
+ /**
4
+ * What the in-page script does with the target it resolves. Reads happen in the
5
+ * page, in the same round-trip as the resolution: a WebDriver command costs a
6
+ * few milliseconds each way, and a read that first fetched an element reference
7
+ * and then asked about it would pay that twice.
8
+ */
9
+ export type PageOp = 'frame' | 'frameNow' | 'element' | 'probe' | 'visible' | 'count' | 'texts' | 'text' | 'value' | 'attribute' | 'enabled' | 'checked' | 'selected' | 'select' | 'clear' | 'focus' | 'blur' | 'box' | 'upload';
10
+ /**
11
+ * The script's answer. A page error comes back as a value rather than a thrown
12
+ * script error, so its name and message arrive intact — a thrown one reaches the
13
+ * test wrapped in protocol noise, and under WebDriver Classic is logged as a
14
+ * driver failure as well.
15
+ */
16
+ export type PageReply = {
17
+ kind: 'missing';
18
+ } | {
19
+ kind: 'ok';
20
+ value: unknown;
21
+ url: string;
22
+ } | {
23
+ kind: 'error';
24
+ name: string;
25
+ message: string;
26
+ };
27
+ /** Raised in the page by the shared resolver: strictness, index and frame rules. */
28
+ export declare class PageError extends Error {
29
+ constructor(name: string, message: string);
30
+ }
31
+ /** The shared resolver's refusal of a frame it cannot enter. Never "not there yet". */
32
+ export declare function isFrameEntryError(error: unknown): boolean;
33
+ /**
34
+ * An element as WebDriver returns it from a script, before the driver's client
35
+ * wraps it (WebdriverIO's `$()`; selenium-webdriver hands back a `WebElement`
36
+ * already).
37
+ */
38
+ export type ElementRef = {
39
+ 'element-6066-11e4-a52e-4f735466cecf': string;
40
+ };
41
+ /**
42
+ * Runs IN THE PAGE, serialised with `Function.prototype.toString`, so it must
43
+ * close over nothing: every input arrives as an argument. Elements are
44
+ * only ever returned as object properties, each at most once — WebDriver BiDi
45
+ * serialises an element repeated in a result, or one inside an array, as `{}`.
46
+ */
47
+ export declare function pageScript(name: string, op: PageOp, scope: WireSelector[], selector: WireSelector, options: PageApiOptions, arg: string | null): Promise<PageReply>;
48
+ //# sourceMappingURL=webdriver-page-script.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webdriver-page-script.d.ts","sourceRoot":"","sources":["../src/webdriver-page-script.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,cAAc,EAAE,MAAM,YAAY,CAAA;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAE1C;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GACd,OAAO,GACP,UAAU,GACV,SAAS,GACT,OAAO,GACP,SAAS,GACT,OAAO,GACP,OAAO,GACP,MAAM,GACN,OAAO,GACP,WAAW,GACX,SAAS,GACT,SAAS,GACT,UAAU,GACV,QAAQ,GACR,OAAO,GACP,OAAO,GACP,MAAM,GACN,KAAK,GACL,QAAQ,CAAA;AAEZ;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAEpD,oFAAoF;AACpF,qBAAa,SAAU,SAAQ,KAAK;gBACtB,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAI1C;AAED,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEzD;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG;IAAE,qCAAqC,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1E;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,YAAY,EAAE,EACrB,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE,MAAM,GAAG,IAAI,GACjB,OAAO,CAAC,SAAS,CAAC,CAyJpB"}
@@ -0,0 +1,272 @@
1
+ 'use strict';
2
+
3
+ var core = require('@harnessed-ts/core');
4
+ var fs = require('fs');
5
+ require('os');
6
+ var path = require('path');
7
+ var url = require('url');
8
+
9
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
10
+ // src/webdriver.ts
11
+
12
+ // src/global-name.ts
13
+ var PAGE_API_GLOBAL = "__harnessedResolve";
14
+
15
+ // src/wire.ts
16
+ function isRegExp(value) {
17
+ return Object.prototype.toString.call(value) === "[object RegExp]";
18
+ }
19
+ function encodeValue(value) {
20
+ if (isRegExp(value)) return { $regexp: value.source, flags: value.flags };
21
+ if (Array.isArray(value)) return value.map(encodeValue);
22
+ if (typeof value === "object" && value !== null) {
23
+ return Object.fromEntries(
24
+ Object.entries(value).map(([key, inner]) => [key, encodeValue(inner)])
25
+ );
26
+ }
27
+ return value;
28
+ }
29
+ function encodeSelector(selector) {
30
+ return encodeValue(selector);
31
+ }
32
+
33
+ // src/inject.ts
34
+ function injectPath() {
35
+ if (typeof __filename === "string") return path.join(path.dirname(__filename), "inject.global.js");
36
+ return url.fileURLToPath(new URL("./inject.global.js", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('webdriver.cjs', document.baseURI).href))));
37
+ }
38
+ var cached;
39
+ function injectSource() {
40
+ if (cached === void 0) cached = fs.readFileSync(injectPath(), "utf8");
41
+ return cached;
42
+ }
43
+
44
+ // src/webdriver-page-script.ts
45
+ var PageError = class extends Error {
46
+ constructor(name, message) {
47
+ super(message);
48
+ this.name = name;
49
+ }
50
+ };
51
+ function isFrameEntryError(error) {
52
+ return error instanceof PageError && error.name === "FrameEntryError";
53
+ }
54
+ async function pageScript(name, op, scope, selector, options, arg) {
55
+ const api = window[name];
56
+ if (api === void 0) return { kind: "missing" };
57
+ const one = () => api.one(null, scope, selector, options);
58
+ const iframe = async (found) => {
59
+ if (found !== null && found.localName !== "iframe") {
60
+ await api.count(null, [...scope, selector], selector, options);
61
+ }
62
+ return {
63
+ element: found,
64
+ origin: found === null ? { x: 0, y: 0 } : api.origin(found),
65
+ // Whether the iframe is visible in its own document: content in a hidden
66
+ // frame is hidden, whatever its own style says.
67
+ visible: found !== null && api.visible(found)
68
+ };
69
+ };
70
+ try {
71
+ let value;
72
+ switch (op) {
73
+ case "frame":
74
+ value = await iframe(await one());
75
+ break;
76
+ case "frameNow":
77
+ value = await iframe(api.oneNow(null, scope, selector, options));
78
+ break;
79
+ case "element":
80
+ value = { element: await one() };
81
+ break;
82
+ case "probe": {
83
+ const matches = api.allNow(null, scope, selector, options);
84
+ value = { count: matches.length, element: matches.length === 1 ? matches[0] : null };
85
+ break;
86
+ }
87
+ case "visible": {
88
+ const matches = api.allNow(null, scope, selector, options);
89
+ value = {
90
+ count: matches.length,
91
+ visible: matches.length === 1 && api.visible(matches[0])
92
+ };
93
+ break;
94
+ }
95
+ case "count":
96
+ value = await api.count(null, scope, selector, options);
97
+ break;
98
+ case "texts": {
99
+ const matches = await api.all(null, scope, selector, options);
100
+ const index = selector.nth;
101
+ const listed = index === void 0 ? matches : matches.slice(index, index + 1);
102
+ value = listed.map((element) => element.textContent ?? "");
103
+ break;
104
+ }
105
+ case "text":
106
+ value = (await one()).textContent ?? "";
107
+ break;
108
+ case "value":
109
+ value = (await one()).value ?? "";
110
+ break;
111
+ case "attribute":
112
+ value = (await one()).getAttribute(arg ?? "");
113
+ break;
114
+ case "enabled": {
115
+ const element = await one();
116
+ value = {
117
+ disabled: element.disabled === true || element.closest(":disabled") !== null,
118
+ aria: element.getAttribute("aria-disabled")
119
+ };
120
+ break;
121
+ }
122
+ case "checked": {
123
+ const element = await one();
124
+ value = {
125
+ element,
126
+ aria: element.getAttribute("aria-checked"),
127
+ native: Boolean(element.checked)
128
+ };
129
+ break;
130
+ }
131
+ case "selected": {
132
+ const element = await one();
133
+ value = [...element.selectedOptions ?? []].map((option) => option.value);
134
+ break;
135
+ }
136
+ case "select": {
137
+ const element = await one();
138
+ value = {
139
+ element,
140
+ multiple: element.multiple,
141
+ selected: [...element.selectedOptions ?? []].map((option) => option.value),
142
+ // What each option can be chosen by: its value, or the label it shows.
143
+ options: [...element.options ?? []].map((option) => ({
144
+ value: option.value,
145
+ label: option.label.trim()
146
+ }))
147
+ };
148
+ break;
149
+ }
150
+ case "clear": {
151
+ const element = await one();
152
+ const direct = ["range", "date", "datetime-local", "month", "week", "time", "color"];
153
+ element.focus();
154
+ if (typeof element.select === "function") element.select();
155
+ else window.getSelection()?.selectAllChildren(element);
156
+ value = {
157
+ element,
158
+ direct: element.localName === "input" && direct.includes(element.type),
159
+ filled: (element.value ?? element.textContent ?? "") !== ""
160
+ };
161
+ break;
162
+ }
163
+ case "box":
164
+ value = api.box(await one());
165
+ break;
166
+ case "upload": {
167
+ const element = await one();
168
+ const control = element.localName === "label" ? element.control : null;
169
+ const input = control ?? element;
170
+ value = {
171
+ element: input,
172
+ target: api.fileTarget(element),
173
+ filled: (input.files?.length ?? 0) > 0
174
+ };
175
+ break;
176
+ }
177
+ case "focus":
178
+ ;
179
+ (await one()).focus();
180
+ value = null;
181
+ break;
182
+ case "blur":
183
+ ;
184
+ (await one()).blur();
185
+ value = null;
186
+ break;
187
+ }
188
+ return { kind: "ok", value, url: location.href };
189
+ } catch (error) {
190
+ const { name: name2 = "Error", message = String(error) } = error ?? {};
191
+ return { kind: "error", name: name2, message };
192
+ }
193
+ }
194
+
195
+ // src/webdriver.ts
196
+ var classic;
197
+ function classicScript() {
198
+ if (classic === void 0) classic = `return (${pageScript.toString()}).apply(null, arguments)`;
199
+ return classic;
200
+ }
201
+ async function callPage(driver, site, op, scope, selector, timeout, arg = null) {
202
+ core.checkTestIds(scope, selector);
203
+ const args = [
204
+ PAGE_API_GLOBAL,
205
+ op,
206
+ scope.map(encodeSelector),
207
+ encodeSelector(selector),
208
+ // The page cannot see this process's configure(), so the settings travel.
209
+ { testIdAttribute: core.getConfig().testIdAttribute, timeout },
210
+ arg
211
+ ];
212
+ const ask = async () => await driver.run(site, args);
213
+ let reply = await ask();
214
+ if (reply.kind === "missing") {
215
+ await driver.inject(site, injectSource());
216
+ reply = await ask();
217
+ }
218
+ if (reply.kind === "missing") {
219
+ throw new Error("harnessed: the resolver could not be injected into the page.");
220
+ }
221
+ if (reply.kind === "error") throw core.reviveStrictViolation(new PageError(reply.name, reply.message));
222
+ if (site.top) driver.sawUrl?.(reply.url);
223
+ return reply.value;
224
+ }
225
+ function splitAtFrames(scope) {
226
+ const hops = [];
227
+ let current = [];
228
+ for (const link of scope) {
229
+ if (link.frame === true) {
230
+ hops.push({ scope: current, link });
231
+ current = [];
232
+ } else {
233
+ current.push(link);
234
+ }
235
+ }
236
+ return { hops, rest: current };
237
+ }
238
+ var FrameAbsent = class extends Error {
239
+ name = "FrameAbsent";
240
+ };
241
+ async function walk(driver, scope, options, operation) {
242
+ const deadline = Date.now() + options.timeout;
243
+ const remaining = () => Math.max(0, deadline - Date.now());
244
+ const { hops, rest } = splitAtFrames(scope);
245
+ let site = driver.top();
246
+ let entered = false;
247
+ try {
248
+ for (const hop of hops) {
249
+ const op = options.waiting ? "frame" : "frameNow";
250
+ const found = await callPage(driver, site, op, hop.scope, hop.link, remaining());
251
+ if (found.element === null) throw new FrameAbsent();
252
+ const iframe = await driver.element(site, found.element);
253
+ await options.onFrame?.(iframe, { visible: found.visible });
254
+ const origin = { x: site.origin.x + found.origin.x, y: site.origin.y + found.origin.y };
255
+ site = await driver.enterFrame(site, iframe, origin);
256
+ entered = true;
257
+ }
258
+ return await operation(site, rest, remaining);
259
+ } finally {
260
+ if (entered) await driver.leaveFrames();
261
+ }
262
+ }
263
+
264
+ exports.FrameAbsent = FrameAbsent;
265
+ exports.PageError = PageError;
266
+ exports.callPage = callPage;
267
+ exports.classicScript = classicScript;
268
+ exports.isFrameEntryError = isFrameEntryError;
269
+ exports.pageScript = pageScript;
270
+ exports.walk = walk;
271
+ //# sourceMappingURL=webdriver.cjs.map
272
+ //# sourceMappingURL=webdriver.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/global-name.ts","../src/wire.ts","../src/inject.ts","../src/webdriver-page-script.ts","../src/webdriver.ts"],"names":["join","dirname","fileURLToPath","readFileSync","name","checkTestIds","getConfig","reviveStrictViolation"],"mappings":"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","file":"webdriver.cjs","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","import type { PageApi, PageApiOptions } from './page-api'\nimport type { WireSelector } from './wire'\n\n/**\n * What the in-page script does with the target it resolves. Reads happen in the\n * page, in the same round-trip as the resolution: a WebDriver command costs a\n * few milliseconds each way, and a read that first fetched an element reference\n * and then asked about it would pay that twice.\n */\nexport type PageOp =\n | 'frame'\n | 'frameNow'\n | 'element'\n | 'probe'\n | 'visible'\n | 'count'\n | 'texts'\n | 'text'\n | 'value'\n | 'attribute'\n | 'enabled'\n | 'checked'\n | 'selected'\n | 'select'\n | 'clear'\n | 'focus'\n | 'blur'\n | 'box'\n | 'upload'\n\n/**\n * The script's answer. A page error comes back as a value rather than a thrown\n * script error, so its name and message arrive intact — a thrown one reaches the\n * test wrapped in protocol noise, and under WebDriver Classic is logged as a\n * driver failure as well.\n */\nexport type PageReply =\n | { kind: 'missing' }\n | { kind: 'ok'; value: unknown; url: string }\n | { kind: 'error'; name: string; message: string }\n\n/** Raised in the page by the shared resolver: strictness, index and frame rules. */\nexport class PageError extends Error {\n constructor(name: string, message: string) {\n super(message)\n this.name = name\n }\n}\n\n/** The shared resolver's refusal of a frame it cannot enter. Never \"not there yet\". */\nexport function isFrameEntryError(error: unknown): boolean {\n return error instanceof PageError && error.name === 'FrameEntryError'\n}\n\n/**\n * An element as WebDriver returns it from a script, before the driver's client\n * wraps it (WebdriverIO's `$()`; selenium-webdriver hands back a `WebElement`\n * already).\n */\nexport type ElementRef = { 'element-6066-11e4-a52e-4f735466cecf': string }\n\n/**\n * Runs IN THE PAGE, serialised with `Function.prototype.toString`, so it must\n * close over nothing: every input arrives as an argument. Elements are\n * only ever returned as object properties, each at most once — WebDriver BiDi\n * serialises an element repeated in a result, or one inside an array, as `{}`.\n */\nexport async function pageScript(\n name: string,\n op: PageOp,\n scope: WireSelector[],\n selector: WireSelector,\n options: PageApiOptions,\n arg: string | null,\n): Promise<PageReply> {\n const api = (window as unknown as Record<string, PageApi | undefined>)[name]\n // Navigation replaces the window, and the resolver with it.\n if (api === undefined) return { kind: 'missing' }\n const one = () => api.one(null, scope, selector, options)\n /**\n * A frame link must land on an iframe. When it does not, walking through the\n * same link with the shared resolver raises its FrameEntryError, so the\n * refusal and its wording stay the resolver's rather than a copy kept here.\n */\n const iframe = async (found: Element | null) => {\n if (found !== null && found.localName !== 'iframe') {\n await api.count(null, [...scope, selector], selector, options)\n }\n return {\n element: found,\n origin: found === null ? { x: 0, y: 0 } : api.origin(found),\n // Whether the iframe is visible in its own document: content in a hidden\n // frame is hidden, whatever its own style says.\n visible: found !== null && api.visible(found),\n }\n }\n try {\n let value: unknown\n switch (op) {\n case 'frame':\n value = await iframe(await one())\n break\n case 'frameNow':\n value = await iframe(api.oneNow(null, scope, selector, options))\n break\n case 'element':\n value = { element: await one() }\n break\n case 'probe': {\n // `allNow` applies `nth` and answers at once; the caller decides what\n // more than one match means, with the whole scope chain in hand.\n const matches = api.allNow(null, scope, selector, options)\n value = { count: matches.length, element: matches.length === 1 ? matches[0] : null }\n break\n }\n case 'visible': {\n // `probe`'s answer, with the layout check made in the same round-trip.\n const matches = api.allNow(null, scope, selector, options)\n value = {\n count: matches.length,\n visible: matches.length === 1 && api.visible(matches[0] as Element),\n }\n break\n }\n case 'count':\n value = await api.count(null, scope, selector, options)\n break\n case 'texts': {\n const matches = await api.all(null, scope, selector, options)\n const index = (selector as { nth?: number }).nth\n const listed = index === undefined ? matches : matches.slice(index, index + 1)\n value = listed.map(element => element.textContent ?? '')\n break\n }\n case 'text':\n value = (await one()).textContent ?? ''\n break\n case 'value':\n value = ((await one()) as HTMLInputElement).value ?? ''\n break\n case 'attribute':\n value = (await one()).getAttribute(arg ?? '')\n break\n case 'enabled': {\n const element = (await one()) as HTMLInputElement\n // The property, and the closest `:disabled`, both see an ancestor\n // fieldset's `disabled`; the element's own attribute does not.\n value = {\n disabled: element.disabled === true || element.closest(':disabled') !== null,\n aria: element.getAttribute('aria-disabled'),\n }\n break\n }\n case 'checked': {\n const element = (await one()) as HTMLInputElement\n value = {\n element,\n aria: element.getAttribute('aria-checked'),\n native: Boolean(element.checked),\n }\n break\n }\n case 'selected': {\n const element = (await one()) as HTMLSelectElement\n value = [...(element.selectedOptions ?? [])].map(option => option.value)\n break\n }\n case 'select': {\n const element = (await one()) as HTMLSelectElement\n value = {\n element,\n multiple: element.multiple,\n selected: [...(element.selectedOptions ?? [])].map(option => option.value),\n // What each option can be chosen by: its value, or the label it shows.\n options: [...(element.options ?? [])].map(option => ({\n value: option.value,\n label: option.label.trim(),\n })),\n }\n break\n }\n case 'clear': {\n const element = (await one()) as HTMLInputElement\n // WebDriver sets these input types' values directly: they take no typing.\n const direct = ['range', 'date', 'datetime-local', 'month', 'week', 'time', 'color']\n element.focus()\n // Selected, so one Backspace deletes it all: a real edit, which fires the\n // input events a controlled component listens for.\n if (typeof element.select === 'function') element.select()\n else window.getSelection()?.selectAllChildren(element)\n value = {\n element,\n direct: element.localName === 'input' && direct.includes(element.type),\n filled: (element.value ?? element.textContent ?? '') !== '',\n }\n break\n }\n case 'box':\n // In this document's viewport; the driver adds the frames' origins.\n value = api.box(await one())\n break\n case 'upload': {\n const element = await one()\n const control = element.localName === 'label' ? (element as HTMLLabelElement).control : null\n // The input itself, where a label is the target: WebDriver types paths into the input.\n const input = (control ?? element) as HTMLInputElement\n value = {\n element: input,\n target: api.fileTarget(element),\n filled: (input.files?.length ?? 0) > 0,\n }\n break\n }\n case 'focus':\n ;((await one()) as HTMLElement).focus()\n value = null\n break\n case 'blur':\n ;((await one()) as HTMLElement).blur()\n value = null\n break\n }\n return { kind: 'ok', value, url: location.href }\n } catch (error) {\n const { name = 'Error', message = String(error) } = (error ?? {}) as Partial<Error>\n return { kind: 'error', name, message }\n }\n}\n","import { checkTestIds, getConfig, reviveStrictViolation } from '@harnessed-ts/core'\nimport type { Selector } from '@harnessed-ts/core'\nimport type { Point } from './box'\nimport { injectSource } from './inject'\nimport { PAGE_API_GLOBAL } from './global-name'\nimport { encodeSelector } from './wire'\nimport { PageError, pageScript } from './webdriver-page-script'\nimport type { ElementRef, PageOp, PageReply } from './webdriver-page-script'\n\nexport { isFrameEntryError, PageError, pageScript } from './webdriver-page-script'\nexport type { ElementRef, PageOp, PageReply } from './webdriver-page-script'\nexport type { Point } from './box'\n\n/** What `pageScript` takes, in order: what every `WebDriverLike.run` passes it. */\nexport type PageScriptArgs = Parameters<typeof pageScript>\n\n/**\n * A document a WebDriver client is working in: the top-level one, or a frame's\n * a walk entered. A driver extends it with whatever it needs to address that\n * document (a BiDi browsing context, say).\n */\nexport interface WebDriverSite {\n /** The top-level document, whose URL is the page's URL. */\n readonly top: boolean\n /** Where this document's viewport starts in the top-level one, in CSS pixels. */\n readonly origin: Point\n}\n\n/**\n * What the shared WebDriver code needs from a client — WebdriverIO,\n * selenium-webdriver under Nightwatch — and nothing more: running a script in a\n * document, and crossing into a frame. `S` is the client's site, `E` its element.\n */\nexport interface WebDriverLike<S extends WebDriverSite, E> {\n /** The top-level document. */\n top(): S\n /**\n * Runs `pageScript` in the site's document with these arguments and returns\n * its reply. Under WebDriver Classic, `classicScript()` is the body to send.\n */\n run(site: S, args: PageScriptArgs): Promise<unknown>\n /** Evaluates the resolver's injectable source in the site's document. */\n inject(site: S, source: string): Promise<void>\n /** An element the page script returned, as the client's own element. */\n element(site: S, ref: ElementRef): Promise<E>\n /**\n * Enters `iframe`, an element of `site`'s document, and returns the site for\n * the frame's document, at `origin` in the top-level viewport.\n */\n enterFrame(site: S, iframe: E, origin: Point): Promise<S>\n /**\n * Undoes what entering frames changed in the session — under Classic, switches\n * back to the top-level document. Called once after a walk that entered any.\n */\n leaveFrames(): Promise<void>\n /** Hears the top-level document's URL, which every call there brings back. */\n sawUrl?(url: string): void\n}\n\nlet classic: string | undefined\n\n/**\n * The page script as a classic Execute Script body, which awaits the promise\n * the script returns.\n */\nexport function classicScript(): string {\n if (classic === undefined) classic = `return (${pageScript.toString()}).apply(null, arguments)`\n return classic\n}\n\n/**\n * Runs one operation of the shared resolver in a document, injecting the\n * resolver first if the document does not have it yet.\n *\n * The presence check rides along with the call itself, so a document that\n * already has the resolver pays one round-trip, not two. Injecting happens once\n * per document: navigation replaces the window, and the resolver with it.\n */\nexport async function callPage<S extends WebDriverSite, E>(\n driver: WebDriverLike<S, E>,\n site: S,\n op: PageOp,\n scope: readonly Selector[],\n selector: Selector,\n timeout: number,\n arg: string | null = null,\n): Promise<unknown> {\n checkTestIds(scope, selector)\n const args: PageScriptArgs = [\n PAGE_API_GLOBAL,\n op,\n scope.map(encodeSelector),\n encodeSelector(selector),\n // The page cannot see this process's configure(), so the settings travel.\n { testIdAttribute: getConfig().testIdAttribute, timeout },\n arg,\n ]\n const ask = async () => (await driver.run(site, args)) as PageReply\n let reply = await ask()\n if (reply.kind === 'missing') {\n await driver.inject(site, injectSource())\n reply = await ask()\n }\n if (reply.kind === 'missing') {\n throw new Error('harnessed: the resolver could not be injected into the page.')\n }\n // A strict violation comes back as core's own class, as under every driver.\n if (reply.kind === 'error') throw reviveStrictViolation(new PageError(reply.name, reply.message))\n if (site.top) driver.sawUrl?.(reply.url)\n return reply.value\n}\n\n/** A scope chain cut at a frame link: the links before it, and the link itself. */\ninterface Hop {\n scope: Selector[]\n link: Selector\n}\n\nfunction splitAtFrames(scope: readonly Selector[]): { hops: Hop[]; rest: Selector[] } {\n const hops: Hop[] = []\n let current: Selector[] = []\n for (const link of scope) {\n if (link.frame === true) {\n hops.push({ scope: current, link })\n current = []\n } else {\n current.push(link)\n }\n }\n return { hops, rest: current }\n}\n\n/** A non-waiting walk found a frame link that is not on screen. */\nexport class FrameAbsent extends Error {\n override name = 'FrameAbsent'\n}\n\n/** What the page reports about an iframe a walk is about to enter. */\nexport interface FrameInfo {\n /** Whether the iframe is visible by layout in its own document. */\n readonly visible: boolean\n}\n\nexport interface WalkOptions<E> {\n /** Wait for each frame link, or answer at once by throwing FrameAbsent. */\n waiting: boolean\n /** The budget for every hop and the operation together. */\n timeout: number\n /** Called with each iframe element, from its own document, before entering it. */\n onFrame?: (iframe: E, info: FrameInfo) => Promise<void>\n}\n\n/**\n * Walks the scope chain's frame links, entering each frame, then runs the\n * operation in the innermost document with the links that remain.\n *\n * A WebDriver element belongs to one document: an element inside a frame cannot\n * be acted on from the top-level one, even where the page's own script could\n * reach it. So the shared resolver runs once per document — up to the iframe,\n * then inside it — and WebDriver does the crossing. That is also why\n * cross-origin frames work here, where an in-page resolver cannot enter them.\n *\n * Whatever entering changed in the session is undone afterwards, whatever\n * happened (`leaveFrames`).\n */\nexport async function walk<S extends WebDriverSite, E, T>(\n driver: WebDriverLike<S, E>,\n scope: readonly Selector[],\n options: WalkOptions<E>,\n operation: (site: S, scope: Selector[], remaining: () => number) => Promise<T>,\n): Promise<T> {\n const deadline = Date.now() + options.timeout\n const remaining = () => Math.max(0, deadline - Date.now())\n const { hops, rest } = splitAtFrames(scope)\n let site = driver.top()\n let entered = false\n try {\n for (const hop of hops) {\n const op = options.waiting ? 'frame' : 'frameNow'\n const found = (await callPage(driver, site, op, hop.scope, hop.link, remaining())) as {\n element: ElementRef | null\n origin: Point\n visible: boolean\n }\n if (found.element === null) throw new FrameAbsent()\n const iframe = await driver.element(site, found.element)\n await options.onFrame?.(iframe, { visible: found.visible })\n // The frame's document starts where its content box does, in the document around it.\n const origin = { x: site.origin.x + found.origin.x, y: site.origin.y + found.origin.y }\n site = await driver.enterFrame(site, iframe, origin)\n entered = true\n }\n return await operation(site, rest, remaining)\n } finally {\n if (entered) await driver.leaveFrames()\n }\n}\n"]}
@@ -0,0 +1,96 @@
1
+ import type { Selector } from '@harnessed-ts/core';
2
+ import type { Point } from './box';
3
+ import { pageScript } from './webdriver-page-script';
4
+ import type { ElementRef, PageOp } from './webdriver-page-script';
5
+ export { isFrameEntryError, PageError, pageScript } from './webdriver-page-script';
6
+ export type { ElementRef, PageOp, PageReply } from './webdriver-page-script';
7
+ export type { Point } from './box';
8
+ /** What `pageScript` takes, in order: what every `WebDriverLike.run` passes it. */
9
+ export type PageScriptArgs = Parameters<typeof pageScript>;
10
+ /**
11
+ * A document a WebDriver client is working in: the top-level one, or a frame's
12
+ * a walk entered. A driver extends it with whatever it needs to address that
13
+ * document (a BiDi browsing context, say).
14
+ */
15
+ export interface WebDriverSite {
16
+ /** The top-level document, whose URL is the page's URL. */
17
+ readonly top: boolean;
18
+ /** Where this document's viewport starts in the top-level one, in CSS pixels. */
19
+ readonly origin: Point;
20
+ }
21
+ /**
22
+ * What the shared WebDriver code needs from a client — WebdriverIO,
23
+ * selenium-webdriver under Nightwatch — and nothing more: running a script in a
24
+ * document, and crossing into a frame. `S` is the client's site, `E` its element.
25
+ */
26
+ export interface WebDriverLike<S extends WebDriverSite, E> {
27
+ /** The top-level document. */
28
+ top(): S;
29
+ /**
30
+ * Runs `pageScript` in the site's document with these arguments and returns
31
+ * its reply. Under WebDriver Classic, `classicScript()` is the body to send.
32
+ */
33
+ run(site: S, args: PageScriptArgs): Promise<unknown>;
34
+ /** Evaluates the resolver's injectable source in the site's document. */
35
+ inject(site: S, source: string): Promise<void>;
36
+ /** An element the page script returned, as the client's own element. */
37
+ element(site: S, ref: ElementRef): Promise<E>;
38
+ /**
39
+ * Enters `iframe`, an element of `site`'s document, and returns the site for
40
+ * the frame's document, at `origin` in the top-level viewport.
41
+ */
42
+ enterFrame(site: S, iframe: E, origin: Point): Promise<S>;
43
+ /**
44
+ * Undoes what entering frames changed in the session — under Classic, switches
45
+ * back to the top-level document. Called once after a walk that entered any.
46
+ */
47
+ leaveFrames(): Promise<void>;
48
+ /** Hears the top-level document's URL, which every call there brings back. */
49
+ sawUrl?(url: string): void;
50
+ }
51
+ /**
52
+ * The page script as a classic Execute Script body, which awaits the promise
53
+ * the script returns.
54
+ */
55
+ export declare function classicScript(): string;
56
+ /**
57
+ * Runs one operation of the shared resolver in a document, injecting the
58
+ * resolver first if the document does not have it yet.
59
+ *
60
+ * The presence check rides along with the call itself, so a document that
61
+ * already has the resolver pays one round-trip, not two. Injecting happens once
62
+ * per document: navigation replaces the window, and the resolver with it.
63
+ */
64
+ export declare function callPage<S extends WebDriverSite, E>(driver: WebDriverLike<S, E>, site: S, op: PageOp, scope: readonly Selector[], selector: Selector, timeout: number, arg?: string | null): Promise<unknown>;
65
+ /** A non-waiting walk found a frame link that is not on screen. */
66
+ export declare class FrameAbsent extends Error {
67
+ name: string;
68
+ }
69
+ /** What the page reports about an iframe a walk is about to enter. */
70
+ export interface FrameInfo {
71
+ /** Whether the iframe is visible by layout in its own document. */
72
+ readonly visible: boolean;
73
+ }
74
+ export interface WalkOptions<E> {
75
+ /** Wait for each frame link, or answer at once by throwing FrameAbsent. */
76
+ waiting: boolean;
77
+ /** The budget for every hop and the operation together. */
78
+ timeout: number;
79
+ /** Called with each iframe element, from its own document, before entering it. */
80
+ onFrame?: (iframe: E, info: FrameInfo) => Promise<void>;
81
+ }
82
+ /**
83
+ * Walks the scope chain's frame links, entering each frame, then runs the
84
+ * operation in the innermost document with the links that remain.
85
+ *
86
+ * A WebDriver element belongs to one document: an element inside a frame cannot
87
+ * be acted on from the top-level one, even where the page's own script could
88
+ * reach it. So the shared resolver runs once per document — up to the iframe,
89
+ * then inside it — and WebDriver does the crossing. That is also why
90
+ * cross-origin frames work here, where an in-page resolver cannot enter them.
91
+ *
92
+ * Whatever entering changed in the session is undone afterwards, whatever
93
+ * happened (`leaveFrames`).
94
+ */
95
+ export declare function walk<S extends WebDriverSite, E, T>(driver: WebDriverLike<S, E>, scope: readonly Selector[], options: WalkOptions<E>, operation: (site: S, scope: Selector[], remaining: () => number) => Promise<T>): Promise<T>;
96
+ //# sourceMappingURL=webdriver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webdriver.d.ts","sourceRoot":"","sources":["../src/webdriver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,226 @@
1
+ import { PAGE_API_GLOBAL, encodeSelector, injectSource } from './chunk-BYG4HMLR.js';
2
+ import { checkTestIds, getConfig, reviveStrictViolation } from '@harnessed-ts/core';
3
+
4
+ // src/webdriver-page-script.ts
5
+ var PageError = class extends Error {
6
+ constructor(name, message) {
7
+ super(message);
8
+ this.name = name;
9
+ }
10
+ };
11
+ function isFrameEntryError(error) {
12
+ return error instanceof PageError && error.name === "FrameEntryError";
13
+ }
14
+ async function pageScript(name, op, scope, selector, options, arg) {
15
+ const api = window[name];
16
+ if (api === void 0) return { kind: "missing" };
17
+ const one = () => api.one(null, scope, selector, options);
18
+ const iframe = async (found) => {
19
+ if (found !== null && found.localName !== "iframe") {
20
+ await api.count(null, [...scope, selector], selector, options);
21
+ }
22
+ return {
23
+ element: found,
24
+ origin: found === null ? { x: 0, y: 0 } : api.origin(found),
25
+ // Whether the iframe is visible in its own document: content in a hidden
26
+ // frame is hidden, whatever its own style says.
27
+ visible: found !== null && api.visible(found)
28
+ };
29
+ };
30
+ try {
31
+ let value;
32
+ switch (op) {
33
+ case "frame":
34
+ value = await iframe(await one());
35
+ break;
36
+ case "frameNow":
37
+ value = await iframe(api.oneNow(null, scope, selector, options));
38
+ break;
39
+ case "element":
40
+ value = { element: await one() };
41
+ break;
42
+ case "probe": {
43
+ const matches = api.allNow(null, scope, selector, options);
44
+ value = { count: matches.length, element: matches.length === 1 ? matches[0] : null };
45
+ break;
46
+ }
47
+ case "visible": {
48
+ const matches = api.allNow(null, scope, selector, options);
49
+ value = {
50
+ count: matches.length,
51
+ visible: matches.length === 1 && api.visible(matches[0])
52
+ };
53
+ break;
54
+ }
55
+ case "count":
56
+ value = await api.count(null, scope, selector, options);
57
+ break;
58
+ case "texts": {
59
+ const matches = await api.all(null, scope, selector, options);
60
+ const index = selector.nth;
61
+ const listed = index === void 0 ? matches : matches.slice(index, index + 1);
62
+ value = listed.map((element) => element.textContent ?? "");
63
+ break;
64
+ }
65
+ case "text":
66
+ value = (await one()).textContent ?? "";
67
+ break;
68
+ case "value":
69
+ value = (await one()).value ?? "";
70
+ break;
71
+ case "attribute":
72
+ value = (await one()).getAttribute(arg ?? "");
73
+ break;
74
+ case "enabled": {
75
+ const element = await one();
76
+ value = {
77
+ disabled: element.disabled === true || element.closest(":disabled") !== null,
78
+ aria: element.getAttribute("aria-disabled")
79
+ };
80
+ break;
81
+ }
82
+ case "checked": {
83
+ const element = await one();
84
+ value = {
85
+ element,
86
+ aria: element.getAttribute("aria-checked"),
87
+ native: Boolean(element.checked)
88
+ };
89
+ break;
90
+ }
91
+ case "selected": {
92
+ const element = await one();
93
+ value = [...element.selectedOptions ?? []].map((option) => option.value);
94
+ break;
95
+ }
96
+ case "select": {
97
+ const element = await one();
98
+ value = {
99
+ element,
100
+ multiple: element.multiple,
101
+ selected: [...element.selectedOptions ?? []].map((option) => option.value),
102
+ // What each option can be chosen by: its value, or the label it shows.
103
+ options: [...element.options ?? []].map((option) => ({
104
+ value: option.value,
105
+ label: option.label.trim()
106
+ }))
107
+ };
108
+ break;
109
+ }
110
+ case "clear": {
111
+ const element = await one();
112
+ const direct = ["range", "date", "datetime-local", "month", "week", "time", "color"];
113
+ element.focus();
114
+ if (typeof element.select === "function") element.select();
115
+ else window.getSelection()?.selectAllChildren(element);
116
+ value = {
117
+ element,
118
+ direct: element.localName === "input" && direct.includes(element.type),
119
+ filled: (element.value ?? element.textContent ?? "") !== ""
120
+ };
121
+ break;
122
+ }
123
+ case "box":
124
+ value = api.box(await one());
125
+ break;
126
+ case "upload": {
127
+ const element = await one();
128
+ const control = element.localName === "label" ? element.control : null;
129
+ const input = control ?? element;
130
+ value = {
131
+ element: input,
132
+ target: api.fileTarget(element),
133
+ filled: (input.files?.length ?? 0) > 0
134
+ };
135
+ break;
136
+ }
137
+ case "focus":
138
+ ;
139
+ (await one()).focus();
140
+ value = null;
141
+ break;
142
+ case "blur":
143
+ ;
144
+ (await one()).blur();
145
+ value = null;
146
+ break;
147
+ }
148
+ return { kind: "ok", value, url: location.href };
149
+ } catch (error) {
150
+ const { name: name2 = "Error", message = String(error) } = error ?? {};
151
+ return { kind: "error", name: name2, message };
152
+ }
153
+ }
154
+
155
+ // src/webdriver.ts
156
+ var classic;
157
+ function classicScript() {
158
+ if (classic === void 0) classic = `return (${pageScript.toString()}).apply(null, arguments)`;
159
+ return classic;
160
+ }
161
+ async function callPage(driver, site, op, scope, selector, timeout, arg = null) {
162
+ checkTestIds(scope, selector);
163
+ const args = [
164
+ PAGE_API_GLOBAL,
165
+ op,
166
+ scope.map(encodeSelector),
167
+ encodeSelector(selector),
168
+ // The page cannot see this process's configure(), so the settings travel.
169
+ { testIdAttribute: getConfig().testIdAttribute, timeout },
170
+ arg
171
+ ];
172
+ const ask = async () => await driver.run(site, args);
173
+ let reply = await ask();
174
+ if (reply.kind === "missing") {
175
+ await driver.inject(site, injectSource());
176
+ reply = await ask();
177
+ }
178
+ if (reply.kind === "missing") {
179
+ throw new Error("harnessed: the resolver could not be injected into the page.");
180
+ }
181
+ if (reply.kind === "error") throw reviveStrictViolation(new PageError(reply.name, reply.message));
182
+ if (site.top) driver.sawUrl?.(reply.url);
183
+ return reply.value;
184
+ }
185
+ function splitAtFrames(scope) {
186
+ const hops = [];
187
+ let current = [];
188
+ for (const link of scope) {
189
+ if (link.frame === true) {
190
+ hops.push({ scope: current, link });
191
+ current = [];
192
+ } else {
193
+ current.push(link);
194
+ }
195
+ }
196
+ return { hops, rest: current };
197
+ }
198
+ var FrameAbsent = class extends Error {
199
+ name = "FrameAbsent";
200
+ };
201
+ async function walk(driver, scope, options, operation) {
202
+ const deadline = Date.now() + options.timeout;
203
+ const remaining = () => Math.max(0, deadline - Date.now());
204
+ const { hops, rest } = splitAtFrames(scope);
205
+ let site = driver.top();
206
+ let entered = false;
207
+ try {
208
+ for (const hop of hops) {
209
+ const op = options.waiting ? "frame" : "frameNow";
210
+ const found = await callPage(driver, site, op, hop.scope, hop.link, remaining());
211
+ if (found.element === null) throw new FrameAbsent();
212
+ const iframe = await driver.element(site, found.element);
213
+ await options.onFrame?.(iframe, { visible: found.visible });
214
+ const origin = { x: site.origin.x + found.origin.x, y: site.origin.y + found.origin.y };
215
+ site = await driver.enterFrame(site, iframe, origin);
216
+ entered = true;
217
+ }
218
+ return await operation(site, rest, remaining);
219
+ } finally {
220
+ if (entered) await driver.leaveFrames();
221
+ }
222
+ }
223
+
224
+ export { FrameAbsent, PageError, callPage, classicScript, isFrameEntryError, pageScript, walk };
225
+ //# sourceMappingURL=webdriver.js.map
226
+ //# sourceMappingURL=webdriver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/webdriver-page-script.ts","../src/webdriver.ts"],"names":["name"],"mappings":"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","file":"webdriver.js","sourcesContent":["import type { PageApi, PageApiOptions } from './page-api'\nimport type { WireSelector } from './wire'\n\n/**\n * What the in-page script does with the target it resolves. Reads happen in the\n * page, in the same round-trip as the resolution: a WebDriver command costs a\n * few milliseconds each way, and a read that first fetched an element reference\n * and then asked about it would pay that twice.\n */\nexport type PageOp =\n | 'frame'\n | 'frameNow'\n | 'element'\n | 'probe'\n | 'visible'\n | 'count'\n | 'texts'\n | 'text'\n | 'value'\n | 'attribute'\n | 'enabled'\n | 'checked'\n | 'selected'\n | 'select'\n | 'clear'\n | 'focus'\n | 'blur'\n | 'box'\n | 'upload'\n\n/**\n * The script's answer. A page error comes back as a value rather than a thrown\n * script error, so its name and message arrive intact — a thrown one reaches the\n * test wrapped in protocol noise, and under WebDriver Classic is logged as a\n * driver failure as well.\n */\nexport type PageReply =\n | { kind: 'missing' }\n | { kind: 'ok'; value: unknown; url: string }\n | { kind: 'error'; name: string; message: string }\n\n/** Raised in the page by the shared resolver: strictness, index and frame rules. */\nexport class PageError extends Error {\n constructor(name: string, message: string) {\n super(message)\n this.name = name\n }\n}\n\n/** The shared resolver's refusal of a frame it cannot enter. Never \"not there yet\". */\nexport function isFrameEntryError(error: unknown): boolean {\n return error instanceof PageError && error.name === 'FrameEntryError'\n}\n\n/**\n * An element as WebDriver returns it from a script, before the driver's client\n * wraps it (WebdriverIO's `$()`; selenium-webdriver hands back a `WebElement`\n * already).\n */\nexport type ElementRef = { 'element-6066-11e4-a52e-4f735466cecf': string }\n\n/**\n * Runs IN THE PAGE, serialised with `Function.prototype.toString`, so it must\n * close over nothing: every input arrives as an argument. Elements are\n * only ever returned as object properties, each at most once — WebDriver BiDi\n * serialises an element repeated in a result, or one inside an array, as `{}`.\n */\nexport async function pageScript(\n name: string,\n op: PageOp,\n scope: WireSelector[],\n selector: WireSelector,\n options: PageApiOptions,\n arg: string | null,\n): Promise<PageReply> {\n const api = (window as unknown as Record<string, PageApi | undefined>)[name]\n // Navigation replaces the window, and the resolver with it.\n if (api === undefined) return { kind: 'missing' }\n const one = () => api.one(null, scope, selector, options)\n /**\n * A frame link must land on an iframe. When it does not, walking through the\n * same link with the shared resolver raises its FrameEntryError, so the\n * refusal and its wording stay the resolver's rather than a copy kept here.\n */\n const iframe = async (found: Element | null) => {\n if (found !== null && found.localName !== 'iframe') {\n await api.count(null, [...scope, selector], selector, options)\n }\n return {\n element: found,\n origin: found === null ? { x: 0, y: 0 } : api.origin(found),\n // Whether the iframe is visible in its own document: content in a hidden\n // frame is hidden, whatever its own style says.\n visible: found !== null && api.visible(found),\n }\n }\n try {\n let value: unknown\n switch (op) {\n case 'frame':\n value = await iframe(await one())\n break\n case 'frameNow':\n value = await iframe(api.oneNow(null, scope, selector, options))\n break\n case 'element':\n value = { element: await one() }\n break\n case 'probe': {\n // `allNow` applies `nth` and answers at once; the caller decides what\n // more than one match means, with the whole scope chain in hand.\n const matches = api.allNow(null, scope, selector, options)\n value = { count: matches.length, element: matches.length === 1 ? matches[0] : null }\n break\n }\n case 'visible': {\n // `probe`'s answer, with the layout check made in the same round-trip.\n const matches = api.allNow(null, scope, selector, options)\n value = {\n count: matches.length,\n visible: matches.length === 1 && api.visible(matches[0] as Element),\n }\n break\n }\n case 'count':\n value = await api.count(null, scope, selector, options)\n break\n case 'texts': {\n const matches = await api.all(null, scope, selector, options)\n const index = (selector as { nth?: number }).nth\n const listed = index === undefined ? matches : matches.slice(index, index + 1)\n value = listed.map(element => element.textContent ?? '')\n break\n }\n case 'text':\n value = (await one()).textContent ?? ''\n break\n case 'value':\n value = ((await one()) as HTMLInputElement).value ?? ''\n break\n case 'attribute':\n value = (await one()).getAttribute(arg ?? '')\n break\n case 'enabled': {\n const element = (await one()) as HTMLInputElement\n // The property, and the closest `:disabled`, both see an ancestor\n // fieldset's `disabled`; the element's own attribute does not.\n value = {\n disabled: element.disabled === true || element.closest(':disabled') !== null,\n aria: element.getAttribute('aria-disabled'),\n }\n break\n }\n case 'checked': {\n const element = (await one()) as HTMLInputElement\n value = {\n element,\n aria: element.getAttribute('aria-checked'),\n native: Boolean(element.checked),\n }\n break\n }\n case 'selected': {\n const element = (await one()) as HTMLSelectElement\n value = [...(element.selectedOptions ?? [])].map(option => option.value)\n break\n }\n case 'select': {\n const element = (await one()) as HTMLSelectElement\n value = {\n element,\n multiple: element.multiple,\n selected: [...(element.selectedOptions ?? [])].map(option => option.value),\n // What each option can be chosen by: its value, or the label it shows.\n options: [...(element.options ?? [])].map(option => ({\n value: option.value,\n label: option.label.trim(),\n })),\n }\n break\n }\n case 'clear': {\n const element = (await one()) as HTMLInputElement\n // WebDriver sets these input types' values directly: they take no typing.\n const direct = ['range', 'date', 'datetime-local', 'month', 'week', 'time', 'color']\n element.focus()\n // Selected, so one Backspace deletes it all: a real edit, which fires the\n // input events a controlled component listens for.\n if (typeof element.select === 'function') element.select()\n else window.getSelection()?.selectAllChildren(element)\n value = {\n element,\n direct: element.localName === 'input' && direct.includes(element.type),\n filled: (element.value ?? element.textContent ?? '') !== '',\n }\n break\n }\n case 'box':\n // In this document's viewport; the driver adds the frames' origins.\n value = api.box(await one())\n break\n case 'upload': {\n const element = await one()\n const control = element.localName === 'label' ? (element as HTMLLabelElement).control : null\n // The input itself, where a label is the target: WebDriver types paths into the input.\n const input = (control ?? element) as HTMLInputElement\n value = {\n element: input,\n target: api.fileTarget(element),\n filled: (input.files?.length ?? 0) > 0,\n }\n break\n }\n case 'focus':\n ;((await one()) as HTMLElement).focus()\n value = null\n break\n case 'blur':\n ;((await one()) as HTMLElement).blur()\n value = null\n break\n }\n return { kind: 'ok', value, url: location.href }\n } catch (error) {\n const { name = 'Error', message = String(error) } = (error ?? {}) as Partial<Error>\n return { kind: 'error', name, message }\n }\n}\n","import { checkTestIds, getConfig, reviveStrictViolation } from '@harnessed-ts/core'\nimport type { Selector } from '@harnessed-ts/core'\nimport type { Point } from './box'\nimport { injectSource } from './inject'\nimport { PAGE_API_GLOBAL } from './global-name'\nimport { encodeSelector } from './wire'\nimport { PageError, pageScript } from './webdriver-page-script'\nimport type { ElementRef, PageOp, PageReply } from './webdriver-page-script'\n\nexport { isFrameEntryError, PageError, pageScript } from './webdriver-page-script'\nexport type { ElementRef, PageOp, PageReply } from './webdriver-page-script'\nexport type { Point } from './box'\n\n/** What `pageScript` takes, in order: what every `WebDriverLike.run` passes it. */\nexport type PageScriptArgs = Parameters<typeof pageScript>\n\n/**\n * A document a WebDriver client is working in: the top-level one, or a frame's\n * a walk entered. A driver extends it with whatever it needs to address that\n * document (a BiDi browsing context, say).\n */\nexport interface WebDriverSite {\n /** The top-level document, whose URL is the page's URL. */\n readonly top: boolean\n /** Where this document's viewport starts in the top-level one, in CSS pixels. */\n readonly origin: Point\n}\n\n/**\n * What the shared WebDriver code needs from a client — WebdriverIO,\n * selenium-webdriver under Nightwatch — and nothing more: running a script in a\n * document, and crossing into a frame. `S` is the client's site, `E` its element.\n */\nexport interface WebDriverLike<S extends WebDriverSite, E> {\n /** The top-level document. */\n top(): S\n /**\n * Runs `pageScript` in the site's document with these arguments and returns\n * its reply. Under WebDriver Classic, `classicScript()` is the body to send.\n */\n run(site: S, args: PageScriptArgs): Promise<unknown>\n /** Evaluates the resolver's injectable source in the site's document. */\n inject(site: S, source: string): Promise<void>\n /** An element the page script returned, as the client's own element. */\n element(site: S, ref: ElementRef): Promise<E>\n /**\n * Enters `iframe`, an element of `site`'s document, and returns the site for\n * the frame's document, at `origin` in the top-level viewport.\n */\n enterFrame(site: S, iframe: E, origin: Point): Promise<S>\n /**\n * Undoes what entering frames changed in the session — under Classic, switches\n * back to the top-level document. Called once after a walk that entered any.\n */\n leaveFrames(): Promise<void>\n /** Hears the top-level document's URL, which every call there brings back. */\n sawUrl?(url: string): void\n}\n\nlet classic: string | undefined\n\n/**\n * The page script as a classic Execute Script body, which awaits the promise\n * the script returns.\n */\nexport function classicScript(): string {\n if (classic === undefined) classic = `return (${pageScript.toString()}).apply(null, arguments)`\n return classic\n}\n\n/**\n * Runs one operation of the shared resolver in a document, injecting the\n * resolver first if the document does not have it yet.\n *\n * The presence check rides along with the call itself, so a document that\n * already has the resolver pays one round-trip, not two. Injecting happens once\n * per document: navigation replaces the window, and the resolver with it.\n */\nexport async function callPage<S extends WebDriverSite, E>(\n driver: WebDriverLike<S, E>,\n site: S,\n op: PageOp,\n scope: readonly Selector[],\n selector: Selector,\n timeout: number,\n arg: string | null = null,\n): Promise<unknown> {\n checkTestIds(scope, selector)\n const args: PageScriptArgs = [\n PAGE_API_GLOBAL,\n op,\n scope.map(encodeSelector),\n encodeSelector(selector),\n // The page cannot see this process's configure(), so the settings travel.\n { testIdAttribute: getConfig().testIdAttribute, timeout },\n arg,\n ]\n const ask = async () => (await driver.run(site, args)) as PageReply\n let reply = await ask()\n if (reply.kind === 'missing') {\n await driver.inject(site, injectSource())\n reply = await ask()\n }\n if (reply.kind === 'missing') {\n throw new Error('harnessed: the resolver could not be injected into the page.')\n }\n // A strict violation comes back as core's own class, as under every driver.\n if (reply.kind === 'error') throw reviveStrictViolation(new PageError(reply.name, reply.message))\n if (site.top) driver.sawUrl?.(reply.url)\n return reply.value\n}\n\n/** A scope chain cut at a frame link: the links before it, and the link itself. */\ninterface Hop {\n scope: Selector[]\n link: Selector\n}\n\nfunction splitAtFrames(scope: readonly Selector[]): { hops: Hop[]; rest: Selector[] } {\n const hops: Hop[] = []\n let current: Selector[] = []\n for (const link of scope) {\n if (link.frame === true) {\n hops.push({ scope: current, link })\n current = []\n } else {\n current.push(link)\n }\n }\n return { hops, rest: current }\n}\n\n/** A non-waiting walk found a frame link that is not on screen. */\nexport class FrameAbsent extends Error {\n override name = 'FrameAbsent'\n}\n\n/** What the page reports about an iframe a walk is about to enter. */\nexport interface FrameInfo {\n /** Whether the iframe is visible by layout in its own document. */\n readonly visible: boolean\n}\n\nexport interface WalkOptions<E> {\n /** Wait for each frame link, or answer at once by throwing FrameAbsent. */\n waiting: boolean\n /** The budget for every hop and the operation together. */\n timeout: number\n /** Called with each iframe element, from its own document, before entering it. */\n onFrame?: (iframe: E, info: FrameInfo) => Promise<void>\n}\n\n/**\n * Walks the scope chain's frame links, entering each frame, then runs the\n * operation in the innermost document with the links that remain.\n *\n * A WebDriver element belongs to one document: an element inside a frame cannot\n * be acted on from the top-level one, even where the page's own script could\n * reach it. So the shared resolver runs once per document — up to the iframe,\n * then inside it — and WebDriver does the crossing. That is also why\n * cross-origin frames work here, where an in-page resolver cannot enter them.\n *\n * Whatever entering changed in the session is undone afterwards, whatever\n * happened (`leaveFrames`).\n */\nexport async function walk<S extends WebDriverSite, E, T>(\n driver: WebDriverLike<S, E>,\n scope: readonly Selector[],\n options: WalkOptions<E>,\n operation: (site: S, scope: Selector[], remaining: () => number) => Promise<T>,\n): Promise<T> {\n const deadline = Date.now() + options.timeout\n const remaining = () => Math.max(0, deadline - Date.now())\n const { hops, rest } = splitAtFrames(scope)\n let site = driver.top()\n let entered = false\n try {\n for (const hop of hops) {\n const op = options.waiting ? 'frame' : 'frameNow'\n const found = (await callPage(driver, site, op, hop.scope, hop.link, remaining())) as {\n element: ElementRef | null\n origin: Point\n visible: boolean\n }\n if (found.element === null) throw new FrameAbsent()\n const iframe = await driver.element(site, found.element)\n await options.onFrame?.(iframe, { visible: found.visible })\n // The frame's document starts where its content box does, in the document around it.\n const origin = { x: site.origin.x + found.origin.x, y: site.origin.y + found.origin.y }\n site = await driver.enterFrame(site, iframe, origin)\n entered = true\n }\n return await operation(site, rest, remaining)\n } finally {\n if (entered) await driver.leaveFrames()\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harnessed-ts/resolve",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "The shared selector resolver behind the Testing Library-family drivers, plus an injectable build for drivers that resolve inside a remote page.",
5
5
  "keywords": [
6
6
  "testing",
@@ -41,6 +41,11 @@
41
41
  "import": "./dist/inject.js",
42
42
  "require": "./dist/inject.cjs"
43
43
  },
44
+ "./webdriver": {
45
+ "types": "./dist/webdriver.d.ts",
46
+ "import": "./dist/webdriver.js",
47
+ "require": "./dist/webdriver.cjs"
48
+ },
44
49
  "./inject.global.js": "./dist/inject.global.js",
45
50
  "./package.json": "./package.json"
46
51
  },
@@ -54,7 +59,7 @@
54
59
  "node": ">=22.12.0"
55
60
  },
56
61
  "dependencies": {
57
- "@harnessed-ts/core": "^0.6.0"
62
+ "@harnessed-ts/core": "^0.7.0"
58
63
  },
59
64
  "peerDependencies": {
60
65
  "@testing-library/dom": ">=10"
@@ -65,7 +70,7 @@
65
70
  "devDependencies": {
66
71
  "@testing-library/dom": "^10.4.2",
67
72
  "@types/jsdom": "^30.0.0",
68
- "@types/node": "^26.6.3",
73
+ "@types/node": "^26.6.5",
69
74
  "jsdom": "^30.1.1",
70
75
  "vitest": "^5.0.3"
71
76
  },