@harnessed-ts/resolve 0.0.0-stage → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/LICENSE +21 -0
  3. package/README.md +106 -2
  4. package/dist/box.d.ts +27 -0
  5. package/dist/box.d.ts.map +1 -0
  6. package/dist/chunk-BYG4HMLR.js +61 -0
  7. package/dist/chunk-BYG4HMLR.js.map +1 -0
  8. package/dist/files.d.ts +7 -0
  9. package/dist/files.d.ts.map +1 -0
  10. package/dist/global-name.d.ts +7 -0
  11. package/dist/global-name.d.ts.map +1 -0
  12. package/dist/index.cjs +315 -0
  13. package/dist/index.cjs.map +1 -0
  14. package/dist/index.d.ts +10 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +296 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/inject.cjs +68 -0
  19. package/dist/inject.cjs.map +1 -0
  20. package/dist/inject.d.ts +27 -0
  21. package/dist/inject.d.ts.map +1 -0
  22. package/dist/inject.global.d.ts +2 -0
  23. package/dist/inject.global.d.ts.map +1 -0
  24. package/dist/inject.global.js +70 -0
  25. package/dist/inject.js +3 -0
  26. package/dist/inject.js.map +1 -0
  27. package/dist/page-api.d.ts +57 -0
  28. package/dist/page-api.d.ts.map +1 -0
  29. package/dist/resolve.d.ts +38 -0
  30. package/dist/resolve.d.ts.map +1 -0
  31. package/dist/visible.d.ts +18 -0
  32. package/dist/visible.d.ts.map +1 -0
  33. package/dist/webdriver-page-script.d.ts +48 -0
  34. package/dist/webdriver-page-script.d.ts.map +1 -0
  35. package/dist/webdriver.cjs +272 -0
  36. package/dist/webdriver.cjs.map +1 -0
  37. package/dist/webdriver.d.ts +96 -0
  38. package/dist/webdriver.d.ts.map +1 -0
  39. package/dist/webdriver.js +226 -0
  40. package/dist/webdriver.js.map +1 -0
  41. package/dist/wire.d.ts +16 -0
  42. package/dist/wire.d.ts.map +1 -0
  43. package/package.json +79 -3
package/dist/index.js ADDED
@@ -0,0 +1,296 @@
1
+ import { testIdSync, describeScope, configure as configure$1, testIdPresenceSelector, getConfig, indexOutOfRange, strictViolation, timeoutFor } from '@harnessed-ts/core';
2
+ import { configure, buildQueries, within } from '@testing-library/dom';
3
+
4
+ // src/resolve.ts
5
+ var syncTestIdAttribute = testIdSync(
6
+ (testIdAttribute) => configure({ testIdAttribute })
7
+ );
8
+ function queryAllByPresence(container, css) {
9
+ return [...container.querySelectorAll(css)];
10
+ }
11
+ var [, , , findAllByPresence] = buildQueries(
12
+ queryAllByPresence,
13
+ (_container, css) => `Found multiple elements by: ${css}`,
14
+ (_container, css) => `Unable to find an element by: ${css}`
15
+ );
16
+ function presenceOf(selector) {
17
+ return testIdPresenceSelector(getConfig().testIdAttribute, selector.testId);
18
+ }
19
+ function queryAll(root, selector) {
20
+ syncTestIdAttribute();
21
+ const scoped = within(root);
22
+ switch (selector.type) {
23
+ case "role":
24
+ return scoped.queryAllByRole(selector.role, selector.options);
25
+ case "label":
26
+ return scoped.queryAllByLabelText(selector.text);
27
+ case "testId": {
28
+ const css = presenceOf(selector);
29
+ return css === void 0 ? scoped.queryAllByTestId(selector.testId) : queryAllByPresence(root, css);
30
+ }
31
+ case "text":
32
+ return scoped.queryAllByText(selector.text);
33
+ case "placeholder":
34
+ return scoped.queryAllByPlaceholderText(selector.text);
35
+ }
36
+ }
37
+ async function findOne(root, scope, selector, timeout) {
38
+ syncTestIdAttribute();
39
+ const immediate = queryAll(root, selector);
40
+ const resolved = immediate.length > 0 ? immediate : await findAll(root, selector, timeout);
41
+ return pick(resolved, scope, selector);
42
+ }
43
+ function pick(resolved, scope, selector) {
44
+ if (selector.nth !== void 0) {
45
+ const found = resolved[selector.nth];
46
+ if (found === void 0) {
47
+ throw indexOutOfRange(selector.nth, resolved.length, scope, selector);
48
+ }
49
+ return found;
50
+ }
51
+ if (resolved.length > 1) throw strictViolation(resolved.length, scope, selector);
52
+ return resolved[0];
53
+ }
54
+ async function findAll(root, selector, timeout) {
55
+ syncTestIdAttribute();
56
+ const scoped = within(root);
57
+ const options = { timeout: timeoutFor(timeout) };
58
+ switch (selector.type) {
59
+ case "role":
60
+ return scoped.findAllByRole(selector.role, selector.options, options);
61
+ case "label":
62
+ return scoped.findAllByLabelText(selector.text, void 0, options);
63
+ case "testId": {
64
+ const css = presenceOf(selector);
65
+ return css === void 0 ? scoped.findAllByTestId(selector.testId, void 0, options) : findAllByPresence(root, css, void 0, options);
66
+ }
67
+ case "text":
68
+ return scoped.findAllByText(selector.text, void 0, options);
69
+ case "placeholder":
70
+ return scoped.findAllByPlaceholderText(selector.text, void 0, options);
71
+ }
72
+ }
73
+ async function resolveScope(container, scope, timeout) {
74
+ let current = container;
75
+ for (const [index, step] of scope.entries()) {
76
+ const path = scope.slice(0, index);
77
+ const found = await findOne(current, path, step, timeout);
78
+ current = step.frame === true ? frameBody(found, path, step) : found;
79
+ }
80
+ return current;
81
+ }
82
+ var FrameEntryError = class extends Error {
83
+ name = "FrameEntryError";
84
+ };
85
+ function frameBody(element, scope, selector) {
86
+ if (element.localName !== "iframe") {
87
+ throw new FrameEntryError(
88
+ `harnessed: ${describeScope(scope, selector)} is marked as a frame but is a <${element.localName}>, not an <iframe>.`
89
+ );
90
+ }
91
+ const body = element.contentDocument?.body;
92
+ if (body == null) {
93
+ throw new FrameEntryError(
94
+ `harnessed: ${describeScope(scope, selector)} 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.`
95
+ );
96
+ }
97
+ return body;
98
+ }
99
+ async function resolveOne(container, scope, selector, timeout) {
100
+ const root = await resolveScope(container, scope, timeout);
101
+ return findOne(root, scope, selector, timeout);
102
+ }
103
+ async function countAll(container, scope, selector, timeout) {
104
+ let root;
105
+ try {
106
+ root = await resolveScope(container, scope, timeout);
107
+ } catch (error) {
108
+ if (error instanceof FrameEntryError) throw error;
109
+ return 0;
110
+ }
111
+ const matches = queryAll(root, selector);
112
+ if (selector.nth === void 0) return matches.length;
113
+ return matches[selector.nth] === void 0 ? 0 : 1;
114
+ }
115
+ function resolveScopeNow(container, scope) {
116
+ let current = container;
117
+ for (const [index, step] of scope.entries()) {
118
+ const path = scope.slice(0, index);
119
+ const matches = queryAll(current, step);
120
+ if (matches.length === 0) return null;
121
+ const found = pick(matches, path, step);
122
+ current = step.frame === true ? frameBody(found, path, step) : found;
123
+ }
124
+ return current;
125
+ }
126
+ function resolveOneNow(container, scope, selector) {
127
+ const root = resolveScopeNow(container, scope);
128
+ if (root === null) return null;
129
+ const matches = queryAll(root, selector);
130
+ if (matches.length === 0) return null;
131
+ return pick(matches, scope, selector);
132
+ }
133
+ function resolveAllNow(container, scope, selector) {
134
+ let root;
135
+ try {
136
+ root = resolveScopeNow(container, scope);
137
+ } catch (error) {
138
+ if (error instanceof FrameEntryError) throw error;
139
+ return [];
140
+ }
141
+ if (root === null) return [];
142
+ const matches = queryAll(root, selector);
143
+ if (selector.nth === void 0) return matches;
144
+ const found = matches[selector.nth];
145
+ return found === void 0 ? [] : [found];
146
+ }
147
+ async function resolveAll(container, scope, selector, timeout) {
148
+ return queryAll(await resolveScope(container, scope, timeout), selector);
149
+ }
150
+
151
+ // src/visible.ts
152
+ function isVisibleInLayout(element, home) {
153
+ if (!isVisibleInOwnDocument(element)) return false;
154
+ const document = element.ownerDocument;
155
+ if (document === home) return true;
156
+ const frame = document.defaultView?.frameElement;
157
+ return frame == null || isVisibleInLayout(frame, home);
158
+ }
159
+ function isVisibleInOwnDocument(element) {
160
+ const view = element.ownerDocument.defaultView;
161
+ if (view == null) return false;
162
+ const style = view.getComputedStyle(element);
163
+ if (style.display === "contents") {
164
+ return [...element.children].some((child) => isVisibleInOwnDocument(child));
165
+ }
166
+ if (style.visibility !== "visible") return false;
167
+ const box = element.getBoundingClientRect();
168
+ return box.width > 0 && box.height > 0;
169
+ }
170
+
171
+ // src/box.ts
172
+ function boxInOwnDocument(element) {
173
+ if (element.getClientRects().length === 0) return null;
174
+ const { x, y, width, height } = element.getBoundingClientRect();
175
+ return { x, y, width, height };
176
+ }
177
+ function frameOrigin(iframe) {
178
+ const { x, y } = iframe.getBoundingClientRect();
179
+ const style = iframe.ownerDocument.defaultView?.getComputedStyle(iframe);
180
+ const px = (value) => Number.parseFloat(value ?? "") || 0;
181
+ return {
182
+ x: x + px(style?.borderLeftWidth) + px(style?.paddingLeft),
183
+ y: y + px(style?.borderTopWidth) + px(style?.paddingTop)
184
+ };
185
+ }
186
+ function boundingBoxOf(element, home) {
187
+ const box = boxInOwnDocument(element);
188
+ if (box === null) return { x: 0, y: 0, width: 0, height: 0 };
189
+ let document = element.ownerDocument;
190
+ while (document !== home) {
191
+ const frame = document.defaultView?.frameElement;
192
+ if (frame == null) break;
193
+ const origin = frameOrigin(frame);
194
+ box.x += origin.x;
195
+ box.y += origin.y;
196
+ document = frame.ownerDocument;
197
+ }
198
+ return box;
199
+ }
200
+
201
+ // src/files.ts
202
+ function fileInputOf(element) {
203
+ const target = element.localName === "label" ? element.control : element;
204
+ if (target === null) return "<label> with no control";
205
+ if (target.localName !== "input") return `<${target.localName}>`;
206
+ const input = target;
207
+ return input.type === "file" ? input : `<input type="${input.type}">`;
208
+ }
209
+
210
+ // src/global-name.ts
211
+ var PAGE_API_GLOBAL = "__harnessedResolve";
212
+
213
+ // src/wire.ts
214
+ function isRegExp(value) {
215
+ return Object.prototype.toString.call(value) === "[object RegExp]";
216
+ }
217
+ function encodeValue(value) {
218
+ if (isRegExp(value)) return { $regexp: value.source, flags: value.flags };
219
+ if (Array.isArray(value)) return value.map(encodeValue);
220
+ if (typeof value === "object" && value !== null) {
221
+ return Object.fromEntries(
222
+ Object.entries(value).map(([key, inner]) => [key, encodeValue(inner)])
223
+ );
224
+ }
225
+ return value;
226
+ }
227
+ function isWireRegExp(value) {
228
+ return typeof value === "object" && value !== null && typeof value.$regexp === "string" && typeof value.flags === "string";
229
+ }
230
+ function decodeValue(value) {
231
+ if (isRegExp(value)) return new RegExp(value.source, value.flags);
232
+ if (isWireRegExp(value)) return new RegExp(value.$regexp, value.flags);
233
+ if (Array.isArray(value)) return value.map(decodeValue);
234
+ if (typeof value === "object" && value !== null) {
235
+ return Object.fromEntries(
236
+ Object.entries(value).map(([key, inner]) => [key, decodeValue(inner)])
237
+ );
238
+ }
239
+ return value;
240
+ }
241
+ function encodeSelector(selector) {
242
+ return encodeValue(selector);
243
+ }
244
+ function decodeSelector(selector) {
245
+ return decodeValue(selector);
246
+ }
247
+
248
+ // src/page-api.ts
249
+ function createPageApi(document) {
250
+ const start = (root) => root ?? document.body;
251
+ const apply = (options) => configure$1({ testIdAttribute: options.testIdAttribute });
252
+ const decode = (scope, selector) => [
253
+ scope.map(decodeSelector),
254
+ decodeSelector(selector)
255
+ ];
256
+ return {
257
+ async count(root, scope, selector, options) {
258
+ apply(options);
259
+ const [steps, target] = decode(scope, selector);
260
+ return countAll(start(root), steps, target, options.timeout);
261
+ },
262
+ async one(root, scope, selector, options) {
263
+ apply(options);
264
+ const [steps, target] = decode(scope, selector);
265
+ return resolveOne(start(root), steps, target, options.timeout);
266
+ },
267
+ async all(root, scope, selector, options) {
268
+ apply(options);
269
+ const [steps, target] = decode(scope, selector);
270
+ return resolveAll(start(root), steps, target, options.timeout);
271
+ },
272
+ oneNow(root, scope, selector, options) {
273
+ apply(options);
274
+ const [steps, target] = decode(scope, selector);
275
+ return resolveOneNow(start(root), steps, target);
276
+ },
277
+ allNow(root, scope, selector, options) {
278
+ apply(options);
279
+ const [steps, target] = decode(scope, selector);
280
+ return resolveAllNow(start(root), steps, target);
281
+ },
282
+ visible(element) {
283
+ return isVisibleInLayout(element, element.ownerDocument);
284
+ },
285
+ box: boxInOwnDocument,
286
+ origin: frameOrigin,
287
+ fileTarget(element) {
288
+ const input = fileInputOf(element);
289
+ return typeof input === "string" ? input : { multiple: input.multiple };
290
+ }
291
+ };
292
+ }
293
+
294
+ export { FrameEntryError, PAGE_API_GLOBAL, boundingBoxOf, boxInOwnDocument, countAll, createPageApi, decodeSelector, encodeSelector, fileInputOf, frameOrigin, isVisibleInLayout, queryAll, resolveAll, resolveAllNow, resolveOne, resolveOneNow, resolveScope, resolveScopeNow };
295
+ //# sourceMappingURL=index.js.map
296
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/resolve.ts","../src/visible.ts","../src/box.ts","../src/files.ts","../src/global-name.ts","../src/wire.ts","../src/page-api.ts"],"names":["configureTestingLibrary","configure"],"mappings":"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","file":"index.js","sourcesContent":["import {\n describeScope,\n getConfig,\n indexOutOfRange,\n strictViolation,\n testIdPresenceSelector,\n testIdSync,\n timeoutFor,\n} from '@harnessed-ts/core'\nimport type { Selector } from '@harnessed-ts/core'\nimport { buildQueries, configure as configureTestingLibrary, within } from '@testing-library/dom'\nimport type { ByRoleMatcher } from '@testing-library/dom'\n\n/** `Selector` keeps `role` a plain string so it stays driver-agnostic; each driver\n * casts it to its own role union at the boundary. */\ntype RoleName = ByRoleMatcher\n\nconst syncTestIdAttribute = testIdSync(testIdAttribute =>\n configureTestingLibrary({ testIdAttribute }),\n)\n\n/** Descendants carrying a presence-style test id: `[data-test-x]`, any value. */\nfunction queryAllByPresence(container: HTMLElement, css: string): HTMLElement[] {\n return [...container.querySelectorAll<HTMLElement>(css)]\n}\n\n// Built the way Testing Library builds its own queries, so a presence test id\n// waits, and reports \"Unable to find …\" when it gives up, exactly as `*ByTestId` does.\nconst [, , , findAllByPresence] = buildQueries(\n queryAllByPresence,\n (_container, css: string) => `Found multiple elements by: ${css}`,\n (_container, css: string) => `Unable to find an element by: ${css}`,\n)\n\n/** The presence selector for a test id under a `data-test-*` config, if that is the mode. */\nfunction presenceOf(selector: Selector & { type: 'testId' }): string | undefined {\n return testIdPresenceSelector(getConfig().testIdAttribute, selector.testId)\n}\n\n/** Every match for one selector, without waiting. Zero is an answer. */\nexport function queryAll(root: HTMLElement, selector: Selector): HTMLElement[] {\n syncTestIdAttribute()\n const scoped = within(root)\n switch (selector.type) {\n case 'role':\n return scoped.queryAllByRole(selector.role as RoleName, selector.options)\n case 'label':\n return scoped.queryAllByLabelText(selector.text)\n case 'testId': {\n const css = presenceOf(selector)\n return css === undefined\n ? scoped.queryAllByTestId(selector.testId)\n : queryAllByPresence(root, css)\n }\n case 'text':\n return scoped.queryAllByText(selector.text)\n case 'placeholder':\n return scoped.queryAllByPlaceholderText(selector.text)\n }\n}\n\n/**\n * One match, waiting for it to appear. Strict: several matches is an error.\n *\n * The non-waiting query runs first. Testing Library's `findBy*` is\n * `waitFor(getBy*)`, so a \"found multiple elements\" error is retried for the full\n * timeout before surfacing — but more than one match never becomes one by\n * waiting, and Playwright rejects at once. Checking up front keeps the two\n * drivers in step and turns a timeout into an immediate, readable failure.\n *\n * The check is applied to the waited result too. Otherwise the guarantee would\n * hold only for nodes that happened to be on screen already, and a target that\n * renders late as two nodes would still sit out the whole timeout.\n */\nasync function findOne(\n root: HTMLElement,\n scope: readonly Selector[],\n selector: Selector,\n timeout?: number,\n): Promise<HTMLElement> {\n syncTestIdAttribute()\n\n const immediate = queryAll(root, selector)\n const resolved = immediate.length > 0 ? immediate : await findAll(root, selector, timeout)\n return pick(resolved, scope, selector)\n}\n\n/** The one node a strict operation takes from a set of matches. */\nfunction pick(\n resolved: readonly HTMLElement[],\n scope: readonly Selector[],\n selector: Selector,\n): HTMLElement {\n if (selector.nth !== undefined) {\n const found = resolved[selector.nth]\n if (found === undefined) {\n throw indexOutOfRange(selector.nth, resolved.length, scope, selector)\n }\n return found\n }\n\n if (resolved.length > 1) throw strictViolation(resolved.length, scope, selector)\n return resolved[0]!\n}\n\nasync function findAll(\n root: HTMLElement,\n selector: Selector,\n timeout?: number,\n): Promise<HTMLElement[]> {\n syncTestIdAttribute()\n const scoped = within(root)\n const options = { timeout: timeoutFor(timeout) }\n switch (selector.type) {\n case 'role':\n return scoped.findAllByRole(selector.role as RoleName, selector.options, options)\n case 'label':\n return scoped.findAllByLabelText(selector.text, undefined, options)\n case 'testId': {\n const css = presenceOf(selector)\n return css === undefined\n ? scoped.findAllByTestId(selector.testId, undefined, options)\n : findAllByPresence(root, css, undefined, options)\n }\n case 'text':\n return scoped.findAllByText(selector.text, undefined, options)\n case 'placeholder':\n return scoped.findAllByPlaceholderText(selector.text, undefined, options)\n }\n}\n\n/**\n * Walks the scope chain down to the element the final selector is queried within.\n * Waiting is correct here: a container that has not rendered yet is not the same\n * thing as a target that is absent.\n */\nexport async function resolveScope(\n container: HTMLElement,\n scope: readonly Selector[],\n timeout?: number,\n): Promise<HTMLElement> {\n let current = container\n for (const [index, step] of scope.entries()) {\n // Each link is reported with the path that reached it, so an ambiguous\n // container names the container rather than the leaf you asked for.\n const path = scope.slice(0, index)\n const found = await findOne(current, path, step, timeout)\n current = step.frame === true ? frameBody(found, path, step) : found\n }\n return current\n}\n\n/** A frame link that cannot be entered — a wiring mistake, never \"not rendered yet\". */\nexport class FrameEntryError extends Error {\n override name = 'FrameEntryError'\n}\n\n/**\n * The body a frame link's children are queried within. Only a same-origin frame\n * exposes one — a cross-origin document is unreachable from script by design, and\n * no amount of waiting changes that, so this refuses at once.\n */\nfunction frameBody(\n element: HTMLElement,\n scope: readonly Selector[],\n selector: Selector,\n): HTMLElement {\n // localName, not instanceof: a frame nested in a frame belongs to another realm.\n if (element.localName !== 'iframe') {\n throw new FrameEntryError(\n `harnessed: ${describeScope(scope, selector)} is marked as a frame but is a <${element.localName}>, not an <iframe>.`,\n )\n }\n const body = (element as HTMLIFrameElement).contentDocument?.body\n if (body == null) {\n throw new FrameEntryError(\n `harnessed: ${describeScope(scope, selector)} has no reachable document: it is ` +\n `cross-origin, or its document has no body. The dom driver enters same-origin ` +\n `frames only; drive a cross-origin one with a browser driver.`,\n )\n }\n return body\n}\n\n/** The single node a strict operation acts on. */\nexport async function resolveOne(\n container: HTMLElement,\n scope: readonly Selector[],\n selector: Selector,\n timeout?: number,\n): Promise<HTMLElement> {\n const root = await resolveScope(container, scope, timeout)\n // The errors already carry the scope path, so there is nothing to wrap.\n return findOne(root, scope, selector, timeout)\n}\n\n/**\n * How many nodes match. The scope chain still waits, but the final count uses the\n * non-waiting query so that zero answers immediately rather than throwing after a\n * retry timeout. This is the behaviour Playwright already had.\n */\nexport async function countAll(\n container: HTMLElement,\n scope: readonly Selector[],\n selector: Selector,\n timeout?: number,\n): Promise<number> {\n let root: HTMLElement\n try {\n root = await resolveScope(container, scope, timeout)\n } catch (error) {\n if (error instanceof FrameEntryError) throw error\n // The scope itself is not on screen, so nothing inside it can be either.\n return 0\n }\n const matches = queryAll(root, selector)\n if (selector.nth === undefined) return matches.length\n return matches[selector.nth] === undefined ? 0 : 1\n}\n\n// --- without waiting ------------------------------------------------------\n//\n// For a driver whose own runtime does the retrying — TestCafe's Selector, a\n// remote driver polling from Node — every lookup has to answer at once. These\n// apply exactly the same strictness, index, and frame rules as the waiting\n// forms above: anything those reject at once, these throw. The one difference\n// is that \"nothing matches yet\" is `null` instead of a wait.\n\n/** The scope chain's innermost element now, or `null` if a link is not on screen. */\nexport function resolveScopeNow(\n container: HTMLElement,\n scope: readonly Selector[],\n): HTMLElement | null {\n let current = container\n for (const [index, step] of scope.entries()) {\n const path = scope.slice(0, index)\n const matches = queryAll(current, step)\n if (matches.length === 0) return null\n const found = pick(matches, path, step)\n current = step.frame === true ? frameBody(found, path, step) : found\n }\n return current\n}\n\n/** The single node a strict operation acts on now, or `null` if it is not on screen. */\nexport function resolveOneNow(\n container: HTMLElement,\n scope: readonly Selector[],\n selector: Selector,\n): HTMLElement | null {\n const root = resolveScopeNow(container, scope)\n if (root === null) return null\n const matches = queryAll(root, selector)\n if (matches.length === 0) return null\n return pick(matches, scope, selector)\n}\n\n/**\n * Every match now, agreeing with `countAll` on every scope it cannot resolve:\n * an absent or ambiguous scope is an empty list, and only a frame that cannot\n * be entered throws.\n */\nexport function resolveAllNow(\n container: HTMLElement,\n scope: readonly Selector[],\n selector: Selector,\n): HTMLElement[] {\n let root: HTMLElement | null\n try {\n root = resolveScopeNow(container, scope)\n } catch (error) {\n if (error instanceof FrameEntryError) throw error\n return []\n }\n if (root === null) return []\n const matches = queryAll(root, selector)\n if (selector.nth === undefined) return matches\n const found = matches[selector.nth]\n return found === undefined ? [] : [found]\n}\n\n/**\n * Every match for a list operation: the scope waits, the list does not, and\n * `nth` is ignored — each result is addressed by its own index afterwards. The\n * one definition of \"the list\" for in-process and injected drivers alike.\n */\nexport async function resolveAll(\n container: HTMLElement,\n scope: readonly Selector[],\n selector: Selector,\n timeout?: number,\n): Promise<HTMLElement[]> {\n return queryAll(await resolveScope(container, scope, timeout), selector)\n}\n","/**\n * Whether an element is visible by layout — Playwright's definition, for drivers\n * that run in a real browser beside the page (Vitest browser mode, Ember, Cypress).\n * jsdom computes no layout, so the dom driver cannot use this and reads computed\n * style instead.\n *\n * Visible means a non-empty box that `visibility` does not hide. `display: none`\n * anywhere above gives an empty box, so it needs no check of its own. Opacity\n * does not count: a transparent box is still there to click, and Playwright\n * reports it visible.\n *\n * Inside a frame, the iframe has to be visible too — a hidden frame hides its\n * content. The walk crosses frames up to `home` (by default, every reachable\n * one), so a driver passes its own document to stop at the frames its scope\n * actually entered.\n */\nexport function isVisibleInLayout(element: Element, home?: Document): boolean {\n if (!isVisibleInOwnDocument(element)) return false\n const document = element.ownerDocument\n if (document === home) return true\n // Null at the top, and for a cross-origin parent, which script cannot see into.\n const frame = document.defaultView?.frameElement\n return frame == null || isVisibleInLayout(frame, home)\n}\n\nfunction isVisibleInOwnDocument(element: Element): boolean {\n const view = element.ownerDocument.defaultView\n // A node with no window is detached from any rendering.\n if (view == null) return false\n const style = view.getComputedStyle(element)\n // `display: contents` draws no box of its own, so it is as visible as what it\n // contains.\n if (style.display === 'contents') {\n return [...element.children].some(child => isVisibleInOwnDocument(child))\n }\n if (style.visibility !== 'visible') return false\n const box = element.getBoundingClientRect()\n return box.width > 0 && box.height > 0\n}\n","import type { BoundingBox } from '@harnessed-ts/core'\n\n/** A point in a document's viewport, in CSS pixels. */\nexport interface Point {\n x: number\n y: number\n}\n\n/**\n * An element's border box in its own document's viewport, or `null` when it\n * renders no box at all (`display: none`, or anything inside it). Every driver\n * reads `null` as all zeros, which is what Playwright and Puppeteer — whose\n * native boxes are `null` there — are normalised to.\n */\nexport function boxInOwnDocument(element: Element): BoundingBox | null {\n if (element.getClientRects().length === 0) return null\n const { x, y, width, height } = element.getBoundingClientRect()\n return { x, y, width, height }\n}\n\n/**\n * Where an iframe's document is drawn, in the iframe's own document: the corner\n * of its content box, inside its border and padding. The same offset Playwright\n * and Puppeteer add when they report a framed element's box.\n */\nexport function frameOrigin(iframe: Element): Point {\n const { x, y } = iframe.getBoundingClientRect()\n const style = iframe.ownerDocument.defaultView?.getComputedStyle(iframe)\n const px = (value: string | undefined): number => Number.parseFloat(value ?? '') || 0\n return {\n x: x + px(style?.borderLeftWidth) + px(style?.paddingLeft),\n y: y + px(style?.borderTopWidth) + px(style?.paddingTop),\n }\n}\n\n/**\n * An element's box relative to the viewport of `home` — the document the\n * driver drives — adding the origin of every same-origin frame crossed on the\n * way up. For drivers that resolve inside the page; under jsdom everything is\n * zeros, because jsdom computes no layout.\n */\nexport function boundingBoxOf(element: Element, home?: Document): BoundingBox {\n const box = boxInOwnDocument(element)\n if (box === null) return { x: 0, y: 0, width: 0, height: 0 }\n let document = element.ownerDocument\n while (document !== home) {\n // Null at the top, and for a cross-origin parent, which script cannot see into.\n const frame = document.defaultView?.frameElement\n if (frame == null) break\n const origin = frameOrigin(frame)\n box.x += origin.x\n box.y += origin.y\n document = frame.ownerDocument\n }\n return box\n}\n","/**\n * The file input an upload sets: the element itself, or the control of a\n * `<label>` — user-event's and Playwright's rule both. Anything else comes back\n * described as markup, for core's `checkFileTarget` to refuse in one wording.\n */\nexport function fileInputOf(element: Element): HTMLInputElement | string {\n const target =\n element.localName === 'label' ? (element as HTMLLabelElement).control : (element as Element)\n if (target === null) return '<label> with no control'\n if (target.localName !== 'input') return `<${target.localName}>`\n const input = target as HTMLInputElement\n return input.type === 'file' ? input : `<input type=\"${input.type}\">`\n}\n","/**\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 { configure } from '@harnessed-ts/core'\nimport type { BoundingBox, FileTarget, Selector } from '@harnessed-ts/core'\nimport { boxInOwnDocument, frameOrigin } from './box'\nimport type { Point } from './box'\nimport { fileInputOf } from './files'\nimport { PAGE_API_GLOBAL } from './global-name'\nimport { countAll, resolveAll, resolveAllNow, resolveOne, resolveOneNow } from './resolve'\nimport { isVisibleInLayout } from './visible'\nimport { decodeSelector } from './wire'\nimport type { WireSelector } from './wire'\n\nexport { PAGE_API_GLOBAL }\n\n/**\n * Settings a remote driver passes on every call. The page has no access to the\n * test process's `configure()`, so the runtime config travels with the call\n * rather than being pushed once.\n */\n/** A selector as handed over the wire, or as-is when caller and page share a realm. */\ntype Wire = Selector | WireSelector\n\nexport interface PageApiOptions {\n testIdAttribute: string\n timeout: number\n}\n\n/**\n * The resolver as seen from inside a page a driver does not share a realm with\n * — WebdriverIO, Puppeteer, TestCafe. Every argument is serialisable except\n * `root`, which is an element handle the driver passes back in (or `null` for\n * the document body).\n *\n * The same functions back the dom driver, so role, label, strictness, index\n * and frame semantics cannot differ between a driver resolving in-process and\n * one resolving through this.\n */\nexport interface PageApi {\n /** How many nodes match. The scope waits; the count does not. */\n count(\n root: Element | null,\n scope: Wire[],\n selector: Wire,\n options: PageApiOptions,\n ): Promise<number>\n /** The single node a strict operation acts on, waiting for it. */\n one(\n root: Element | null,\n scope: Wire[],\n selector: Wire,\n options: PageApiOptions,\n ): Promise<Element>\n /** Every match, after waiting for the scope. `nth` is ignored, as for any list. */\n all(\n root: Element | null,\n scope: Wire[],\n selector: Wire,\n options: PageApiOptions,\n ): Promise<Element[]>\n /** The single node now, or `null`. For runtimes that do their own retrying. */\n oneNow(\n root: Element | null,\n scope: Wire[],\n selector: Wire,\n options: PageApiOptions,\n ): Element | null\n /** Every match now. */\n allNow(root: Element | null, scope: Wire[], selector: Wire, options: PageApiOptions): Element[]\n /**\n * Whether an element is visible by layout, within its own document only. A\n * driver that enters frames from outside the page asks about each iframe it\n * crossed in that iframe's own document, which also covers a cross-origin\n * frame no in-page walk could cross.\n */\n visible(element: Element): boolean\n /**\n * The element's box in its own document's viewport, or `null` when it draws\n * none. A driver that enters frames from outside adds each crossed frame's\n * `origin` itself.\n */\n box(element: Element): BoundingBox | null\n /** Where an iframe's document is drawn, in the iframe's own document. */\n origin(iframe: Element): Point\n /** What an upload would set: the file input's `multiple`, or what the element is instead. */\n fileTarget(element: Element): FileTarget\n}\n\nexport function createPageApi(document: Document): PageApi {\n const start = (root: Element | null): HTMLElement => (root ?? document.body) as HTMLElement\n // Only the test-id attribute: every waiting call passes its timeout\n // explicitly. Note this writes the page's harnessed config, so the API belongs\n // in a page that runs no harnesses of its own — which is every remote page.\n const apply = (options: PageApiOptions): void =>\n configure({ testIdAttribute: options.testIdAttribute })\n const decode = (scope: Wire[], selector: Wire): [Selector[], Selector] => [\n scope.map(decodeSelector),\n decodeSelector(selector),\n ]\n\n return {\n async count(root, scope, selector, options) {\n apply(options)\n const [steps, target] = decode(scope, selector)\n return countAll(start(root), steps, target, options.timeout)\n },\n async one(root, scope, selector, options) {\n apply(options)\n const [steps, target] = decode(scope, selector)\n return resolveOne(start(root), steps, target, options.timeout)\n },\n async all(root, scope, selector, options) {\n apply(options)\n const [steps, target] = decode(scope, selector)\n return resolveAll(start(root), steps, target, options.timeout)\n },\n oneNow(root, scope, selector, options) {\n apply(options)\n const [steps, target] = decode(scope, selector)\n return resolveOneNow(start(root), steps, target)\n },\n allNow(root, scope, selector, options) {\n apply(options)\n const [steps, target] = decode(scope, selector)\n return resolveAllNow(start(root), steps, target)\n },\n visible(element) {\n return isVisibleInLayout(element, element.ownerDocument)\n },\n box: boxInOwnDocument,\n origin: frameOrigin,\n fileTarget(element) {\n const input = fileInputOf(element)\n return typeof input === 'string' ? input : { multiple: input.multiple }\n },\n }\n}\n"]}
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs');
4
+ var os = require('os');
5
+ var path = require('path');
6
+ var url = require('url');
7
+
8
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
9
+ // src/inject.ts
10
+
11
+ // src/global-name.ts
12
+ var PAGE_API_GLOBAL = "__harnessedResolve";
13
+
14
+ // src/wire.ts
15
+ function isRegExp(value) {
16
+ return Object.prototype.toString.call(value) === "[object RegExp]";
17
+ }
18
+ function encodeValue(value) {
19
+ if (isRegExp(value)) return { $regexp: value.source, flags: value.flags };
20
+ if (Array.isArray(value)) return value.map(encodeValue);
21
+ if (typeof value === "object" && value !== null) {
22
+ return Object.fromEntries(
23
+ Object.entries(value).map(([key, inner]) => [key, encodeValue(inner)])
24
+ );
25
+ }
26
+ return value;
27
+ }
28
+ function encodeSelector(selector) {
29
+ return encodeValue(selector);
30
+ }
31
+
32
+ // src/inject.ts
33
+ function injectPath() {
34
+ if (typeof __filename === "string") return path.join(path.dirname(__filename), "inject.global.js");
35
+ 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('inject.cjs', document.baseURI).href))));
36
+ }
37
+ var cached;
38
+ function injectSource() {
39
+ if (cached === void 0) cached = fs.readFileSync(injectPath(), "utf8");
40
+ return cached;
41
+ }
42
+ var uploads;
43
+ var written = 0;
44
+ async function filesOnDisk(files) {
45
+ if (uploads === void 0) {
46
+ const root2 = fs.mkdtempSync(path.join(os.tmpdir(), "harnessed-uploads-"));
47
+ uploads = root2;
48
+ process.once("exit", () => fs.rmSync(root2, { recursive: true, force: true }));
49
+ }
50
+ const root = uploads;
51
+ return Promise.all(
52
+ files.map(async (file) => {
53
+ const dir = path.join(root, String(written++));
54
+ fs.mkdirSync(dir);
55
+ const path$1 = path.join(dir, path.basename(file.name) || "upload");
56
+ fs.writeFileSync(path$1, new Uint8Array(await file.arrayBuffer()));
57
+ return path$1;
58
+ })
59
+ );
60
+ }
61
+
62
+ exports.PAGE_API_GLOBAL = PAGE_API_GLOBAL;
63
+ exports.encodeSelector = encodeSelector;
64
+ exports.filesOnDisk = filesOnDisk;
65
+ exports.injectPath = injectPath;
66
+ exports.injectSource = injectSource;
67
+ //# sourceMappingURL=inject.cjs.map
68
+ //# sourceMappingURL=inject.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/global-name.ts","../src/wire.ts","../src/inject.ts"],"names":["join","dirname","fileURLToPath","readFileSync","root","mkdtempSync","tmpdir","rmSync","mkdirSync","path","basename","writeFileSync"],"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,OAAOA,UAAKC,YAAA,CAAQ,UAAU,GAAG,kBAAkB,CAAA;AACvF,EAAA,OAAOC,kBAAc,IAAI,GAAA,CAAI,oBAAA,EAAsB,4PAAe,CAAC,CAAA;AACrE;AAEA,IAAI,MAAA;AAMG,SAAS,YAAA,GAAuB;AACrC,EAAA,IAAI,WAAW,MAAA,EAAW,MAAA,GAASC,eAAA,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,MAAMC,QAAOC,cAAA,CAAYL,SAAA,CAAKM,SAAA,EAAO,EAAG,oBAAoB,CAAC,CAAA;AAC7D,IAAA,OAAA,GAAUF,KAAAA;AACV,IAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,MAAMG,SAAA,CAAOH,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,GAAMJ,SAAA,CAAK,IAAA,EAAM,MAAA,CAAO,SAAS,CAAC,CAAA;AACxC,MAAAQ,YAAA,CAAU,GAAG,CAAA;AAEb,MAAA,MAAMC,SAAOT,SAAA,CAAK,GAAA,EAAKU,cAAS,IAAA,CAAK,IAAI,KAAK,QAAQ,CAAA;AACtD,MAAAC,gBAAA,CAAcF,QAAM,IAAI,UAAA,CAAW,MAAM,IAAA,CAAK,WAAA,EAAa,CAAC,CAAA;AAC5D,MAAA,OAAOA,MAAA;AAAA,IACT,CAAC;AAAA,GACH;AACF","file":"inject.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"]}
@@ -0,0 +1,27 @@
1
+ export { PAGE_API_GLOBAL } from './global-name';
2
+ export { encodeSelector } from './wire';
3
+ export type { WireSelector } from './wire';
4
+ export type { PageApi, PageApiOptions } from './page-api';
5
+ /**
6
+ * Absolute path to the injectable build, for drivers that inject by path
7
+ * (TestCafe's `clientScripts`, Puppeteer's `addScriptTag`).
8
+ */
9
+ export declare function injectPath(): string;
10
+ /**
11
+ * The injectable build's source, for drivers that inject by evaluating a string
12
+ * (WebdriverIO's `execute`, Puppeteer's `evaluate`). Read once per process.
13
+ */
14
+ export declare function injectSource(): string;
15
+ /**
16
+ * Writes files to disk for a driver whose browser only takes file paths
17
+ * (Puppeteer's `uploadFile`, WebDriver's file input, TestCafe's
18
+ * `setFilesToUpload`), and returns the paths in order.
19
+ *
20
+ * Each file gets a directory of its own, so its name survives as the path's
21
+ * last segment even when two files share one. The browser reads the file when
22
+ * the page does — after the upload resolves — so nothing is deleted until the
23
+ * process exits. What travels is the name and the bytes: the browser infers
24
+ * `type` from the extension, and `lastModified` is the time of writing.
25
+ */
26
+ export declare function filesOnDisk(files: readonly File[]): Promise<string[]>;
27
+ //# sourceMappingURL=inject.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inject.d.ts","sourceRoot":"","sources":["../src/inject.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AACvC,YAAY,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAC1C,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAIzD;;;GAGG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAKnC;AAID;;;GAGG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAGrC;AAKD;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAiB3E"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=inject.global.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inject.global.d.ts","sourceRoot":"","sources":["../src/inject.global.ts"],"names":[],"mappings":""}