@nyssance/tauri-hasgard 0.2.1 → 0.4.1
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/README.md +44 -0
- package/dist/chunk-N7YDYAKI.js +31 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +533 -107
- package/dist/process-group.test.d.ts +1 -0
- package/dist/process-manager.d.ts +18 -0
- package/dist/process-manager.test.d.ts +1 -0
- package/dist/process-supervisor.js +154 -0
- package/dist/protocol-limits.d.ts +1 -0
- package/dist/protocol.d.ts +16 -0
- package/dist/protocol.test.d.ts +1 -0
- package/dist/types.d.ts +79 -0
- package/dist/window.d.ts +87 -4
- package/package.json +6 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -3,7 +3,25 @@ export declare class HasgardProcess {
|
|
|
3
3
|
private child;
|
|
4
4
|
private stdout;
|
|
5
5
|
private stderr;
|
|
6
|
+
private starting;
|
|
7
|
+
private stopping;
|
|
8
|
+
private applicationPid;
|
|
9
|
+
private applicationExit;
|
|
10
|
+
/** Bounded log tails and the supervisor's actual child-close result. */
|
|
11
|
+
diagnostics(waitForExitMs?: number): Promise<{
|
|
12
|
+
pid: number | undefined;
|
|
13
|
+
exit: {
|
|
14
|
+
code: number | null;
|
|
15
|
+
signal: string | null;
|
|
16
|
+
} | undefined;
|
|
17
|
+
stdout: string;
|
|
18
|
+
stderr: string;
|
|
19
|
+
}>;
|
|
6
20
|
start(config: HasgardLaunchConfig, socketPath: string): Promise<void>;
|
|
21
|
+
private launch;
|
|
22
|
+
private launchSupervised;
|
|
7
23
|
waitForSocket(socketPath: string, timeoutMs: number): Promise<void>;
|
|
24
|
+
private confirmEndpointOwnership;
|
|
8
25
|
stop(): Promise<void>;
|
|
26
|
+
private stopChild;
|
|
9
27
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import {
|
|
2
|
+
stopProcessGroup
|
|
3
|
+
} from "./chunk-N7YDYAKI.js";
|
|
4
|
+
|
|
5
|
+
// src/process-supervisor.js
|
|
6
|
+
import { spawn } from "child_process";
|
|
7
|
+
import { lstatSync, unlinkSync } from "fs";
|
|
8
|
+
import { createConnection } from "net";
|
|
9
|
+
import { setTimeout as delay } from "timers/promises";
|
|
10
|
+
var application;
|
|
11
|
+
var starting = false;
|
|
12
|
+
var stopping;
|
|
13
|
+
var endpoint;
|
|
14
|
+
var initialEndpoint;
|
|
15
|
+
var ownedEndpoint;
|
|
16
|
+
var endpointWatch;
|
|
17
|
+
function identity(path) {
|
|
18
|
+
try {
|
|
19
|
+
const stat = lstatSync(path);
|
|
20
|
+
return { dev: stat.dev, ino: stat.ino, socket: stat.isSocket() };
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (error.code === "ENOENT") return void 0;
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function sameIdentity(first, second) {
|
|
27
|
+
return first && second && first.dev === second.dev && first.ino === second.ino;
|
|
28
|
+
}
|
|
29
|
+
function captureEndpoint() {
|
|
30
|
+
if (ownedEndpoint || !endpoint) return;
|
|
31
|
+
const current = identity(endpoint);
|
|
32
|
+
if (current?.socket && !sameIdentity(current, initialEndpoint)) {
|
|
33
|
+
ownedEndpoint = current;
|
|
34
|
+
clearInterval(endpointWatch);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function removeOwnedEndpoint() {
|
|
38
|
+
if (!ownedEndpoint || !sameIdentity(identity(endpoint), ownedEndpoint)) return;
|
|
39
|
+
const accepting = await new Promise((resolve, reject) => {
|
|
40
|
+
const socket = createConnection({ path: endpoint });
|
|
41
|
+
socket.once("connect", () => {
|
|
42
|
+
socket.destroy();
|
|
43
|
+
resolve(true);
|
|
44
|
+
});
|
|
45
|
+
socket.once("error", (error) => {
|
|
46
|
+
if (error.code === "ECONNREFUSED" || error.code === "ENOENT") resolve(false);
|
|
47
|
+
else reject(error);
|
|
48
|
+
});
|
|
49
|
+
socket.setTimeout(1e3, () => {
|
|
50
|
+
socket.destroy();
|
|
51
|
+
reject(new Error("Endpoint cleanup probe timed out"));
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
if (!accepting && sameIdentity(identity(endpoint), ownedEndpoint)) {
|
|
55
|
+
try {
|
|
56
|
+
unlinkSync(endpoint);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (error.code !== "ENOENT") throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
process.stderr.on("error", (error) => {
|
|
63
|
+
if (error.code !== "EPIPE") shutdown(1);
|
|
64
|
+
});
|
|
65
|
+
function send(message, stream) {
|
|
66
|
+
if (!process.connected) return;
|
|
67
|
+
const writable = process.send(message, (error) => {
|
|
68
|
+
if (error) {
|
|
69
|
+
if (error.code !== "ERR_IPC_CHANNEL_CLOSED" && error.code !== "EPIPE")
|
|
70
|
+
process.stderr.write(`Hasgard supervisor IPC: ${error.message}
|
|
71
|
+
`);
|
|
72
|
+
if (process.connected) process.disconnect();
|
|
73
|
+
} else stream?.resume();
|
|
74
|
+
});
|
|
75
|
+
if (!writable) stream?.pause();
|
|
76
|
+
}
|
|
77
|
+
process.once("message", (config) => {
|
|
78
|
+
if (!process.connected || stopping) return;
|
|
79
|
+
starting = true;
|
|
80
|
+
try {
|
|
81
|
+
endpoint = config.env.TAURI_HASGARD_SOCKET;
|
|
82
|
+
if (typeof endpoint !== "string" || !endpoint) throw new Error("Supervisor requires TAURI_HASGARD_SOCKET");
|
|
83
|
+
initialEndpoint = identity(endpoint);
|
|
84
|
+
endpointWatch = setInterval(captureEndpoint, 25);
|
|
85
|
+
process.on("message", (message) => {
|
|
86
|
+
if (message.type !== "endpoint") return;
|
|
87
|
+
const current = identity(endpoint);
|
|
88
|
+
if (!current?.socket || !sameIdentity(current, message.identity)) {
|
|
89
|
+
send({ type: "error", message: "Application endpoint changed during readiness" });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
ownedEndpoint = current;
|
|
93
|
+
clearInterval(endpointWatch);
|
|
94
|
+
send({ type: "endpoint-owned" });
|
|
95
|
+
});
|
|
96
|
+
application = spawn(config.command, config.args, {
|
|
97
|
+
cwd: config.cwd,
|
|
98
|
+
env: config.env,
|
|
99
|
+
detached: true,
|
|
100
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
101
|
+
});
|
|
102
|
+
application.stdout.on("data", (chunk) => send({ type: "stdout", data: chunk.toString() }, application.stdout));
|
|
103
|
+
application.stderr.on("data", (chunk) => send({ type: "stderr", data: chunk.toString() }, application.stderr));
|
|
104
|
+
application.once("spawn", () => send({ type: "started", pid: application.pid }));
|
|
105
|
+
application.once("error", (error) => send({ type: "error", message: error.message, code: error.code }));
|
|
106
|
+
application.once("close", (code, signal) => send({ type: "exit", code, signal }));
|
|
107
|
+
} catch (error) {
|
|
108
|
+
send({ type: "error", message: error.message, code: error.code });
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
process.once("disconnect", () => shutdown(0));
|
|
112
|
+
for (const signal of ["SIGTERM", "SIGINT", "SIGHUP"]) process.on(signal, () => shutdown(0));
|
|
113
|
+
process.on("uncaughtException", (error) => {
|
|
114
|
+
process.stderr.write(`Hasgard supervisor error: ${error.stack}
|
|
115
|
+
`);
|
|
116
|
+
shutdown(1);
|
|
117
|
+
});
|
|
118
|
+
process.on("unhandledRejection", (error) => {
|
|
119
|
+
process.stderr.write(`Hasgard supervisor rejection: ${String(error)}
|
|
120
|
+
`);
|
|
121
|
+
shutdown(1);
|
|
122
|
+
});
|
|
123
|
+
function shutdown(exitCode) {
|
|
124
|
+
if (!stopping) stopping = cleanup(exitCode);
|
|
125
|
+
}
|
|
126
|
+
async function cleanup(exitCode) {
|
|
127
|
+
clearInterval(endpointWatch);
|
|
128
|
+
const pid = application?.pid;
|
|
129
|
+
if (!starting || pid === void 0) process.exit(exitCode);
|
|
130
|
+
let groupStopped = false;
|
|
131
|
+
let endpointFailures = 0;
|
|
132
|
+
for (; ; ) {
|
|
133
|
+
try {
|
|
134
|
+
let endpointError;
|
|
135
|
+
try {
|
|
136
|
+
captureEndpoint();
|
|
137
|
+
} catch (error) {
|
|
138
|
+
endpointError = error;
|
|
139
|
+
}
|
|
140
|
+
if (!groupStopped) {
|
|
141
|
+
await stopProcessGroup(pid);
|
|
142
|
+
groupStopped = true;
|
|
143
|
+
}
|
|
144
|
+
if (endpointError) throw endpointError;
|
|
145
|
+
await removeOwnedEndpoint();
|
|
146
|
+
process.exit(exitCode);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
process.stderr.write(`Hasgard orphan cleanup: ${error.message}
|
|
149
|
+
`);
|
|
150
|
+
if (groupStopped && ++endpointFailures >= 3) process.exit(1);
|
|
151
|
+
await delay(1e3);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const MAX_REQUEST_BYTES = 1048576;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { JsonValue } from "./types.js";
|
|
2
|
+
export type RpcResponse = {
|
|
3
|
+
jsonrpc: "2.0";
|
|
4
|
+
id: number;
|
|
5
|
+
result: JsonValue;
|
|
6
|
+
} | {
|
|
7
|
+
jsonrpc: "2.0";
|
|
8
|
+
id: number | null;
|
|
9
|
+
error: {
|
|
10
|
+
code: number;
|
|
11
|
+
message: string;
|
|
12
|
+
data?: JsonValue;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
/** Validate the shared Hasgard envelope without inventing missing results. */
|
|
16
|
+
export declare function parseRpcResponse(line: string): RpcResponse;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types.d.ts
CHANGED
|
@@ -33,6 +33,11 @@ export interface SnapshotOptions {
|
|
|
33
33
|
interactive?: boolean;
|
|
34
34
|
selector?: string;
|
|
35
35
|
depth?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Take the snapshot inside this chain of same-origin iframes, one CSS
|
|
38
|
+
* selector per nesting level, instead of in the top document.
|
|
39
|
+
*/
|
|
40
|
+
frame?: string[];
|
|
36
41
|
}
|
|
37
42
|
export interface WindowInfo {
|
|
38
43
|
label: string;
|
|
@@ -71,6 +76,37 @@ export interface DragOffset {
|
|
|
71
76
|
x: number;
|
|
72
77
|
y: number;
|
|
73
78
|
}
|
|
79
|
+
/** A held keyboard modifier. Named as the platform reports them on the event. */
|
|
80
|
+
export type KeyModifier = "Alt" | "Control" | "Meta" | "Shift";
|
|
81
|
+
/** An application postcondition to await before another native press may focus a window. */
|
|
82
|
+
export interface PressOptions {
|
|
83
|
+
completion?: "webview" | "native";
|
|
84
|
+
waitFor?: string;
|
|
85
|
+
timeoutMs?: number;
|
|
86
|
+
}
|
|
87
|
+
export type MouseButton = "left" | "middle" | "right";
|
|
88
|
+
/** A point inside the element, measured from its top-left corner. */
|
|
89
|
+
export interface ElementPosition {
|
|
90
|
+
x: number;
|
|
91
|
+
y: number;
|
|
92
|
+
}
|
|
93
|
+
export interface ClickOptions {
|
|
94
|
+
/**
|
|
95
|
+
* Modifiers held for the whole gesture. `Meta` is Command on macOS and the
|
|
96
|
+
* Windows key elsewhere -- the platform's own accelerator, which is why
|
|
97
|
+
* multi-select lists usually bind it rather than `Control`.
|
|
98
|
+
*/
|
|
99
|
+
modifiers?: KeyModifier[];
|
|
100
|
+
/**
|
|
101
|
+
* Which button. `right` raises `contextmenu` and `middle` raises `auxclick`;
|
|
102
|
+
* neither raises `click`, exactly as a real press does not.
|
|
103
|
+
*/
|
|
104
|
+
button?: MouseButton;
|
|
105
|
+
/** Presses in one gesture. `2` also raises a single `dblclick` at the end. */
|
|
106
|
+
clickCount?: number;
|
|
107
|
+
/** Where inside the element to press. Defaults to its centre. */
|
|
108
|
+
position?: ElementPosition;
|
|
109
|
+
}
|
|
74
110
|
export interface DropFile {
|
|
75
111
|
name: string;
|
|
76
112
|
/** Base64-encoded file contents. */
|
|
@@ -121,6 +157,49 @@ export interface DialogListing {
|
|
|
121
157
|
dialogs: DialogRecord[];
|
|
122
158
|
policy: DialogPolicy;
|
|
123
159
|
}
|
|
160
|
+
/** How a matched request is answered. */
|
|
161
|
+
export interface RouteOptions {
|
|
162
|
+
/**
|
|
163
|
+
* Glob against the full URL. `*` stays inside one path segment, `**` crosses
|
|
164
|
+
* separators, and the pattern is anchored at both ends -- so `**\/api` does
|
|
165
|
+
* not also intercept `/v2/api-docs`.
|
|
166
|
+
*/
|
|
167
|
+
pattern: string;
|
|
168
|
+
/** Restrict to one HTTP verb. Any verb matches when omitted. */
|
|
169
|
+
method?: string;
|
|
170
|
+
/** Stop matching after this many requests, letting later ones through. */
|
|
171
|
+
times?: number;
|
|
172
|
+
}
|
|
173
|
+
export interface FulfillOptions extends RouteOptions {
|
|
174
|
+
/** Defaults to 200. */
|
|
175
|
+
status?: number;
|
|
176
|
+
body?: string;
|
|
177
|
+
/** Defaults to `text/plain`. */
|
|
178
|
+
contentType?: string;
|
|
179
|
+
}
|
|
180
|
+
export interface RouteRule {
|
|
181
|
+
id: number;
|
|
182
|
+
pattern: string;
|
|
183
|
+
method: string | null;
|
|
184
|
+
action: "fulfill" | "abort";
|
|
185
|
+
status: number;
|
|
186
|
+
times: number | null;
|
|
187
|
+
/** How many requests this rule has already answered. */
|
|
188
|
+
used: number;
|
|
189
|
+
}
|
|
190
|
+
export interface InterceptedRequest {
|
|
191
|
+
id: number;
|
|
192
|
+
timestamp: number;
|
|
193
|
+
route_id: number;
|
|
194
|
+
method: string;
|
|
195
|
+
url: string;
|
|
196
|
+
action: "fulfill" | "abort";
|
|
197
|
+
status: number;
|
|
198
|
+
}
|
|
199
|
+
export interface RouteListing {
|
|
200
|
+
routes: RouteRule[];
|
|
201
|
+
intercepted: InterceptedRequest[];
|
|
202
|
+
}
|
|
124
203
|
export interface ConsoleLogOptions {
|
|
125
204
|
level?: "log" | "warn" | "error" | "info";
|
|
126
205
|
since?: number;
|
package/dist/window.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HasgardRpcClient } from "./rpc-client.js";
|
|
2
|
-
import type { BoundingBox, ConsoleLogEntry, ConsoleLogOptions, DialogListing, DialogPolicy, DiffOptions, DragOffset, DropFile, FileInput, FilterOptions, FormsDump, HasgardTarget, JsonValue, NativeScreenshot, NativeScreenshotOptions, NetworkRequestEntry, NetworkRequestOptions, QueryDimension, RecorderEntry, RecorderResult, RecorderStatus, RoleQuery, ScrollOptions, Snapshot, SnapshotDiff, SnapshotOptions, StorageListing, StorageOptions, TextQuery, WaitForFunctionOptions, WaitOptions, WatchChanges, WatchOptions, WindowInfo, WindowState } from "./types.js";
|
|
2
|
+
import type { BoundingBox, ClickOptions, ConsoleLogEntry, ConsoleLogOptions, DialogListing, DialogPolicy, DiffOptions, DragOffset, DropFile, FileInput, FilterOptions, FormsDump, FulfillOptions, HasgardTarget, JsonValue, NativeScreenshot, NativeScreenshotOptions, NetworkRequestEntry, NetworkRequestOptions, PressOptions, QueryDimension, RecorderEntry, RecorderResult, RecorderStatus, RoleQuery, RouteListing, RouteOptions, ScrollOptions, Snapshot, SnapshotDiff, SnapshotOptions, StorageListing, StorageOptions, TextQuery, WaitForFunctionOptions, WaitOptions, WatchChanges, WatchOptions, WindowInfo, WindowState } from "./types.js";
|
|
3
3
|
export declare const byRef: (ref: string) => HasgardTarget;
|
|
4
4
|
export declare const bySelector: (selector: string) => HasgardTarget;
|
|
5
5
|
export declare const byPoint: (x: number, y: number) => HasgardTarget;
|
|
@@ -44,6 +44,15 @@ export declare class HasgardWindow {
|
|
|
44
44
|
getByTitle(text: string, query?: TextQuery): HasgardLocator;
|
|
45
45
|
/** Test ids always match exactly, so `save` cannot select `save-draft`. */
|
|
46
46
|
getByTestId(testId: string): HasgardLocator;
|
|
47
|
+
/**
|
|
48
|
+
* Scope locators to a same-origin `<iframe>`.
|
|
49
|
+
*
|
|
50
|
+
* Cross-origin frames are out of reach: the same-origin policy binds injected
|
|
51
|
+
* script exactly as it binds the page's own, so no bridge can see into a
|
|
52
|
+
* third-party payment or OAuth frame. Those raise an error naming the origin
|
|
53
|
+
* problem rather than reporting a missing element.
|
|
54
|
+
*/
|
|
55
|
+
frameLocator(selector: string): HasgardFrameLocator;
|
|
47
56
|
snapshot(options?: SnapshotOptions): Promise<Snapshot>;
|
|
48
57
|
state(): Promise<WindowState>;
|
|
49
58
|
url(): Promise<string>;
|
|
@@ -56,7 +65,7 @@ export declare class HasgardWindow {
|
|
|
56
65
|
navigate(url: string): Promise<void>;
|
|
57
66
|
/** Scroll the document by a relative amount, or to `top`/`bottom`. */
|
|
58
67
|
scrollBy(options: ScrollOptions): Promise<void>;
|
|
59
|
-
press(key: string): Promise<void>;
|
|
68
|
+
press(key: string, options?: PressOptions): Promise<void>;
|
|
60
69
|
diff(options?: DiffOptions): Promise<SnapshotDiff>;
|
|
61
70
|
/**
|
|
62
71
|
* Poll a JavaScript expression in the webview until it returns a truthy
|
|
@@ -121,6 +130,36 @@ export declare class HasgardWindow {
|
|
|
121
130
|
/** Forget the recorded dialogs. Leaves the accept/dismiss policy alone. */
|
|
122
131
|
clear: () => Promise<void>;
|
|
123
132
|
};
|
|
133
|
+
/**
|
|
134
|
+
* Shape what the page's own requests receive, without a real backend.
|
|
135
|
+
*
|
|
136
|
+
* Rules are declarative rather than Playwright's per-request callback: the
|
|
137
|
+
* bridge only ever answers requests, so it cannot call back into this process
|
|
138
|
+
* and await a handler while the page sits inside `fetch`. The same constraint
|
|
139
|
+
* shaped `dialogs`.
|
|
140
|
+
*
|
|
141
|
+
* Rules are checked in registration order and the first match wins, so
|
|
142
|
+
* register the narrow ones before a catch-all. Both `fetch` and
|
|
143
|
+
* `XMLHttpRequest` are covered; resources the webview loads itself -- the
|
|
144
|
+
* document, `<img src>`, stylesheets -- are not, since no page script sees
|
|
145
|
+
* them.
|
|
146
|
+
*/
|
|
147
|
+
readonly routes: {
|
|
148
|
+
/** Answer matching requests with this response instead of the network. */
|
|
149
|
+
fulfill: (options: FulfillOptions) => Promise<{
|
|
150
|
+
id: number;
|
|
151
|
+
}>;
|
|
152
|
+
/** Fail matching requests the way a dropped connection does. */
|
|
153
|
+
abort: (options: RouteOptions) => Promise<{
|
|
154
|
+
id: number;
|
|
155
|
+
}>;
|
|
156
|
+
/** The active rules, their use counts, and every request they answered. */
|
|
157
|
+
list: () => Promise<RouteListing>;
|
|
158
|
+
/** Remove every rule and its interception log. */
|
|
159
|
+
clear: () => Promise<{
|
|
160
|
+
removed: number;
|
|
161
|
+
}>;
|
|
162
|
+
};
|
|
124
163
|
/** This window's operating-system window id, where the platform exposes one. */
|
|
125
164
|
nativeId(): Promise<number>;
|
|
126
165
|
call<T>(method: string, params?: Record<string, JsonValue>): Promise<T>;
|
|
@@ -141,12 +180,46 @@ type LocatorQuery = ({
|
|
|
141
180
|
}) & {
|
|
142
181
|
index?: number;
|
|
143
182
|
filters?: FilterOptions[];
|
|
183
|
+
frame?: string[];
|
|
144
184
|
};
|
|
185
|
+
/**
|
|
186
|
+
* The locator factories of a window, scoped to a chain of same-origin iframes.
|
|
187
|
+
*
|
|
188
|
+
* Mirrors the window's own factories rather than wrapping a locator, because a
|
|
189
|
+
* frame is a document to search in, not an element to search under: `#pay`
|
|
190
|
+
* inside the frame is not a descendant of `#pay` outside it.
|
|
191
|
+
*/
|
|
192
|
+
export declare class HasgardFrameLocator {
|
|
193
|
+
private readonly windowRef;
|
|
194
|
+
private readonly frame;
|
|
195
|
+
constructor(window: HasgardWindow, frame: string[]);
|
|
196
|
+
/** Descend into a frame nested inside this one. */
|
|
197
|
+
frameLocator(selector: string): HasgardFrameLocator;
|
|
198
|
+
locator(selector: string): HasgardLocator;
|
|
199
|
+
getByRole(role: string, query?: RoleQuery): HasgardLocator;
|
|
200
|
+
getByText(text: string, query?: TextQuery): HasgardLocator;
|
|
201
|
+
getByLabel(text: string, query?: TextQuery): HasgardLocator;
|
|
202
|
+
getByPlaceholder(text: string, query?: TextQuery): HasgardLocator;
|
|
203
|
+
getByAltText(text: string, query?: TextQuery): HasgardLocator;
|
|
204
|
+
getByTitle(text: string, query?: TextQuery): HasgardLocator;
|
|
205
|
+
getByTestId(testId: string): HasgardLocator;
|
|
206
|
+
/** Snapshot the frame's own document rather than the top one. */
|
|
207
|
+
snapshot(options?: SnapshotOptions): Promise<Snapshot>;
|
|
208
|
+
private byDimension;
|
|
209
|
+
}
|
|
145
210
|
export declare class HasgardLocator {
|
|
146
211
|
readonly window: HasgardWindow;
|
|
147
212
|
private readonly query;
|
|
148
213
|
constructor(window: HasgardWindow, query: LocatorQuery);
|
|
149
|
-
|
|
214
|
+
/**
|
|
215
|
+
* Click this element.
|
|
216
|
+
*
|
|
217
|
+
* A right or middle click raises `contextmenu`/`auxclick` and no `click`, and
|
|
218
|
+
* `clickCount: 2` escalates `detail` across the presses before a single
|
|
219
|
+
* `dblclick` -- both matching what the platform does, so a listener bound to
|
|
220
|
+
* the wrong event fails here rather than in front of a user.
|
|
221
|
+
*/
|
|
222
|
+
click(options?: ClickOptions): Promise<void>;
|
|
150
223
|
fill(value: string): Promise<void>;
|
|
151
224
|
type(text: string): Promise<void>;
|
|
152
225
|
/** Empty the control, firing `input` and `change` as `fill` does. */
|
|
@@ -175,7 +248,8 @@ export declare class HasgardLocator {
|
|
|
175
248
|
/** Flip the checkbox, whatever its current state. */
|
|
176
249
|
toggle(): Promise<void>;
|
|
177
250
|
hover(): Promise<void>;
|
|
178
|
-
|
|
251
|
+
/** Shorthand for `click({ clickCount: 2 })`; other options still apply. */
|
|
252
|
+
dblclick(options?: Omit<ClickOptions, "clickCount">): Promise<void>;
|
|
179
253
|
focus(): Promise<void>;
|
|
180
254
|
blur(): Promise<void>;
|
|
181
255
|
/** Scroll this element's own scrollport. */
|
|
@@ -215,6 +289,15 @@ export declare class HasgardLocator {
|
|
|
215
289
|
first(): HasgardLocator;
|
|
216
290
|
last(): HasgardLocator;
|
|
217
291
|
private resolveUnique;
|
|
292
|
+
/**
|
|
293
|
+
* Serialize a target together with the frame chain this locator lives in.
|
|
294
|
+
*
|
|
295
|
+
* A ref carries its own document, so the bridge ignores `frame` for one; the
|
|
296
|
+
* chain still travels so that a selector or coordinate target -- which the
|
|
297
|
+
* bridge must resolve itself -- lands in the right document.
|
|
298
|
+
*/
|
|
299
|
+
private scoped;
|
|
300
|
+
private frameParams;
|
|
218
301
|
private withTarget;
|
|
219
302
|
private resolveAll;
|
|
220
303
|
private resolveBase;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nyssance/tauri-hasgard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Playwright Test fixtures for native Tauri Hasgard automation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,12 +18,14 @@
|
|
|
18
18
|
"NOTICE.md"
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
|
-
"build": "bun node_modules/tsup/dist/cli-default.js src/index.ts --format esm --outDir dist --clean && bun node_modules/typescript/bin/tsc -p tsconfig.json --noEmit false --emitDeclarationOnly --declaration true",
|
|
22
|
-
"dev": "
|
|
21
|
+
"build": "bun node_modules/tsup/dist/cli-default.js src/index.ts src/process-supervisor.js --format esm --outDir dist --clean && bun node_modules/typescript/bin/tsc -p tsconfig.json --noEmit false --emitDeclarationOnly --declaration true",
|
|
22
|
+
"dev": "bun run --parallel dev:js dev:types",
|
|
23
23
|
"test": "vitest run",
|
|
24
24
|
"test:watch": "vitest",
|
|
25
25
|
"typecheck": "tsc --noEmit",
|
|
26
|
-
"clean": "rm -rf dist"
|
|
26
|
+
"clean": "rm -rf dist",
|
|
27
|
+
"dev:js": "tsup src/index.ts src/process-supervisor.js --format esm --outDir dist --watch",
|
|
28
|
+
"dev:types": "tsc -p tsconfig.json --noEmit false --emitDeclarationOnly --declaration true --watch"
|
|
27
29
|
},
|
|
28
30
|
"peerDependencies": {
|
|
29
31
|
"@playwright/test": ">=1.61.1 <2"
|