@phyto/driver-tauri 0.1.17 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +40 -1
- package/dist/index.js +37 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,27 @@ type InspectionLocator = Locator | {
|
|
|
48
48
|
};
|
|
49
49
|
/** Argument shape accepted by element-targeting driver methods. */
|
|
50
50
|
type LocatorInput = string | Locator;
|
|
51
|
+
/**
|
|
52
|
+
* Loosely-typed `EffectConfig` from the perspective of this package.
|
|
53
|
+
*
|
|
54
|
+
* The canonical type + mode-preset table live in `phyto/src/effects.ts`.
|
|
55
|
+
* The driver doesn't need to know what each field *means* — it just
|
|
56
|
+
* passes the object through, stamping a copy onto every action /
|
|
57
|
+
* wait command. We avoid taking a direct type dep on `phyto` (the
|
|
58
|
+
* driver is a lower-level package that several consumers may use
|
|
59
|
+
* without the test framework above it).
|
|
60
|
+
*/
|
|
61
|
+
interface DriverEffectConfig {
|
|
62
|
+
mode: "off" | "demo" | "debug" | string;
|
|
63
|
+
pointer?: boolean;
|
|
64
|
+
outline?: boolean;
|
|
65
|
+
color?: string;
|
|
66
|
+
delay?: number;
|
|
67
|
+
outlineHoldMs?: number;
|
|
68
|
+
travelMsPerPx?: number;
|
|
69
|
+
travelMinMs?: number;
|
|
70
|
+
travelMaxMs?: number;
|
|
71
|
+
}
|
|
51
72
|
interface TauriDriverOptions {
|
|
52
73
|
/** Path to the built Tauri binary */
|
|
53
74
|
binary: string;
|
|
@@ -61,6 +82,16 @@ interface TauriDriverOptions {
|
|
|
61
82
|
env?: Record<string, string>;
|
|
62
83
|
/** Default timeout in ms for all wait operations (default: 5000) */
|
|
63
84
|
defaultTimeout?: number;
|
|
85
|
+
/**
|
|
86
|
+
* Optional visual effects config — stamped onto every action /
|
|
87
|
+
* wait command's `options.effects` so the in-page harness can run
|
|
88
|
+
* the overlay. `mode === "off"` is a no-op (the harness short-
|
|
89
|
+
* circuits before touching the DOM).
|
|
90
|
+
*
|
|
91
|
+
* Resolution (mode + project / per-test overrides) is the caller's
|
|
92
|
+
* responsibility — see `resolveEffectConfig` in `phyto/src/effects.ts`.
|
|
93
|
+
*/
|
|
94
|
+
effects?: DriverEffectConfig;
|
|
64
95
|
}
|
|
65
96
|
/** Transport-level response wrapper from the Tauri automation server. */
|
|
66
97
|
interface TransportResult<T = unknown> {
|
|
@@ -138,7 +169,15 @@ declare class TauriDriver {
|
|
|
138
169
|
private readonly env;
|
|
139
170
|
private readonly baseUrl;
|
|
140
171
|
private readonly defaultTimeout;
|
|
172
|
+
private readonly effects;
|
|
141
173
|
constructor(options: TauriDriverOptions);
|
|
174
|
+
/**
|
|
175
|
+
* Merge the driver's resolved effects config into a command's
|
|
176
|
+
* `options.effects` slot, when the action is in the visualized set.
|
|
177
|
+
* Plumbing actions (`invoke`, `eval`, `handshake`) pass through
|
|
178
|
+
* untouched so the wire payload stays minimal.
|
|
179
|
+
*/
|
|
180
|
+
private stampEffects;
|
|
142
181
|
/**
|
|
143
182
|
* Launch the Tauri app and wait until the automation server is ready.
|
|
144
183
|
*/
|
|
@@ -292,4 +331,4 @@ interface SelectCommandResult {
|
|
|
292
331
|
selectedValue: string;
|
|
293
332
|
}
|
|
294
333
|
|
|
295
|
-
export { type FailureReason, type InspectionLocator, type InteractionOptions, type Locator, type LocatorInput, PhytoCommandError, type PhytoFailureContext, type SelectCommandResult, TauriDriver, type TauriDriverOptions, type TransportResult, type WaitOptions };
|
|
334
|
+
export { type DriverEffectConfig, type FailureReason, type InspectionLocator, type InteractionOptions, type Locator, type LocatorInput, PhytoCommandError, type PhytoFailureContext, type SelectCommandResult, TauriDriver, type TauriDriverOptions, type TransportResult, type WaitOptions };
|
package/dist/index.js
CHANGED
|
@@ -59,6 +59,14 @@ function stringifyLocator(locator) {
|
|
|
59
59
|
function truncate(s, n) {
|
|
60
60
|
return s.length > n ? `${s.slice(0, n)}\u2026` : s;
|
|
61
61
|
}
|
|
62
|
+
var EFFECTS_VISIBLE_ACTIONS = /* @__PURE__ */ new Set([
|
|
63
|
+
"click",
|
|
64
|
+
"type",
|
|
65
|
+
"select",
|
|
66
|
+
"wait-for",
|
|
67
|
+
"wait-for-text",
|
|
68
|
+
"wait-for-any"
|
|
69
|
+
]);
|
|
62
70
|
var TauriDriver = class {
|
|
63
71
|
process = null;
|
|
64
72
|
binary;
|
|
@@ -68,6 +76,7 @@ var TauriDriver = class {
|
|
|
68
76
|
env;
|
|
69
77
|
baseUrl;
|
|
70
78
|
defaultTimeout;
|
|
79
|
+
effects;
|
|
71
80
|
constructor(options) {
|
|
72
81
|
this.binary = options.binary;
|
|
73
82
|
this.port = options.port ?? 9876;
|
|
@@ -76,6 +85,30 @@ var TauriDriver = class {
|
|
|
76
85
|
this.env = options.env ?? {};
|
|
77
86
|
this.baseUrl = `http://localhost:${this.port}`;
|
|
78
87
|
this.defaultTimeout = options.defaultTimeout ?? 5e3;
|
|
88
|
+
this.effects = options.effects && options.effects.mode !== "off" ? options.effects : null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Merge the driver's resolved effects config into a command's
|
|
92
|
+
* `options.effects` slot, when the action is in the visualized set.
|
|
93
|
+
* Plumbing actions (`invoke`, `eval`, `handshake`) pass through
|
|
94
|
+
* untouched so the wire payload stays minimal.
|
|
95
|
+
*/
|
|
96
|
+
stampEffects(payload) {
|
|
97
|
+
if (!this.effects) return payload;
|
|
98
|
+
const action = payload.action;
|
|
99
|
+
if (typeof action !== "string") return payload;
|
|
100
|
+
if (!EFFECTS_VISIBLE_ACTIONS.has(action)) return payload;
|
|
101
|
+
const existingOptions = typeof payload.options === "object" && payload.options !== null ? payload.options : {};
|
|
102
|
+
return {
|
|
103
|
+
...payload,
|
|
104
|
+
options: {
|
|
105
|
+
...existingOptions,
|
|
106
|
+
// Test-side annotations win the field-level merge (caller
|
|
107
|
+
// already resolved them in); if the same effects object was
|
|
108
|
+
// pre-stamped elsewhere, don't overwrite.
|
|
109
|
+
effects: existingOptions.effects ?? this.effects
|
|
110
|
+
}
|
|
111
|
+
};
|
|
79
112
|
}
|
|
80
113
|
/**
|
|
81
114
|
* Launch the Tauri app and wait until the automation server is ready.
|
|
@@ -201,10 +234,11 @@ Re-build the Tauri app against a matching version of tauri-plugin-phyto, or upda
|
|
|
201
234
|
* `instanceof PhytoCommandError` and read `.context.reason` directly.
|
|
202
235
|
*/
|
|
203
236
|
async command(payload) {
|
|
237
|
+
const stamped = this.stampEffects(payload);
|
|
204
238
|
const res = await fetch(`${this.baseUrl}/command`, {
|
|
205
239
|
method: "POST",
|
|
206
240
|
headers: { "Content-Type": "application/json" },
|
|
207
|
-
body: JSON.stringify(
|
|
241
|
+
body: JSON.stringify(stamped)
|
|
208
242
|
});
|
|
209
243
|
const result = await res.json();
|
|
210
244
|
if (!result.ok) {
|
|
@@ -230,10 +264,11 @@ Re-build the Tauri app against a matching version of tauri-plugin-phyto, or upda
|
|
|
230
264
|
* throws only when the transport itself fails.
|
|
231
265
|
*/
|
|
232
266
|
async commandResult(payload) {
|
|
267
|
+
const stamped = this.stampEffects(payload);
|
|
233
268
|
const res = await fetch(`${this.baseUrl}/command`, {
|
|
234
269
|
method: "POST",
|
|
235
270
|
headers: { "Content-Type": "application/json" },
|
|
236
|
-
body: JSON.stringify(
|
|
271
|
+
body: JSON.stringify(stamped)
|
|
237
272
|
});
|
|
238
273
|
const result = await res.json();
|
|
239
274
|
if (!result.ok || !result.value) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { ChildProcess, spawn } from \"node:child_process\";\n\n/** Protocol version — must match the harness. */\nconst PROTOCOL_VERSION = 1;\n\n// ─── Locator types ──────────────────────────────────────────────────\n\n/**\n * Locator strategies accepted by **action** driver methods (`click`,\n * `type`, `select`, `waitFor`, etc.). Duplicated here so consumers\n * don't have to depend on the `phyto` package.\n *\n * - `css`: standard CSS selector (the most flexible — also produced\n * when a plain string is passed to any driver method).\n * - `role`: ARIA role with optional accessible name (exact match on\n * `textContent` or `aria-label`/`aria-labelledby`).\n * - `text`: any element whose textContent includes the given string.\n * - `testId`: shorthand for `[data-testid=\"...\"]`.\n * - `label`: form control associated with a `<label>`'s `htmlFor` or\n * nested input.\n *\n * Component locators are intentionally absent — they're scoped to\n * the inspection command surface (`InspectionLocator`). See\n * `packages/phyto/src/protocol.ts` for the rationale (action\n * semantics get murky once a component contains multiple form\n * controls; use `{ testId }` / `{ label }` / `{ role }` instead).\n */\nexport type Locator =\n | { css: string }\n | { role: string; name?: string }\n | { text: string }\n | { testId: string }\n /**\n * Resolve via a `<label>` element. Distinct from `{ text }` —\n * `label` finds the form control associated with a `<label>`'s\n * `htmlFor` or nested input; `text` matches any element by\n * `textContent`.\n */\n | { label: string };\n\n/**\n * Broader locator union for inspection commands (e.g. raw\n * `get-component-state` calls via `app.command()`). Adds the\n * `{ component }` strategy on top of the action set.\n */\nexport type InspectionLocator =\n | Locator\n | { component: string; props?: Record<string, unknown> };\n\n/** Argument shape accepted by element-targeting driver methods. */\nexport type LocatorInput = string | Locator;\n\n/**\n * Normalize a `LocatorInput` to a `Locator`. A bare string is treated\n * as a CSS selector — preserves backward compatibility with the\n * original string-only API.\n */\nfunction toLocator(input: LocatorInput): Locator {\n return typeof input === \"string\" ? { css: input } : input;\n}\n\n// ─── Types ──────────────────────────────────────────────────────────\n\nexport interface TauriDriverOptions {\n /** Path to the built Tauri binary */\n binary: string;\n /** Port for the automation server (default: 9876) */\n port?: number;\n /** Timeout in ms for waitUntilReady polling (default: 30000) */\n readyTimeout?: number;\n /** Poll interval in ms for waitUntilReady (default: 250) */\n readyInterval?: number;\n /** Additional environment variables for the app process */\n env?: Record<string, string>;\n /** Default timeout in ms for all wait operations (default: 5000) */\n defaultTimeout?: number;\n}\n\n/** Transport-level response wrapper from the Tauri automation server. */\nexport interface TransportResult<T = unknown> {\n ok: boolean;\n value?: T;\n error?: string;\n}\n\n/**\n * Reason types the harness reports in `FailureContext.reason`. Duplicated\n * here so consumers don't need to depend on the `phyto` package just to\n * narrow `PhytoCommandError.context.reason`.\n */\nexport type FailureReason =\n | \"not-found\"\n | \"not-visible\"\n | \"disabled\"\n | \"timeout\"\n | \"multiple-matches\"\n | \"unknown\";\n\n/**\n * Structured failure context attached by the in-page harness. Surfaced\n * on every `PhytoCommandError` thrown by `command()` so callers can\n * inspect element state, the locator, and the visible-testid breadcrumb\n * without parsing the error message.\n */\nexport interface PhytoFailureContext {\n reason: FailureReason;\n locator?: unknown;\n elementState?: {\n exists: boolean;\n visible: boolean;\n disabled: boolean;\n tagName?: string;\n textContent?: string;\n visibleTestIds?: string[];\n };\n componentTree?: unknown;\n}\n\n/**\n * Error thrown by `command()` (and downstream action methods) when the\n * harness returns a structured `{ ok: false }`. Carries the full\n * `FailureContext` envelope as `.context`, plus the name of the adapter\n * that produced the failure as `.handledBy`. The base `.message`\n * includes a short human-readable render of the context — reason,\n * locator, element-state highlights, and the first few visible testids\n * — so even a bare `console.error(err)` shows enough to triage.\n *\n * Before this change the driver threw a bare `Error(result.error)` and\n * silently discarded the context. The new shape is backwards-compatible\n * for code that only reads `.message`, but `instanceof PhytoCommandError`\n * lets richer callers branch on `.context.reason` directly.\n */\nexport class PhytoCommandError extends Error {\n override readonly name = \"PhytoCommandError\";\n readonly context?: PhytoFailureContext;\n readonly handledBy?: string;\n /**\n * The bare harness-level error string. Preserved so callers that\n * historically pattern-matched on `err.message` substrings have a\n * stable, unprefixed surface to grep against.\n */\n readonly rawError: string;\n\n constructor(\n rawError: string,\n context: PhytoFailureContext | undefined,\n handledBy: string | undefined\n ) {\n super(renderCommandErrorMessage(rawError, context, handledBy));\n this.rawError = rawError;\n this.context = context;\n this.handledBy = handledBy;\n // Restore the standard prototype chain after `super` so\n // `instanceof PhytoCommandError` works under both modern and\n // downlevelled output.\n Object.setPrototypeOf(this, PhytoCommandError.prototype);\n }\n}\n\n/**\n * Render a one-block diagnostic message from the structured failure\n * context. Format is intentionally compact and grep-friendly:\n *\n * <rawError>\n * reason: <reason>\n * locator: <stringified locator>\n * element: <tagName>, visible=<bool>, disabled=<bool>, text=\"...\"\n * visibleTestIds: a, b, c, …(N more)\n * handledBy: <adapter>\n *\n * Lines are omitted when the corresponding field is absent. Keeps the\n * message single-line-able for unstructured log sinks (every line is\n * prefixed with two spaces — no embedded carriage returns).\n */\nfunction renderCommandErrorMessage(\n rawError: string,\n context: PhytoFailureContext | undefined,\n handledBy: string | undefined\n): string {\n const lines: string[] = [rawError];\n if (context?.reason) lines.push(` reason: ${context.reason}`);\n if (context?.locator !== undefined) {\n lines.push(` locator: ${stringifyLocator(context.locator)}`);\n }\n const state = context?.elementState;\n if (state) {\n const parts: string[] = [];\n if (state.tagName) parts.push(`tag=${state.tagName}`);\n parts.push(`exists=${state.exists}`);\n parts.push(`visible=${state.visible}`);\n parts.push(`disabled=${state.disabled}`);\n if (state.textContent) {\n parts.push(`text=${JSON.stringify(truncate(state.textContent, 80))}`);\n }\n lines.push(` element: ${parts.join(\", \")}`);\n if (state.visibleTestIds && state.visibleTestIds.length > 0) {\n const shown = state.visibleTestIds.slice(0, 10);\n const more = state.visibleTestIds.length - shown.length;\n const tail = more > 0 ? `, …(+${more} more)` : \"\";\n lines.push(` visibleTestIds: ${shown.join(\", \")}${tail}`);\n }\n }\n if (handledBy) lines.push(` handledBy: ${handledBy}`);\n return lines.join(\"\\n\");\n}\n\nfunction stringifyLocator(locator: unknown): string {\n try {\n return JSON.stringify(locator);\n } catch {\n return String(locator);\n }\n}\n\nfunction truncate(s: string, n: number): string {\n return s.length > n ? `${s.slice(0, n)}…` : s;\n}\n\nexport interface WaitOptions {\n /** Timeout in ms (default: driver's defaultTimeout) */\n timeout?: number;\n /** Poll interval in ms (default: 100) */\n interval?: number;\n}\n\nexport interface InteractionOptions extends WaitOptions {\n /** If true, also wait for the element to not be disabled (default: false) */\n enabled?: boolean;\n}\n\n// ─── Driver ─────────────────────────────────────────────────────────\n\nexport class TauriDriver {\n private process: ChildProcess | null = null;\n private readonly binary: string;\n private readonly port: number;\n private readonly readyTimeout: number;\n private readonly readyInterval: number;\n private readonly env: Record<string, string>;\n private readonly baseUrl: string;\n private readonly defaultTimeout: number;\n\n constructor(options: TauriDriverOptions) {\n this.binary = options.binary;\n this.port = options.port ?? 9876;\n this.readyTimeout = options.readyTimeout ?? 120_000;\n this.readyInterval = options.readyInterval ?? 250;\n this.env = options.env ?? {};\n this.baseUrl = `http://localhost:${this.port}`;\n this.defaultTimeout = options.defaultTimeout ?? 5000;\n }\n\n /**\n * Launch the Tauri app and wait until the automation server is ready.\n */\n async launch(): Promise<void> {\n this.process = spawn(this.binary, [], {\n stdio: \"pipe\",\n env: { ...process.env, ...this.env },\n });\n\n // Forward stderr for debugging\n this.process.stderr?.on(\"data\", (data: Buffer) => {\n const line = data.toString().trim();\n if (line) {\n process.stderr.write(`[app] ${line}\\n`);\n }\n });\n\n this.process.on(\"error\", (err) => {\n throw new Error(`Failed to launch app binary: ${err.message}`);\n });\n\n await this.waitUntilReady();\n }\n\n /**\n * Poll the /health endpoint until the automation server is responsive,\n * then verify wire-protocol compatibility via /info.\n *\n * Fails fast (without retry) on a version mismatch — re-polling won't\n * fix a stale plugin binary.\n */\n async waitUntilReady(): Promise<void> {\n const deadline = Date.now() + this.readyTimeout;\n\n while (Date.now() < deadline) {\n try {\n const res = await fetch(`${this.baseUrl}/health`, {\n signal: AbortSignal.timeout(2000),\n });\n if (res.ok) {\n await this.assertPluginCompatible();\n return;\n }\n } catch {\n // Server not ready yet — keep polling\n }\n await sleep(this.readyInterval);\n }\n\n throw new Error(\n `Automation server did not become ready within ${this.readyTimeout}ms`\n );\n }\n\n /**\n * Fetch plugin metadata (wire protocol + plugin version) from /info.\n * Used by `waitUntilReady` to assert the plugin's protocol version\n * matches this driver's `PROTOCOL_VERSION`.\n */\n async fetchInfo(): Promise<{\n protocol_version: number;\n plugin_version: string;\n plugin: string;\n }> {\n const res = await fetch(`${this.baseUrl}/info`, {\n signal: AbortSignal.timeout(2000),\n });\n if (!res.ok) {\n throw new Error(\n `GET /info failed with status ${res.status}. Is tauri-plugin-phyto installed and registered?`\n );\n }\n return (await res.json()) as {\n protocol_version: number;\n plugin_version: string;\n plugin: string;\n };\n }\n\n /**\n * Verify the running plugin's wire protocol matches this driver's.\n * Throws a clear error on mismatch — both versions named so the user\n * knows which side is stale.\n */\n private async assertPluginCompatible(): Promise<void> {\n const info = await this.fetchInfo();\n if (info.protocol_version !== PROTOCOL_VERSION) {\n throw new Error(\n `Phyto protocol version mismatch:\\n` +\n ` - @phyto/driver-tauri: wire protocol v${PROTOCOL_VERSION}\\n` +\n ` - tauri-plugin-phyto (v${info.plugin_version}): wire protocol v${info.protocol_version}\\n` +\n `\\n` +\n `Re-build the Tauri app against a matching version of tauri-plugin-phyto, ` +\n `or update the npm packages to match the plugin.`\n );\n }\n }\n\n /**\n * Shut down the app process.\n */\n async shutdown(): Promise<void> {\n if (this.process) {\n this.process.kill(\"SIGTERM\");\n\n // Give it a moment to exit gracefully, then force kill\n await Promise.race([\n new Promise<void>((resolve) => {\n this.process?.on(\"exit\", () => resolve());\n }),\n sleep(5000).then(() => {\n this.process?.kill(\"SIGKILL\");\n }),\n ]);\n\n this.process = null;\n }\n }\n\n /**\n * Evaluate arbitrary JavaScript in the webview and return the result.\n * This is an escape hatch for operations not covered by the command\n * protocol (e.g. custom app-specific scripts).\n */\n async evaluate<T = unknown>(script: string): Promise<T> {\n return this.command<T>({\n action: \"eval\",\n script,\n });\n }\n\n /**\n * Send a declarative command to the in-page harness via POST /command.\n * Returns the structured result from the harness.\n *\n * On a structured harness failure (`{ ok: false }` returned from the\n * adapter chain) this throws a `PhytoCommandError` carrying the full\n * `FailureContext` envelope — element state, locator, and the visible-\n * testid breadcrumb — rather than a bare `Error(result.error)`. The\n * old behaviour silently discarded the context that every adapter\n * goes to the trouble of building, leaving callers with nothing but a\n * short, decontextualised string (\"Element not found\").\n *\n * Callers that only inspect `err.message` still see a richer message\n * (rendered from the context); callers that want to branch can\n * `instanceof PhytoCommandError` and read `.context.reason` directly.\n */\n async command<T = unknown>(payload: Record<string, unknown>): Promise<T> {\n const res = await fetch(`${this.baseUrl}/command`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n });\n\n const result: TransportResult<{\n ok: boolean;\n value?: T;\n error?: string;\n context?: PhytoFailureContext;\n handledBy?: string;\n }> = await res.json();\n\n // The /command endpoint wraps the harness result in a TransportResult envelope.\n // result.ok = true means the eval succeeded (harness didn't throw).\n // result.value contains the CommandResult from the harness.\n if (!result.ok) {\n throw new Error(result.error ?? \"Command transport failed\");\n }\n\n const commandResult = result.value;\n if (!commandResult || !commandResult.ok) {\n const rawError =\n commandResult?.error ?? \"Command failed with unknown error\";\n throw new PhytoCommandError(\n rawError,\n commandResult?.context,\n commandResult?.handledBy\n );\n }\n\n return commandResult.value as T;\n }\n\n /**\n * Send a command and return the full `CommandResult` envelope (so\n * callers can inspect routing — `handledBy` — and structured\n * failure context).\n *\n * Unlike `command()`, this does *not* throw on `{ ok: false }`. It\n * throws only when the transport itself fails.\n */\n async commandResult<T = unknown>(\n payload: Record<string, unknown>\n ): Promise<{\n ok: boolean;\n value?: T;\n error?: string;\n handledBy?: string;\n context?: PhytoFailureContext;\n }> {\n const res = await fetch(`${this.baseUrl}/command`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n });\n const result: TransportResult<{\n ok: boolean;\n value?: T;\n error?: string;\n handledBy?: string;\n context?: PhytoFailureContext;\n }> = await res.json();\n if (!result.ok || !result.value) {\n throw new Error(result.error ?? \"Command transport failed\");\n }\n return result.value;\n }\n\n /**\n * Perform the protocol version handshake with the in-page harness.\n * Returns true if the harness is available and compatible.\n */\n async handshake(): Promise<void> {\n await this.command({\n action: \"handshake\",\n protocolVersion: PROTOCOL_VERSION,\n });\n }\n\n // ─── Public API ─────────────────────────────────────────────────\n\n /**\n * Click an element matching the given target. Accepts a CSS selector\n * string or any `Locator` object (e.g. `{ role: \"button\", name: \"Next\" }`\n * for text-based button matching, or `{ testId: \"...\" }`).\n * Auto-waits for the element to be visible before clicking.\n * With `{ enabled: true }`, also waits for `!el.disabled` — useful for\n * buttons gated by form validation.\n */\n async click(target: LocatorInput, options?: InteractionOptions): Promise<void> {\n await this.command({\n action: \"click\",\n locator: toLocator(target),\n options: {\n timeout: options?.timeout ?? this.defaultTimeout,\n enabled: options?.enabled,\n },\n });\n }\n\n /**\n * Type text into an input/textarea matching the given target.\n * Auto-waits for the element to be visible before typing.\n * Finds the actual `<input>` or `<textarea>` element (even inside wrapper\n * components — useful for Mantine, which puts `data-testid` on the\n * wrapper `<div>`), sets the value using the React-compatible native\n * setter, and dispatches input/change events so React's synthetic\n * event system fires onChange handlers.\n */\n async type(\n target: LocatorInput,\n text: string,\n options?: InteractionOptions\n ): Promise<void> {\n await this.command({\n action: \"type\",\n locator: toLocator(target),\n text,\n options: {\n timeout: options?.timeout ?? this.defaultTimeout,\n },\n });\n }\n\n /**\n * Get the text content of an element matching the given target.\n * Auto-waits for the element to be visible before reading.\n */\n async textContent(\n target: LocatorInput,\n options?: InteractionOptions\n ): Promise<string> {\n return this.command<string>({\n action: \"text-content\",\n locator: toLocator(target),\n options: {\n timeout: options?.timeout ?? this.defaultTimeout,\n },\n });\n }\n\n /**\n * Wait until an element matching the target is in the DOM and visible.\n */\n async waitFor(target: LocatorInput, options?: WaitOptions): Promise<void> {\n await this.command({\n action: \"wait-for\",\n locator: toLocator(target),\n options: {\n timeout: options?.timeout ?? this.defaultTimeout,\n },\n });\n }\n\n /**\n * Wait until an element matching the target contains the specified text.\n */\n async waitForText(\n target: LocatorInput,\n text: string,\n options?: WaitOptions\n ): Promise<void> {\n await this.command({\n action: \"wait-for-text\",\n locator: toLocator(target),\n text,\n options: {\n timeout: options?.timeout ?? this.defaultTimeout,\n },\n });\n }\n\n /**\n * Wait until any of the given targets matches a visible element.\n * Returns the target that matched, so the caller can branch.\n */\n async waitForAny<T extends LocatorInput>(\n targets: T[],\n options?: WaitOptions\n ): Promise<T> {\n const locators = targets.map(toLocator);\n const matchedIndex = await this.command<number>({\n action: \"wait-for-any\",\n locators,\n options: {\n timeout: options?.timeout ?? this.defaultTimeout,\n },\n });\n return targets[matchedIndex];\n }\n\n /**\n * Invoke a Tauri command from the webview.\n * Calls the Tauri backend directly via window.__TAURI_INTERNALS__.invoke().\n */\n async invoke<T = unknown>(\n command: string,\n args?: Record<string, unknown>\n ): Promise<T> {\n return this.command<T>({\n action: \"invoke\",\n command,\n args: args ?? {},\n });\n }\n\n /**\n * Generic \"select an option in this dropdown\" command.\n *\n * `target` identifies the *select control* — the input the user\n * clicks to open the dropdown — using the same `Locator` vocabulary\n * as every other driver method:\n *\n * await app.select({ label: \"Country\" }, \"Canada\");\n * await app.select({ testId: \"country-select\" }, \"Canada\");\n * await app.select({ role: \"combobox\", name: \"Country\" }, \"Canada\");\n * await app.select('[data-testid=\"country-select\"]', \"Canada\");\n *\n * `value` is the *option content* — the visible text of the option\n * to choose (falls back to the option's underlying `value` attribute\n * for native `<select>`).\n *\n * Dispatched via the harness's probe-based adapter chain: the\n * Mantine adapter claims it when the locator resolves to a Mantine\n * combobox; the DOM adapter claims it when the locator resolves to\n * a native `<select>`. Either way the same call shape works.\n *\n * Returns the structured envelope including `handledBy` (the adapter\n * that handled the call) so tests can verify routing.\n */\n async select(\n target: LocatorInput,\n value: string,\n options?: WaitOptions\n ): Promise<SelectCommandResult> {\n const locator = toLocator(target);\n const res = await this.commandResult<{\n component: string;\n selectedValue: string;\n }>({\n action: \"select\",\n locator,\n value,\n options: {\n timeout: options?.timeout ?? this.defaultTimeout,\n },\n });\n if (!res.ok) {\n throw new PhytoCommandError(\n res.error ?? `select() failed with unknown error`,\n res.context,\n res.handledBy\n );\n }\n return {\n handledBy: res.handledBy ?? \"unknown\",\n component: res.value?.component ?? \"Select\",\n selectedValue: res.value?.selectedValue ?? value,\n };\n }\n}\n\n/** Structured return shape for `driver.select(...)`. */\nexport interface SelectCommandResult {\n /** Adapter that handled the command (e.g. `\"mantine\"`, `\"dom\"`). */\n handledBy: string;\n /** Component category reported by the adapter (e.g. `\"Select\"`). */\n component: string;\n /** The text or value actually selected (post-confirmation). */\n selectedValue: string;\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { ChildProcess, spawn } from \"node:child_process\";\n\n/** Protocol version — must match the harness. */\nconst PROTOCOL_VERSION = 1;\n\n// ─── Locator types ──────────────────────────────────────────────────\n\n/**\n * Locator strategies accepted by **action** driver methods (`click`,\n * `type`, `select`, `waitFor`, etc.). Duplicated here so consumers\n * don't have to depend on the `phyto` package.\n *\n * - `css`: standard CSS selector (the most flexible — also produced\n * when a plain string is passed to any driver method).\n * - `role`: ARIA role with optional accessible name (exact match on\n * `textContent` or `aria-label`/`aria-labelledby`).\n * - `text`: any element whose textContent includes the given string.\n * - `testId`: shorthand for `[data-testid=\"...\"]`.\n * - `label`: form control associated with a `<label>`'s `htmlFor` or\n * nested input.\n *\n * Component locators are intentionally absent — they're scoped to\n * the inspection command surface (`InspectionLocator`). See\n * `packages/phyto/src/protocol.ts` for the rationale (action\n * semantics get murky once a component contains multiple form\n * controls; use `{ testId }` / `{ label }` / `{ role }` instead).\n */\nexport type Locator =\n | { css: string }\n | { role: string; name?: string }\n | { text: string }\n | { testId: string }\n /**\n * Resolve via a `<label>` element. Distinct from `{ text }` —\n * `label` finds the form control associated with a `<label>`'s\n * `htmlFor` or nested input; `text` matches any element by\n * `textContent`.\n */\n | { label: string };\n\n/**\n * Broader locator union for inspection commands (e.g. raw\n * `get-component-state` calls via `app.command()`). Adds the\n * `{ component }` strategy on top of the action set.\n */\nexport type InspectionLocator =\n | Locator\n | { component: string; props?: Record<string, unknown> };\n\n/** Argument shape accepted by element-targeting driver methods. */\nexport type LocatorInput = string | Locator;\n\n/**\n * Normalize a `LocatorInput` to a `Locator`. A bare string is treated\n * as a CSS selector — preserves backward compatibility with the\n * original string-only API.\n */\nfunction toLocator(input: LocatorInput): Locator {\n return typeof input === \"string\" ? { css: input } : input;\n}\n\n// ─── Types ──────────────────────────────────────────────────────────\n\n/**\n * Loosely-typed `EffectConfig` from the perspective of this package.\n *\n * The canonical type + mode-preset table live in `phyto/src/effects.ts`.\n * The driver doesn't need to know what each field *means* — it just\n * passes the object through, stamping a copy onto every action /\n * wait command. We avoid taking a direct type dep on `phyto` (the\n * driver is a lower-level package that several consumers may use\n * without the test framework above it).\n */\nexport interface DriverEffectConfig {\n mode: \"off\" | \"demo\" | \"debug\" | string;\n pointer?: boolean;\n outline?: boolean;\n color?: string;\n delay?: number;\n outlineHoldMs?: number;\n travelMsPerPx?: number;\n travelMinMs?: number;\n travelMaxMs?: number;\n}\n\nexport interface TauriDriverOptions {\n /** Path to the built Tauri binary */\n binary: string;\n /** Port for the automation server (default: 9876) */\n port?: number;\n /** Timeout in ms for waitUntilReady polling (default: 30000) */\n readyTimeout?: number;\n /** Poll interval in ms for waitUntilReady (default: 250) */\n readyInterval?: number;\n /** Additional environment variables for the app process */\n env?: Record<string, string>;\n /** Default timeout in ms for all wait operations (default: 5000) */\n defaultTimeout?: number;\n /**\n * Optional visual effects config — stamped onto every action /\n * wait command's `options.effects` so the in-page harness can run\n * the overlay. `mode === \"off\"` is a no-op (the harness short-\n * circuits before touching the DOM).\n *\n * Resolution (mode + project / per-test overrides) is the caller's\n * responsibility — see `resolveEffectConfig` in `phyto/src/effects.ts`.\n */\n effects?: DriverEffectConfig;\n}\n\n/** Transport-level response wrapper from the Tauri automation server. */\nexport interface TransportResult<T = unknown> {\n ok: boolean;\n value?: T;\n error?: string;\n}\n\n/**\n * Reason types the harness reports in `FailureContext.reason`. Duplicated\n * here so consumers don't need to depend on the `phyto` package just to\n * narrow `PhytoCommandError.context.reason`.\n */\nexport type FailureReason =\n | \"not-found\"\n | \"not-visible\"\n | \"disabled\"\n | \"timeout\"\n | \"multiple-matches\"\n | \"unknown\";\n\n/**\n * Structured failure context attached by the in-page harness. Surfaced\n * on every `PhytoCommandError` thrown by `command()` so callers can\n * inspect element state, the locator, and the visible-testid breadcrumb\n * without parsing the error message.\n */\nexport interface PhytoFailureContext {\n reason: FailureReason;\n locator?: unknown;\n elementState?: {\n exists: boolean;\n visible: boolean;\n disabled: boolean;\n tagName?: string;\n textContent?: string;\n visibleTestIds?: string[];\n };\n componentTree?: unknown;\n}\n\n/**\n * Error thrown by `command()` (and downstream action methods) when the\n * harness returns a structured `{ ok: false }`. Carries the full\n * `FailureContext` envelope as `.context`, plus the name of the adapter\n * that produced the failure as `.handledBy`. The base `.message`\n * includes a short human-readable render of the context — reason,\n * locator, element-state highlights, and the first few visible testids\n * — so even a bare `console.error(err)` shows enough to triage.\n *\n * Before this change the driver threw a bare `Error(result.error)` and\n * silently discarded the context. The new shape is backwards-compatible\n * for code that only reads `.message`, but `instanceof PhytoCommandError`\n * lets richer callers branch on `.context.reason` directly.\n */\nexport class PhytoCommandError extends Error {\n override readonly name = \"PhytoCommandError\";\n readonly context?: PhytoFailureContext;\n readonly handledBy?: string;\n /**\n * The bare harness-level error string. Preserved so callers that\n * historically pattern-matched on `err.message` substrings have a\n * stable, unprefixed surface to grep against.\n */\n readonly rawError: string;\n\n constructor(\n rawError: string,\n context: PhytoFailureContext | undefined,\n handledBy: string | undefined\n ) {\n super(renderCommandErrorMessage(rawError, context, handledBy));\n this.rawError = rawError;\n this.context = context;\n this.handledBy = handledBy;\n // Restore the standard prototype chain after `super` so\n // `instanceof PhytoCommandError` works under both modern and\n // downlevelled output.\n Object.setPrototypeOf(this, PhytoCommandError.prototype);\n }\n}\n\n/**\n * Render a one-block diagnostic message from the structured failure\n * context. Format is intentionally compact and grep-friendly:\n *\n * <rawError>\n * reason: <reason>\n * locator: <stringified locator>\n * element: <tagName>, visible=<bool>, disabled=<bool>, text=\"...\"\n * visibleTestIds: a, b, c, …(N more)\n * handledBy: <adapter>\n *\n * Lines are omitted when the corresponding field is absent. Keeps the\n * message single-line-able for unstructured log sinks (every line is\n * prefixed with two spaces — no embedded carriage returns).\n */\nfunction renderCommandErrorMessage(\n rawError: string,\n context: PhytoFailureContext | undefined,\n handledBy: string | undefined\n): string {\n const lines: string[] = [rawError];\n if (context?.reason) lines.push(` reason: ${context.reason}`);\n if (context?.locator !== undefined) {\n lines.push(` locator: ${stringifyLocator(context.locator)}`);\n }\n const state = context?.elementState;\n if (state) {\n const parts: string[] = [];\n if (state.tagName) parts.push(`tag=${state.tagName}`);\n parts.push(`exists=${state.exists}`);\n parts.push(`visible=${state.visible}`);\n parts.push(`disabled=${state.disabled}`);\n if (state.textContent) {\n parts.push(`text=${JSON.stringify(truncate(state.textContent, 80))}`);\n }\n lines.push(` element: ${parts.join(\", \")}`);\n if (state.visibleTestIds && state.visibleTestIds.length > 0) {\n const shown = state.visibleTestIds.slice(0, 10);\n const more = state.visibleTestIds.length - shown.length;\n const tail = more > 0 ? `, …(+${more} more)` : \"\";\n lines.push(` visibleTestIds: ${shown.join(\", \")}${tail}`);\n }\n }\n if (handledBy) lines.push(` handledBy: ${handledBy}`);\n return lines.join(\"\\n\");\n}\n\nfunction stringifyLocator(locator: unknown): string {\n try {\n return JSON.stringify(locator);\n } catch {\n return String(locator);\n }\n}\n\nfunction truncate(s: string, n: number): string {\n return s.length > n ? `${s.slice(0, n)}…` : s;\n}\n\nexport interface WaitOptions {\n /** Timeout in ms (default: driver's defaultTimeout) */\n timeout?: number;\n /** Poll interval in ms (default: 100) */\n interval?: number;\n}\n\nexport interface InteractionOptions extends WaitOptions {\n /** If true, also wait for the element to not be disabled (default: false) */\n enabled?: boolean;\n}\n\n// ─── Driver ─────────────────────────────────────────────────────────\n\n/**\n * Action / wait actions that get effects stamped on. Plumbing actions\n * (`invoke`, `eval`, `handshake`) deliberately skip — they're not\n * user-visible interactions and don't have a target to animate to.\n */\nconst EFFECTS_VISIBLE_ACTIONS = new Set([\n \"click\",\n \"type\",\n \"select\",\n \"wait-for\",\n \"wait-for-text\",\n \"wait-for-any\",\n]);\n\nexport class TauriDriver {\n private process: ChildProcess | null = null;\n private readonly binary: string;\n private readonly port: number;\n private readonly readyTimeout: number;\n private readonly readyInterval: number;\n private readonly env: Record<string, string>;\n private readonly baseUrl: string;\n private readonly defaultTimeout: number;\n private readonly effects: DriverEffectConfig | null;\n\n constructor(options: TauriDriverOptions) {\n this.binary = options.binary;\n this.port = options.port ?? 9876;\n this.readyTimeout = options.readyTimeout ?? 120_000;\n this.readyInterval = options.readyInterval ?? 250;\n this.env = options.env ?? {};\n this.baseUrl = `http://localhost:${this.port}`;\n this.defaultTimeout = options.defaultTimeout ?? 5000;\n // Skip stamping entirely when mode is \"off\" — keeps the wire\n // payload small in the (overwhelmingly common) headless case.\n this.effects =\n options.effects && options.effects.mode !== \"off\"\n ? options.effects\n : null;\n }\n\n /**\n * Merge the driver's resolved effects config into a command's\n * `options.effects` slot, when the action is in the visualized set.\n * Plumbing actions (`invoke`, `eval`, `handshake`) pass through\n * untouched so the wire payload stays minimal.\n */\n private stampEffects(\n payload: Record<string, unknown>\n ): Record<string, unknown> {\n if (!this.effects) return payload;\n const action = payload.action;\n if (typeof action !== \"string\") return payload;\n if (!EFFECTS_VISIBLE_ACTIONS.has(action)) return payload;\n const existingOptions =\n typeof payload.options === \"object\" && payload.options !== null\n ? (payload.options as Record<string, unknown>)\n : {};\n return {\n ...payload,\n options: {\n ...existingOptions,\n // Test-side annotations win the field-level merge (caller\n // already resolved them in); if the same effects object was\n // pre-stamped elsewhere, don't overwrite.\n effects: existingOptions.effects ?? this.effects,\n },\n };\n }\n\n /**\n * Launch the Tauri app and wait until the automation server is ready.\n */\n async launch(): Promise<void> {\n this.process = spawn(this.binary, [], {\n stdio: \"pipe\",\n env: { ...process.env, ...this.env },\n });\n\n // Forward stderr for debugging\n this.process.stderr?.on(\"data\", (data: Buffer) => {\n const line = data.toString().trim();\n if (line) {\n process.stderr.write(`[app] ${line}\\n`);\n }\n });\n\n this.process.on(\"error\", (err) => {\n throw new Error(`Failed to launch app binary: ${err.message}`);\n });\n\n await this.waitUntilReady();\n }\n\n /**\n * Poll the /health endpoint until the automation server is responsive,\n * then verify wire-protocol compatibility via /info.\n *\n * Fails fast (without retry) on a version mismatch — re-polling won't\n * fix a stale plugin binary.\n */\n async waitUntilReady(): Promise<void> {\n const deadline = Date.now() + this.readyTimeout;\n\n while (Date.now() < deadline) {\n try {\n const res = await fetch(`${this.baseUrl}/health`, {\n signal: AbortSignal.timeout(2000),\n });\n if (res.ok) {\n await this.assertPluginCompatible();\n return;\n }\n } catch {\n // Server not ready yet — keep polling\n }\n await sleep(this.readyInterval);\n }\n\n throw new Error(\n `Automation server did not become ready within ${this.readyTimeout}ms`\n );\n }\n\n /**\n * Fetch plugin metadata (wire protocol + plugin version) from /info.\n * Used by `waitUntilReady` to assert the plugin's protocol version\n * matches this driver's `PROTOCOL_VERSION`.\n */\n async fetchInfo(): Promise<{\n protocol_version: number;\n plugin_version: string;\n plugin: string;\n }> {\n const res = await fetch(`${this.baseUrl}/info`, {\n signal: AbortSignal.timeout(2000),\n });\n if (!res.ok) {\n throw new Error(\n `GET /info failed with status ${res.status}. Is tauri-plugin-phyto installed and registered?`\n );\n }\n return (await res.json()) as {\n protocol_version: number;\n plugin_version: string;\n plugin: string;\n };\n }\n\n /**\n * Verify the running plugin's wire protocol matches this driver's.\n * Throws a clear error on mismatch — both versions named so the user\n * knows which side is stale.\n */\n private async assertPluginCompatible(): Promise<void> {\n const info = await this.fetchInfo();\n if (info.protocol_version !== PROTOCOL_VERSION) {\n throw new Error(\n `Phyto protocol version mismatch:\\n` +\n ` - @phyto/driver-tauri: wire protocol v${PROTOCOL_VERSION}\\n` +\n ` - tauri-plugin-phyto (v${info.plugin_version}): wire protocol v${info.protocol_version}\\n` +\n `\\n` +\n `Re-build the Tauri app against a matching version of tauri-plugin-phyto, ` +\n `or update the npm packages to match the plugin.`\n );\n }\n }\n\n /**\n * Shut down the app process.\n */\n async shutdown(): Promise<void> {\n if (this.process) {\n this.process.kill(\"SIGTERM\");\n\n // Give it a moment to exit gracefully, then force kill\n await Promise.race([\n new Promise<void>((resolve) => {\n this.process?.on(\"exit\", () => resolve());\n }),\n sleep(5000).then(() => {\n this.process?.kill(\"SIGKILL\");\n }),\n ]);\n\n this.process = null;\n }\n }\n\n /**\n * Evaluate arbitrary JavaScript in the webview and return the result.\n * This is an escape hatch for operations not covered by the command\n * protocol (e.g. custom app-specific scripts).\n */\n async evaluate<T = unknown>(script: string): Promise<T> {\n return this.command<T>({\n action: \"eval\",\n script,\n });\n }\n\n /**\n * Send a declarative command to the in-page harness via POST /command.\n * Returns the structured result from the harness.\n *\n * On a structured harness failure (`{ ok: false }` returned from the\n * adapter chain) this throws a `PhytoCommandError` carrying the full\n * `FailureContext` envelope — element state, locator, and the visible-\n * testid breadcrumb — rather than a bare `Error(result.error)`. The\n * old behaviour silently discarded the context that every adapter\n * goes to the trouble of building, leaving callers with nothing but a\n * short, decontextualised string (\"Element not found\").\n *\n * Callers that only inspect `err.message` still see a richer message\n * (rendered from the context); callers that want to branch can\n * `instanceof PhytoCommandError` and read `.context.reason` directly.\n */\n async command<T = unknown>(payload: Record<string, unknown>): Promise<T> {\n const stamped = this.stampEffects(payload);\n const res = await fetch(`${this.baseUrl}/command`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(stamped),\n });\n\n const result: TransportResult<{\n ok: boolean;\n value?: T;\n error?: string;\n context?: PhytoFailureContext;\n handledBy?: string;\n }> = await res.json();\n\n // The /command endpoint wraps the harness result in a TransportResult envelope.\n // result.ok = true means the eval succeeded (harness didn't throw).\n // result.value contains the CommandResult from the harness.\n if (!result.ok) {\n throw new Error(result.error ?? \"Command transport failed\");\n }\n\n const commandResult = result.value;\n if (!commandResult || !commandResult.ok) {\n const rawError =\n commandResult?.error ?? \"Command failed with unknown error\";\n throw new PhytoCommandError(\n rawError,\n commandResult?.context,\n commandResult?.handledBy\n );\n }\n\n return commandResult.value as T;\n }\n\n /**\n * Send a command and return the full `CommandResult` envelope (so\n * callers can inspect routing — `handledBy` — and structured\n * failure context).\n *\n * Unlike `command()`, this does *not* throw on `{ ok: false }`. It\n * throws only when the transport itself fails.\n */\n async commandResult<T = unknown>(\n payload: Record<string, unknown>\n ): Promise<{\n ok: boolean;\n value?: T;\n error?: string;\n handledBy?: string;\n context?: PhytoFailureContext;\n }> {\n const stamped = this.stampEffects(payload);\n const res = await fetch(`${this.baseUrl}/command`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(stamped),\n });\n const result: TransportResult<{\n ok: boolean;\n value?: T;\n error?: string;\n handledBy?: string;\n context?: PhytoFailureContext;\n }> = await res.json();\n if (!result.ok || !result.value) {\n throw new Error(result.error ?? \"Command transport failed\");\n }\n return result.value;\n }\n\n /**\n * Perform the protocol version handshake with the in-page harness.\n * Returns true if the harness is available and compatible.\n */\n async handshake(): Promise<void> {\n await this.command({\n action: \"handshake\",\n protocolVersion: PROTOCOL_VERSION,\n });\n }\n\n // ─── Public API ─────────────────────────────────────────────────\n\n /**\n * Click an element matching the given target. Accepts a CSS selector\n * string or any `Locator` object (e.g. `{ role: \"button\", name: \"Next\" }`\n * for text-based button matching, or `{ testId: \"...\" }`).\n * Auto-waits for the element to be visible before clicking.\n * With `{ enabled: true }`, also waits for `!el.disabled` — useful for\n * buttons gated by form validation.\n */\n async click(target: LocatorInput, options?: InteractionOptions): Promise<void> {\n await this.command({\n action: \"click\",\n locator: toLocator(target),\n options: {\n timeout: options?.timeout ?? this.defaultTimeout,\n enabled: options?.enabled,\n },\n });\n }\n\n /**\n * Type text into an input/textarea matching the given target.\n * Auto-waits for the element to be visible before typing.\n * Finds the actual `<input>` or `<textarea>` element (even inside wrapper\n * components — useful for Mantine, which puts `data-testid` on the\n * wrapper `<div>`), sets the value using the React-compatible native\n * setter, and dispatches input/change events so React's synthetic\n * event system fires onChange handlers.\n */\n async type(\n target: LocatorInput,\n text: string,\n options?: InteractionOptions\n ): Promise<void> {\n await this.command({\n action: \"type\",\n locator: toLocator(target),\n text,\n options: {\n timeout: options?.timeout ?? this.defaultTimeout,\n },\n });\n }\n\n /**\n * Get the text content of an element matching the given target.\n * Auto-waits for the element to be visible before reading.\n */\n async textContent(\n target: LocatorInput,\n options?: InteractionOptions\n ): Promise<string> {\n return this.command<string>({\n action: \"text-content\",\n locator: toLocator(target),\n options: {\n timeout: options?.timeout ?? this.defaultTimeout,\n },\n });\n }\n\n /**\n * Wait until an element matching the target is in the DOM and visible.\n */\n async waitFor(target: LocatorInput, options?: WaitOptions): Promise<void> {\n await this.command({\n action: \"wait-for\",\n locator: toLocator(target),\n options: {\n timeout: options?.timeout ?? this.defaultTimeout,\n },\n });\n }\n\n /**\n * Wait until an element matching the target contains the specified text.\n */\n async waitForText(\n target: LocatorInput,\n text: string,\n options?: WaitOptions\n ): Promise<void> {\n await this.command({\n action: \"wait-for-text\",\n locator: toLocator(target),\n text,\n options: {\n timeout: options?.timeout ?? this.defaultTimeout,\n },\n });\n }\n\n /**\n * Wait until any of the given targets matches a visible element.\n * Returns the target that matched, so the caller can branch.\n */\n async waitForAny<T extends LocatorInput>(\n targets: T[],\n options?: WaitOptions\n ): Promise<T> {\n const locators = targets.map(toLocator);\n const matchedIndex = await this.command<number>({\n action: \"wait-for-any\",\n locators,\n options: {\n timeout: options?.timeout ?? this.defaultTimeout,\n },\n });\n return targets[matchedIndex];\n }\n\n /**\n * Invoke a Tauri command from the webview.\n * Calls the Tauri backend directly via window.__TAURI_INTERNALS__.invoke().\n */\n async invoke<T = unknown>(\n command: string,\n args?: Record<string, unknown>\n ): Promise<T> {\n return this.command<T>({\n action: \"invoke\",\n command,\n args: args ?? {},\n });\n }\n\n /**\n * Generic \"select an option in this dropdown\" command.\n *\n * `target` identifies the *select control* — the input the user\n * clicks to open the dropdown — using the same `Locator` vocabulary\n * as every other driver method:\n *\n * await app.select({ label: \"Country\" }, \"Canada\");\n * await app.select({ testId: \"country-select\" }, \"Canada\");\n * await app.select({ role: \"combobox\", name: \"Country\" }, \"Canada\");\n * await app.select('[data-testid=\"country-select\"]', \"Canada\");\n *\n * `value` is the *option content* — the visible text of the option\n * to choose (falls back to the option's underlying `value` attribute\n * for native `<select>`).\n *\n * Dispatched via the harness's probe-based adapter chain: the\n * Mantine adapter claims it when the locator resolves to a Mantine\n * combobox; the DOM adapter claims it when the locator resolves to\n * a native `<select>`. Either way the same call shape works.\n *\n * Returns the structured envelope including `handledBy` (the adapter\n * that handled the call) so tests can verify routing.\n */\n async select(\n target: LocatorInput,\n value: string,\n options?: WaitOptions\n ): Promise<SelectCommandResult> {\n const locator = toLocator(target);\n const res = await this.commandResult<{\n component: string;\n selectedValue: string;\n }>({\n action: \"select\",\n locator,\n value,\n options: {\n timeout: options?.timeout ?? this.defaultTimeout,\n },\n });\n if (!res.ok) {\n throw new PhytoCommandError(\n res.error ?? `select() failed with unknown error`,\n res.context,\n res.handledBy\n );\n }\n return {\n handledBy: res.handledBy ?? \"unknown\",\n component: res.value?.component ?? \"Select\",\n selectedValue: res.value?.selectedValue ?? value,\n };\n }\n}\n\n/** Structured return shape for `driver.select(...)`. */\nexport interface SelectCommandResult {\n /** Adapter that handled the command (e.g. `\"mantine\"`, `\"dom\"`). */\n handledBy: string;\n /** Component category reported by the adapter (e.g. `\"Select\"`). */\n component: string;\n /** The text or value actually selected (post-confirmation). */\n selectedValue: string;\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phyto/driver-tauri",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "Tauri driver for Phyto — controls a running Tauri app via the tauri-plugin-phyto automation socket.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Coniferous <hello@coniferous.dev>",
|