@harnessed-ts/resolve 0.0.0-stage → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/inject.js ADDED
@@ -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=inject.js.map
61
+ //# sourceMappingURL=inject.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":"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"]}
@@ -0,0 +1,57 @@
1
+ import type { BoundingBox, FileTarget, Selector } from '@harnessed-ts/core';
2
+ import type { Point } from './box';
3
+ import { PAGE_API_GLOBAL } from './global-name';
4
+ import type { WireSelector } from './wire';
5
+ export { PAGE_API_GLOBAL };
6
+ /**
7
+ * Settings a remote driver passes on every call. The page has no access to the
8
+ * test process's `configure()`, so the runtime config travels with the call
9
+ * rather than being pushed once.
10
+ */
11
+ /** A selector as handed over the wire, or as-is when caller and page share a realm. */
12
+ type Wire = Selector | WireSelector;
13
+ export interface PageApiOptions {
14
+ testIdAttribute: string;
15
+ timeout: number;
16
+ }
17
+ /**
18
+ * The resolver as seen from inside a page a driver does not share a realm with
19
+ * — WebdriverIO, Puppeteer, TestCafe. Every argument is serialisable except
20
+ * `root`, which is an element handle the driver passes back in (or `null` for
21
+ * the document body).
22
+ *
23
+ * The same functions back the dom driver, so role, label, strictness, index
24
+ * and frame semantics cannot differ between a driver resolving in-process and
25
+ * one resolving through this.
26
+ */
27
+ export interface PageApi {
28
+ /** How many nodes match. The scope waits; the count does not. */
29
+ count(root: Element | null, scope: Wire[], selector: Wire, options: PageApiOptions): Promise<number>;
30
+ /** The single node a strict operation acts on, waiting for it. */
31
+ one(root: Element | null, scope: Wire[], selector: Wire, options: PageApiOptions): Promise<Element>;
32
+ /** Every match, after waiting for the scope. `nth` is ignored, as for any list. */
33
+ all(root: Element | null, scope: Wire[], selector: Wire, options: PageApiOptions): Promise<Element[]>;
34
+ /** The single node now, or `null`. For runtimes that do their own retrying. */
35
+ oneNow(root: Element | null, scope: Wire[], selector: Wire, options: PageApiOptions): Element | null;
36
+ /** Every match now. */
37
+ allNow(root: Element | null, scope: Wire[], selector: Wire, options: PageApiOptions): Element[];
38
+ /**
39
+ * Whether an element is visible by layout, within its own document only. A
40
+ * driver that enters frames from outside the page asks about each iframe it
41
+ * crossed in that iframe's own document, which also covers a cross-origin
42
+ * frame no in-page walk could cross.
43
+ */
44
+ visible(element: Element): boolean;
45
+ /**
46
+ * The element's box in its own document's viewport, or `null` when it draws
47
+ * none. A driver that enters frames from outside adds each crossed frame's
48
+ * `origin` itself.
49
+ */
50
+ box(element: Element): BoundingBox | null;
51
+ /** Where an iframe's document is drawn, in the iframe's own document. */
52
+ origin(iframe: Element): Point;
53
+ /** What an upload would set: the file input's `multiple`, or what the element is instead. */
54
+ fileTarget(element: Element): FileTarget;
55
+ }
56
+ export declare function createPageApi(document: Document): PageApi;
57
+ //# sourceMappingURL=page-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-api.d.ts","sourceRoot":"","sources":["../src/page-api.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAE3E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,OAAO,CAAA;AAElC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAI/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAE1C,OAAO,EAAE,eAAe,EAAE,CAAA;AAE1B;;;;GAIG;AACH,uFAAuF;AACvF,KAAK,IAAI,GAAG,QAAQ,GAAG,YAAY,CAAA;AAEnC,MAAM,WAAW,cAAc;IAC7B,eAAe,EAAE,MAAM,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,OAAO;IACtB,iEAAiE;IACjE,KAAK,CACH,IAAI,EAAE,OAAO,GAAG,IAAI,EACpB,KAAK,EAAE,IAAI,EAAE,EACb,QAAQ,EAAE,IAAI,EACd,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,MAAM,CAAC,CAAA;IAClB,kEAAkE;IAClE,GAAG,CACD,IAAI,EAAE,OAAO,GAAG,IAAI,EACpB,KAAK,EAAE,IAAI,EAAE,EACb,QAAQ,EAAE,IAAI,EACd,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,OAAO,CAAC,CAAA;IACnB,mFAAmF;IACnF,GAAG,CACD,IAAI,EAAE,OAAO,GAAG,IAAI,EACpB,KAAK,EAAE,IAAI,EAAE,EACb,QAAQ,EAAE,IAAI,EACd,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;IACrB,+EAA+E;IAC/E,MAAM,CACJ,IAAI,EAAE,OAAO,GAAG,IAAI,EACpB,KAAK,EAAE,IAAI,EAAE,EACb,QAAQ,EAAE,IAAI,EACd,OAAO,EAAE,cAAc,GACtB,OAAO,GAAG,IAAI,CAAA;IACjB,uBAAuB;IACvB,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,EAAE,CAAA;IAC/F;;;;;OAKG;IACH,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAA;IAClC;;;;OAIG;IACH,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAAA;IACzC,yEAAyE;IACzE,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,KAAK,CAAA;IAC9B,6FAA6F;IAC7F,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CAAA;CACzC;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAgDzD"}
@@ -0,0 +1,38 @@
1
+ import type { Selector } from '@harnessed-ts/core';
2
+ /** Every match for one selector, without waiting. Zero is an answer. */
3
+ export declare function queryAll(root: HTMLElement, selector: Selector): HTMLElement[];
4
+ /**
5
+ * Walks the scope chain down to the element the final selector is queried within.
6
+ * Waiting is correct here: a container that has not rendered yet is not the same
7
+ * thing as a target that is absent.
8
+ */
9
+ export declare function resolveScope(container: HTMLElement, scope: readonly Selector[], timeout?: number): Promise<HTMLElement>;
10
+ /** A frame link that cannot be entered — a wiring mistake, never "not rendered yet". */
11
+ export declare class FrameEntryError extends Error {
12
+ name: string;
13
+ }
14
+ /** The single node a strict operation acts on. */
15
+ export declare function resolveOne(container: HTMLElement, scope: readonly Selector[], selector: Selector, timeout?: number): Promise<HTMLElement>;
16
+ /**
17
+ * How many nodes match. The scope chain still waits, but the final count uses the
18
+ * non-waiting query so that zero answers immediately rather than throwing after a
19
+ * retry timeout. This is the behaviour Playwright already had.
20
+ */
21
+ export declare function countAll(container: HTMLElement, scope: readonly Selector[], selector: Selector, timeout?: number): Promise<number>;
22
+ /** The scope chain's innermost element now, or `null` if a link is not on screen. */
23
+ export declare function resolveScopeNow(container: HTMLElement, scope: readonly Selector[]): HTMLElement | null;
24
+ /** The single node a strict operation acts on now, or `null` if it is not on screen. */
25
+ export declare function resolveOneNow(container: HTMLElement, scope: readonly Selector[], selector: Selector): HTMLElement | null;
26
+ /**
27
+ * Every match now, agreeing with `countAll` on every scope it cannot resolve:
28
+ * an absent or ambiguous scope is an empty list, and only a frame that cannot
29
+ * be entered throws.
30
+ */
31
+ export declare function resolveAllNow(container: HTMLElement, scope: readonly Selector[], selector: Selector): HTMLElement[];
32
+ /**
33
+ * Every match for a list operation: the scope waits, the list does not, and
34
+ * `nth` is ignored — each result is addressed by its own index afterwards. The
35
+ * one definition of "the list" for in-process and injected drivers alike.
36
+ */
37
+ export declare function resolveAll(container: HTMLElement, scope: readonly Selector[], selector: Selector, timeout?: number): Promise<HTMLElement[]>;
38
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AA8BlD,wEAAwE;AACxE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,GAAG,WAAW,EAAE,CAmB7E;AAwED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,CAAC,CAUtB;AAED,wFAAwF;AACxF,qBAAa,eAAgB,SAAQ,KAAK;IAC/B,IAAI,SAAoB;CAClC;AA6BD,kDAAkD;AAClD,wBAAsB,UAAU,CAC9B,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,CAAC,CAItB;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAYjB;AAUD,qFAAqF;AACrF,wBAAgB,eAAe,CAC7B,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,SAAS,QAAQ,EAAE,GACzB,WAAW,GAAG,IAAI,CAUpB;AAED,wFAAwF;AACxF,wBAAgB,aAAa,CAC3B,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,QAAQ,EAAE,QAAQ,GACjB,WAAW,GAAG,IAAI,CAMpB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,QAAQ,EAAE,QAAQ,GACjB,WAAW,EAAE,CAaf;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,EAAE,CAAC,CAExB"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Whether an element is visible by layout — Playwright's definition, for drivers
3
+ * that run in a real browser beside the page (Vitest browser mode, Ember, Cypress).
4
+ * jsdom computes no layout, so the dom driver cannot use this and reads computed
5
+ * style instead.
6
+ *
7
+ * Visible means a non-empty box that `visibility` does not hide. `display: none`
8
+ * anywhere above gives an empty box, so it needs no check of its own. Opacity
9
+ * does not count: a transparent box is still there to click, and Playwright
10
+ * reports it visible.
11
+ *
12
+ * Inside a frame, the iframe has to be visible too — a hidden frame hides its
13
+ * content. The walk crosses frames up to `home` (by default, every reachable
14
+ * one), so a driver passes its own document to stop at the frames its scope
15
+ * actually entered.
16
+ */
17
+ export declare function isVisibleInLayout(element: Element, home?: Document): boolean;
18
+ //# sourceMappingURL=visible.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visible.d.ts","sourceRoot":"","sources":["../src/visible.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAO5E"}
package/dist/wire.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { Selector } from '@harnessed-ts/core';
2
+ /**
3
+ * A `Selector` as it crosses into a remote page. Everything in one is JSON-safe
4
+ * except a RegExp — `role('button', { name: /save/i })` — which WebDriver,
5
+ * CDP and TestCafe all serialise to `{}`. This encodes them as tagged objects
6
+ * on the way out, and the page API decodes them on the way in.
7
+ */
8
+ export interface WireRegExp {
9
+ $regexp: string;
10
+ flags: string;
11
+ }
12
+ export type WireSelector = Record<string, unknown>;
13
+ export declare function encodeSelector(selector: Selector): WireSelector;
14
+ /** Idempotent: a selector that never left the process decodes to itself. */
15
+ export declare function decodeSelector(selector: Selector | WireSelector): Selector;
16
+ //# sourceMappingURL=wire.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire.d.ts","sourceRoot":"","sources":["../src/wire.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAElD;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AA2ClD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,YAAY,CAE/D;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,YAAY,GAAG,QAAQ,CAE1E"}
package/package.json CHANGED
@@ -1,6 +1,77 @@
1
1
  {
2
2
  "name": "@harnessed-ts/resolve",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.6.0",
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
+ "keywords": [
6
+ "testing",
7
+ "harness",
8
+ "testing-library",
9
+ "resolver"
10
+ ],
11
+ "license": "MIT",
12
+ "contributors": [
13
+ {
14
+ "name": "Joe Gaudet",
15
+ "url": "https://github.com/joegaudet"
16
+ },
17
+ {
18
+ "name": "Jay Seo",
19
+ "url": "https://github.com/jayseo5953"
20
+ }
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/joegaudet/harnessed.git",
25
+ "directory": "packages/resolve"
26
+ },
27
+ "homepage": "https://github.com/joegaudet/harnessed#readme",
28
+ "bugs": "https://github.com/joegaudet/harnessed/issues",
29
+ "type": "module",
30
+ "main": "./dist/index.cjs",
31
+ "module": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.ts",
36
+ "import": "./dist/index.js",
37
+ "require": "./dist/index.cjs"
38
+ },
39
+ "./inject": {
40
+ "types": "./dist/inject.d.ts",
41
+ "import": "./dist/inject.js",
42
+ "require": "./dist/inject.cjs"
43
+ },
44
+ "./inject.global.js": "./dist/inject.global.js",
45
+ "./package.json": "./package.json"
46
+ },
47
+ "files": [
48
+ "dist",
49
+ "CHANGELOG.md",
50
+ "README.md",
51
+ "LICENSE"
52
+ ],
53
+ "engines": {
54
+ "node": ">=22.12.0"
55
+ },
56
+ "dependencies": {
57
+ "@harnessed-ts/core": "^0.6.0"
58
+ },
59
+ "peerDependencies": {
60
+ "@testing-library/dom": ">=10"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public"
64
+ },
65
+ "devDependencies": {
66
+ "@testing-library/dom": "^10.4.2",
67
+ "@types/jsdom": "^30.0.0",
68
+ "@types/node": "^26.6.3",
69
+ "jsdom": "^30.1.1",
70
+ "vitest": "^5.0.3"
71
+ },
72
+ "scripts": {
73
+ "build": "tsup",
74
+ "clean": "rm -rf dist *.tsbuildinfo",
75
+ "test": "vitest run"
76
+ }
6
77
  }