@nyssance/tauri-hasgard 0.6.0-alpha.2 → 0.6.0-alpha.5

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.js CHANGED
@@ -1,29 +1,27 @@
1
- import {
2
- stopProcessGroup
3
- } from "./chunk-N7YDYAKI.js";
4
-
5
1
  // src/endpoint.ts
6
- import { tmpdir } from "os";
7
- import { join } from "path";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
8
4
  function endpointPathForPlatform(name, platform, temporaryDirectory) {
9
- if (platform === "win32") return `\\\\.\\pipe\\${name}`;
5
+ if (platform === "win32")
6
+ return `\\\\.\\pipe\\${name}`;
10
7
  return join(temporaryDirectory, `${name}.sock`);
11
8
  }
12
9
  function hasgardEndpointPath(name) {
13
10
  return endpointPathForPlatform(name, process.platform, tmpdir());
14
11
  }
15
-
16
12
  // src/expect.ts
17
13
  async function poll(read, matches, timeoutMs) {
18
14
  if (!Number.isFinite(timeoutMs) || timeoutMs < 0)
19
15
  throw new RangeError("Assertion timeout must be a finite non-negative number");
20
16
  const deadline = Date.now() + timeoutMs;
21
- for (; ; ) {
17
+ for (;; ) {
22
18
  try {
23
19
  const value = await read();
24
- if (matches(value) || Date.now() >= deadline) return value;
20
+ if (matches(value) || Date.now() >= deadline)
21
+ return value;
25
22
  } catch (error) {
26
- if (Date.now() >= deadline) throw error;
23
+ if (Date.now() >= deadline)
24
+ throw error;
27
25
  }
28
26
  await new Promise((resolve) => setTimeout(resolve, Math.min(50, Math.max(0, deadline - Date.now()))));
29
27
  }
@@ -31,29 +29,22 @@ async function poll(read, matches, timeoutMs) {
31
29
  function createHasgardExpect(playwrightExpect) {
32
30
  return playwrightExpect.extend({
33
31
  async toBeVisible(locator, options = {}) {
34
- const timeout = options.timeout === void 0 ? 5e3 : options.timeout;
35
- const result = await poll(
36
- () => locator.isVisible(),
37
- (visible) => this.isNot ? !visible : visible,
38
- timeout
39
- );
32
+ const { timeout = 5000 } = options;
33
+ const result = await poll(() => locator.isVisible(), (visible) => this.isNot ? !visible : visible, timeout);
40
34
  return {
41
- pass: result === true,
35
+ pass: result,
42
36
  message: () => this.isNot ? "expected Hasgard locator not to be visible" : `expected Hasgard locator to be visible within ${timeout}ms`
43
37
  };
44
38
  },
45
39
  async toHaveText(locator, expected, options = {}) {
46
- const timeout = options.timeout === void 0 ? 5e3 : options.timeout;
40
+ const { timeout = 5000 } = options;
47
41
  const matches = (text) => {
48
- if (typeof expected === "string") return text === expected;
42
+ if (typeof expected === "string")
43
+ return text === expected;
49
44
  expected.lastIndex = 0;
50
45
  return expected.test(text);
51
46
  };
52
- const result = await poll(
53
- () => locator.textContent(),
54
- (text) => this.isNot ? !matches(text) : matches(text),
55
- timeout
56
- );
47
+ const result = await poll(() => locator.textContent(), (text) => this.isNot ? !matches(text) : matches(text), timeout);
57
48
  return {
58
49
  pass: matches(result),
59
50
  message: () => this.isNot ? `expected Hasgard locator text not to match ${String(expected)}, received ${String(result)}` : `expected Hasgard locator text to match ${String(expected)}, received ${String(result)}`
@@ -61,13 +52,46 @@ function createHasgardExpect(playwrightExpect) {
61
52
  }
62
53
  });
63
54
  }
55
+ // src/process-manager.ts
56
+ import { fork, spawn } from "node:child_process";
57
+ import { lstat } from "node:fs/promises";
58
+ import { createConnection } from "node:net";
59
+ import { setTimeout as delay2 } from "node:timers/promises";
60
+
61
+ // src/process-group.js
62
+ import { setTimeout as delay } from "node:timers/promises";
63
+ function signalGroup(pid, signal) {
64
+ try {
65
+ process.kill(-pid, signal);
66
+ return true;
67
+ } catch (error) {
68
+ if (error.code === "ESRCH")
69
+ return false;
70
+ if (signal === 0 && error.code === "EPERM")
71
+ return true;
72
+ throw error;
73
+ }
74
+ }
75
+ async function waitForExit(pid, timeoutMs) {
76
+ const deadline = Date.now() + timeoutMs;
77
+ while (signalGroup(pid, 0)) {
78
+ if (Date.now() >= deadline)
79
+ return false;
80
+ await delay(25);
81
+ }
82
+ return true;
83
+ }
84
+ async function stopProcessGroup(pid) {
85
+ signalGroup(pid, "SIGTERM");
86
+ if (!await waitForExit(pid, 5000)) {
87
+ signalGroup(pid, "SIGKILL");
88
+ if (!await waitForExit(pid, 5000))
89
+ throw new Error(`Hasgard process group ${pid} survived SIGKILL`);
90
+ }
91
+ }
64
92
 
65
93
  // src/process-manager.ts
66
- import { fork, spawn } from "child_process";
67
- import { lstat } from "fs/promises";
68
- import { createConnection } from "net";
69
- import { setTimeout as delay } from "timers/promises";
70
- var HasgardProcess = class {
94
+ class HasgardProcess {
71
95
  child;
72
96
  stdout = "";
73
97
  stderr = "";
@@ -75,28 +99,29 @@ var HasgardProcess = class {
75
99
  stopping;
76
100
  applicationPid;
77
101
  applicationExit;
78
- /** Bounded log tails and the supervisor's actual child-close result. */
79
102
  async diagnostics(waitForExitMs = 0) {
80
- if (!Number.isFinite(waitForExitMs) || waitForExitMs < 0) throw new RangeError("Invalid diagnostics wait");
103
+ if (!Number.isFinite(waitForExitMs) || waitForExitMs < 0)
104
+ throw new RangeError("Invalid diagnostics wait");
81
105
  const deadline = Date.now() + waitForExitMs;
82
106
  while (this.child && !this.applicationExit && Date.now() < deadline)
83
- await delay(Math.min(25, deadline - Date.now()));
107
+ await delay2(Math.min(25, deadline - Date.now()));
84
108
  return { pid: this.applicationPid, exit: this.applicationExit, stdout: this.stdout, stderr: this.stderr };
85
109
  }
86
110
  async start(config, socketPath) {
87
- if (this.child || this.stopping || this.starting) throw new Error("Hasgard app process is already running");
111
+ if (this.child || this.stopping || this.starting)
112
+ throw new Error("Hasgard app process is already running");
88
113
  this.starting = this.launch(config, socketPath);
89
114
  try {
90
115
  await this.starting;
91
116
  } finally {
92
- this.starting = void 0;
117
+ this.starting = undefined;
93
118
  }
94
119
  }
95
120
  async launch(config, socketPath) {
96
121
  this.stdout = "";
97
122
  this.stderr = "";
98
- this.applicationPid = void 0;
99
- this.applicationExit = void 0;
123
+ this.applicationPid = undefined;
124
+ this.applicationExit = undefined;
100
125
  if (process.platform !== "win32" && await endpointAcceptsConnections(socketPath)) {
101
126
  throw new Error(`Hasgard endpoint is already in use: ${socketPath}`);
102
127
  }
@@ -127,7 +152,9 @@ var HasgardProcess = class {
127
152
  try {
128
153
  await this.stopChild();
129
154
  } catch (cleanupError) {
130
- throw new AggregateError([error, cleanupError], "Hasgard launch failed and process cleanup failed");
155
+ throw new AggregateError([error, cleanupError], "Hasgard launch failed and process cleanup failed", {
156
+ cause: cleanupError
157
+ });
131
158
  }
132
159
  throw error;
133
160
  }
@@ -151,26 +178,28 @@ var HasgardProcess = class {
151
178
  };
152
179
  child.once("error", failed);
153
180
  child.once("exit", () => failed(new Error("Hasgard process supervisor exited")));
154
- child.on(
155
- "message",
156
- (message) => {
157
- if (message.type === "stdout") this.stdout = appendOutput(this.stdout, message.data);
158
- else if (message.type === "stderr") this.stderr = appendOutput(this.stderr, message.data);
159
- else if (message.type === "exit") this.applicationExit = { code: message.code, signal: message.signal };
160
- else if (message.type === "error") failed(Object.assign(new Error(message.message), { code: message.code }));
161
- else if (message.type === "started") {
162
- if (!Number.isSafeInteger(message.pid) || message.pid <= 1) {
163
- failed(new Error("Invalid supervised application PID"));
164
- return;
165
- }
166
- this.applicationPid = message.pid;
167
- clearTimeout(timer);
168
- resolve();
181
+ child.on("message", (message) => {
182
+ if (message.type === "stdout")
183
+ this.stdout = appendOutput(this.stdout, message.data);
184
+ else if (message.type === "stderr")
185
+ this.stderr = appendOutput(this.stderr, message.data);
186
+ else if (message.type === "exit")
187
+ this.applicationExit = { code: message.code, signal: message.signal };
188
+ else if (message.type === "error")
189
+ failed(Object.assign(new Error(message.message), { code: message.code }));
190
+ else if (message.type === "started") {
191
+ if (!Number.isSafeInteger(message.pid) || message.pid <= 1) {
192
+ failed(new Error("Invalid supervised application PID"));
193
+ return;
169
194
  }
195
+ this.applicationPid = message.pid;
196
+ clearTimeout(timer);
197
+ resolve();
170
198
  }
171
- );
199
+ });
172
200
  child.send({ ...config, env: { ...process.env, ...config.env, TAURI_HASGARD_SOCKET: socketPath } }, (error) => {
173
- if (error) failed(error);
201
+ if (error)
202
+ failed(error);
174
203
  });
175
204
  });
176
205
  await this.waitForSocket(socketPath, config.timeoutMs);
@@ -179,7 +208,9 @@ var HasgardProcess = class {
179
208
  try {
180
209
  await this.stopChild();
181
210
  } catch (cleanupError) {
182
- throw new AggregateError([error, cleanupError], "Hasgard launch failed and process cleanup failed");
211
+ throw new AggregateError([error, cleanupError], "Hasgard launch failed and process cleanup failed", {
212
+ cause: cleanupError
213
+ });
183
214
  }
184
215
  throw error;
185
216
  }
@@ -190,114 +221,120 @@ var HasgardProcess = class {
190
221
  const child = this.child;
191
222
  if (this.applicationExit) {
192
223
  const { code, signal } = this.applicationExit;
193
- throw new Error(
194
- `Tauri process exited with ${signal ? `signal ${signal}` : `code ${code}`}
224
+ throw new Error(`Tauri process exited with ${signal ? `signal ${signal}` : `code ${code}`}
195
225
  stdout:
196
226
  ${this.stdout}
197
227
  stderr:
198
- ${this.stderr}`
199
- );
228
+ ${this.stderr}`);
200
229
  }
201
230
  if (child && (child.exitCode !== null || child.signalCode !== null)) {
202
- throw new Error(
203
- `Tauri process exited with ${child.signalCode ? `signal ${child.signalCode}` : `code ${child.exitCode}`}
231
+ throw new Error(`Tauri process exited with ${child.signalCode ? `signal ${child.signalCode}` : `code ${child.exitCode}`}
204
232
  stdout:
205
233
  ${this.stdout}
206
234
  stderr:
207
- ${this.stderr}`
208
- );
235
+ ${this.stderr}`);
209
236
  }
210
- if (await endpointAcceptsConnections(socketPath)) return;
211
- await delay(50);
237
+ if (await endpointAcceptsConnections(socketPath))
238
+ return;
239
+ await delay2(50);
212
240
  }
213
241
  throw new Error(`Hasgard socket did not appear within ${timeoutMs}ms: ${socketPath}`);
214
242
  }
215
243
  async confirmEndpointOwnership(child, path, timeoutMs) {
216
244
  const stat = await lstat(path);
217
- if (!stat.isSocket()) throw new Error(`Hasgard endpoint is not a socket: ${path}`);
245
+ if (!stat.isSocket())
246
+ throw new Error(`Hasgard endpoint is not a socket: ${path}`);
218
247
  await new Promise((resolve, reject) => {
219
248
  const done = (error) => {
220
249
  clearTimeout(timer);
221
250
  child.off("message", message);
222
251
  child.off("exit", exited);
223
- if (error) reject(error);
224
- else resolve();
252
+ if (error)
253
+ reject(error);
254
+ else
255
+ resolve();
225
256
  };
226
257
  const message = (value) => {
227
- if (value.type === "endpoint-owned") done();
228
- else if (value.type === "error") done(new Error(value.message));
258
+ if (value.type === "endpoint-owned")
259
+ done();
260
+ else if (value.type === "error")
261
+ done(new Error(value.message));
229
262
  };
230
263
  const exited = () => done(new Error("Hasgard supervisor exited before acknowledging endpoint ownership"));
231
- const timer = setTimeout(
232
- () => done(new Error("Hasgard supervisor did not acknowledge endpoint ownership")),
233
- timeoutMs
234
- );
264
+ const timer = setTimeout(() => done(new Error("Hasgard supervisor did not acknowledge endpoint ownership")), timeoutMs);
235
265
  child.on("message", message);
236
266
  child.once("exit", exited);
237
267
  child.send({ type: "endpoint", identity: { dev: stat.dev, ino: stat.ino } }, (error) => {
238
- if (error) done(error);
268
+ if (error)
269
+ done(error);
239
270
  });
240
271
  });
241
272
  }
242
273
  async stop() {
243
- if (this.starting) await this.starting;
244
- if (this.stopping) return this.stopping;
274
+ if (this.starting)
275
+ await this.starting;
276
+ if (this.stopping)
277
+ return this.stopping;
245
278
  this.stopping = this.stopChild();
246
279
  try {
247
280
  await this.stopping;
248
281
  } finally {
249
- this.stopping = void 0;
282
+ this.stopping = undefined;
250
283
  }
251
284
  }
252
285
  async stopChild() {
253
286
  const child = this.child;
254
- if (!child) return;
287
+ if (!child)
288
+ return;
255
289
  if (process.platform !== "win32") {
256
- if (this.applicationPid !== void 0) {
290
+ if (this.applicationPid !== undefined) {
257
291
  await stopProcessGroup(this.applicationPid);
258
292
  }
259
- const exited2 = child.exitCode !== null || child.signalCode !== null ? Promise.resolve() : new Promise((resolve) => child.once("exit", () => resolve()));
260
- if (child.connected) child.disconnect();
261
- const abort2 = new AbortController();
293
+ const exited = child.exitCode !== null || child.signalCode !== null ? Promise.resolve() : new Promise((resolve) => child.once("exit", () => resolve()));
294
+ if (child.connected)
295
+ child.disconnect();
296
+ const abort = new AbortController;
262
297
  try {
263
- if (await Promise.race([exited2, delay(11e3, "timeout", { signal: abort2.signal })]) === "timeout") {
298
+ if (await Promise.race([exited, delay2(11000, "timeout", { signal: abort.signal })]) === "timeout") {
264
299
  throw new Error(`Hasgard process supervisor ${child.pid} did not finish cleanup`);
265
300
  }
266
301
  } finally {
267
- abort2.abort();
302
+ abort.abort();
268
303
  }
269
- this.child = void 0;
270
- this.applicationPid = void 0;
304
+ this.child = undefined;
305
+ this.applicationPid = undefined;
271
306
  if (child.exitCode !== 0 || child.signalCode !== null) {
272
307
  throw new Error(`Hasgard supervisor cleanup failed (${child.signalCode ?? child.exitCode}):
273
308
  ${this.stderr}`);
274
309
  }
275
310
  return;
276
311
  }
277
- if (child.pid === void 0 || child.exitCode !== null || child.signalCode !== null) {
278
- this.child = void 0;
312
+ if (child.pid === undefined || child.exitCode !== null || child.signalCode !== null) {
313
+ this.child = undefined;
279
314
  return;
280
315
  }
281
316
  const exited = new Promise((resolve) => child.once("exit", () => resolve()));
282
- if (process.platform === "win32" && child.pid !== void 0) {
317
+ if (process.platform === "win32" && child.pid !== undefined) {
283
318
  await terminateWindowsProcessTree(child.pid);
284
- } else if (child.pid !== void 0) {
319
+ } else if (child.pid !== undefined) {
285
320
  process.kill(-child.pid, "SIGTERM");
286
321
  }
287
- const abort = new AbortController();
288
- const timeout = delay(5e3, "timeout", { signal: abort.signal });
322
+ const abort = new AbortController;
323
+ const timeout = delay2(5000, "timeout", { signal: abort.signal });
289
324
  try {
290
325
  if (await Promise.race([exited, timeout]) === "timeout") {
291
- if (process.platform === "win32" && child.pid !== void 0) await terminateWindowsProcessTree(child.pid);
292
- else if (child.pid !== void 0) process.kill(-child.pid, "SIGKILL");
326
+ if (process.platform === "win32" && child.pid !== undefined)
327
+ await terminateWindowsProcessTree(child.pid);
328
+ else if (child.pid !== undefined)
329
+ process.kill(-child.pid, "SIGKILL");
293
330
  await exited;
294
331
  }
295
332
  } finally {
296
333
  abort.abort();
297
334
  }
298
- this.child = void 0;
335
+ this.child = undefined;
299
336
  }
300
- };
337
+ }
301
338
  function appendOutput(previous, chunk) {
302
339
  const output = previous + chunk;
303
340
  return output.length > 65536 ? `[earlier output truncated]
@@ -306,9 +343,11 @@ ${output.slice(-65536)}` : output;
306
343
  async function endpointAcceptsConnections(path) {
307
344
  if (process.platform !== "win32") {
308
345
  try {
309
- if (!(await lstat(path)).isSocket()) throw new Error(`Hasgard endpoint exists but is not a socket: ${path}`);
346
+ if (!(await lstat(path)).isSocket())
347
+ throw new Error(`Hasgard endpoint exists but is not a socket: ${path}`);
310
348
  } catch (error) {
311
- if (error.code === "ENOENT") return false;
349
+ if (error.code === "ENOENT")
350
+ return false;
312
351
  throw error;
313
352
  }
314
353
  }
@@ -337,8 +376,11 @@ async function terminateWindowsProcessTree(pid) {
337
376
  }
338
377
 
339
378
  // src/rpc-client.ts
340
- import { createConnection as createConnection2 } from "net";
341
- import { createInterface } from "readline";
379
+ import { createConnection as createConnection2 } from "node:net";
380
+ import { createInterface } from "node:readline";
381
+
382
+ // src/protocol-limits.ts
383
+ var MAX_REQUEST_BYTES = 1048576;
342
384
 
343
385
  // src/protocol.ts
344
386
  function isObject(value) {
@@ -347,12 +389,16 @@ function isObject(value) {
347
389
  function parseRpcResponse(line) {
348
390
  const value = JSON.parse(line);
349
391
  const invalid = () => new Error(`Invalid Hasgard JSON-RPC envelope: ${line}`);
350
- if (!isObject(value) || value.jsonrpc !== "2.0" || !Object.hasOwn(value, "id")) throw invalid();
351
- if (value.id !== null && (!Number.isSafeInteger(value.id) || value.id < 0)) throw invalid();
392
+ if (!isObject(value) || value.jsonrpc !== "2.0" || !Object.hasOwn(value, "id"))
393
+ throw invalid();
394
+ if (value.id !== null && (!Number.isSafeInteger(value.id) || value.id < 0))
395
+ throw invalid();
352
396
  const hasResult = Object.hasOwn(value, "result");
353
397
  const hasError = Object.hasOwn(value, "error");
354
- if (hasResult === hasError) throw invalid();
355
- if (hasResult && value.id === null) throw invalid();
398
+ if (hasResult === hasError)
399
+ throw invalid();
400
+ if (hasResult && value.id === null)
401
+ throw invalid();
356
402
  if (hasError) {
357
403
  const error = value.error;
358
404
  if (!isObject(error) || !Number.isInteger(error.code) || error.code < -2147483648 || error.code > 2147483647 || typeof error.message !== "string")
@@ -361,11 +407,8 @@ function parseRpcResponse(line) {
361
407
  return value;
362
408
  }
363
409
 
364
- // src/protocol-limits.ts
365
- var MAX_REQUEST_BYTES = 1048576;
366
-
367
410
  // src/rpc-client.ts
368
- var HasgardRpcError = class extends Error {
411
+ class HasgardRpcError extends Error {
369
412
  code;
370
413
  data;
371
414
  constructor(code, message, data) {
@@ -374,18 +417,20 @@ var HasgardRpcError = class extends Error {
374
417
  this.code = code;
375
418
  this.data = data;
376
419
  }
377
- };
378
- var HasgardRpcClient = class {
420
+ }
421
+
422
+ class HasgardRpcClient {
379
423
  socketPath;
380
424
  socket;
381
425
  lines;
382
426
  nextId = 1;
383
- pending = /* @__PURE__ */ new Map();
427
+ pending = new Map;
384
428
  constructor(socketPath) {
385
429
  this.socketPath = socketPath;
386
430
  }
387
431
  async connect(timeoutMs) {
388
- if (this.socket) throw new Error("Hasgard RPC client is already connected");
432
+ if (this.socket)
433
+ throw new Error("Hasgard RPC client is already connected");
389
434
  const socket = createConnection2({ path: this.socketPath });
390
435
  this.socket = socket;
391
436
  try {
@@ -414,24 +459,28 @@ var HasgardRpcClient = class {
414
459
  socket.once("close", closed);
415
460
  });
416
461
  } catch (error) {
417
- if (this.socket === socket) this.socket = void 0;
462
+ if (this.socket === socket)
463
+ this.socket = undefined;
418
464
  socket.destroy();
419
465
  throw error;
420
466
  }
421
467
  this.lines = createInterface({ input: socket });
422
468
  this.lines.on("line", (line) => this.handleLine(line));
423
469
  socket.on("close", () => {
424
- if (this.socket !== socket) return;
470
+ if (this.socket !== socket)
471
+ return;
425
472
  this.rejectPending(new Error("Hasgard socket closed"));
426
473
  this.disconnect();
427
474
  });
428
475
  socket.on("error", (error) => {
429
- if (this.socket === socket) this.rejectPending(error);
476
+ if (this.socket === socket)
477
+ this.rejectPending(error);
430
478
  });
431
479
  }
432
480
  async call(method, params) {
433
481
  const socket = this.socket;
434
- if (!socket || socket.destroyed || !socket.writable) throw new Error("Hasgard RPC client is not connected");
482
+ if (!socket || socket.destroyed || !socket.writable)
483
+ throw new Error("Hasgard RPC client is not connected");
435
484
  const id = this.nextId;
436
485
  this.nextId += 1;
437
486
  const request = params ? { jsonrpc: "2.0", id, method, params } : { jsonrpc: "2.0", id, method };
@@ -448,9 +497,11 @@ var HasgardRpcClient = class {
448
497
  });
449
498
  });
450
499
  socket.write(line, (error) => {
451
- if (!error) return;
500
+ if (!error)
501
+ return;
452
502
  const pending = this.pending.get(id);
453
- if (!pending) return;
503
+ if (!pending)
504
+ return;
454
505
  this.pending.delete(id);
455
506
  pending.reject(error);
456
507
  });
@@ -458,9 +509,9 @@ var HasgardRpcClient = class {
458
509
  }
459
510
  disconnect() {
460
511
  this.lines?.close();
461
- this.lines = void 0;
512
+ this.lines = undefined;
462
513
  this.socket?.destroy();
463
- this.socket = void 0;
514
+ this.socket = undefined;
464
515
  this.rejectPending(new Error("Hasgard RPC client disconnected"));
465
516
  }
466
517
  handleLine(line) {
@@ -475,7 +526,7 @@ var HasgardRpcClient = class {
475
526
  this.rejectPending(new HasgardRpcError(response.error.code, response.error.message, response.error.data));
476
527
  return;
477
528
  }
478
- const pending = response.id === null ? void 0 : this.pending.get(response.id);
529
+ const pending = response.id === null ? undefined : this.pending.get(response.id);
479
530
  if (!pending) {
480
531
  this.rejectPending(new Error(`Unexpected Hasgard JSON-RPC response id: ${response.id}`));
481
532
  return;
@@ -488,19 +539,20 @@ var HasgardRpcClient = class {
488
539
  pending.resolve(response.result);
489
540
  }
490
541
  rejectPending(error) {
491
- for (const pending of this.pending.values()) pending.reject(error);
542
+ for (const pending of this.pending.values())
543
+ pending.reject(error);
492
544
  this.pending.clear();
493
545
  }
494
- };
546
+ }
495
547
 
496
548
  // src/window.ts
497
- import { readFile } from "fs/promises";
498
- import { basename } from "path";
499
- var locks = /* @__PURE__ */ new WeakMap();
549
+ import { readFile } from "node:fs/promises";
550
+ import { basename } from "node:path";
551
+ var locks = new WeakMap;
500
552
  function lockFor(rpc, label) {
501
553
  let clientLocks = locks.get(rpc);
502
554
  if (!clientLocks) {
503
- clientLocks = /* @__PURE__ */ new Map();
555
+ clientLocks = new Map;
504
556
  locks.set(rpc, clientLocks);
505
557
  }
506
558
  let lock = clientLocks.get(label);
@@ -518,36 +570,44 @@ function withWindow(window, params) {
518
570
  }
519
571
  async function readFileInputs(files) {
520
572
  const list = Array.isArray(files) ? files : [files];
521
- return Promise.all(
522
- list.map(async (entry) => {
523
- if (typeof entry !== "string") return entry;
524
- const contents = await readFile(entry);
525
- return { name: basename(entry), data: contents.toString("base64") };
526
- })
527
- );
573
+ return Promise.all(list.map(async (entry) => {
574
+ if (typeof entry !== "string")
575
+ return entry;
576
+ const contents = await readFile(entry);
577
+ return { name: basename(entry), data: contents.toString("base64") };
578
+ }));
528
579
  }
529
580
  function targetParams(target) {
530
- if ("ref" in target) return { ref: target.ref };
581
+ if ("ref" in target)
582
+ return { ref: target.ref };
531
583
  if ("selector" in target) {
532
- return target.index === void 0 ? { selector: target.selector } : { selector: target.selector, index: target.index };
584
+ return target.index === undefined ? { selector: target.selector } : { selector: target.selector, index: target.index };
533
585
  }
534
586
  return { x: target.x, y: target.y };
535
587
  }
536
588
  function clickParams(options) {
537
589
  const params = {};
538
- if (options.modifiers !== void 0) params.modifiers = options.modifiers;
539
- if (options.button !== void 0) params.button = options.button;
540
- if (options.clickCount !== void 0) params.clickCount = options.clickCount;
541
- if (options.position !== void 0) params.position = { ...options.position };
590
+ if (options.modifiers !== undefined)
591
+ params.modifiers = options.modifiers;
592
+ if (options.button !== undefined)
593
+ params.button = options.button;
594
+ if (options.clickCount !== undefined)
595
+ params.clickCount = options.clickCount;
596
+ if (options.position !== undefined)
597
+ params.position = { ...options.position };
542
598
  return params;
543
599
  }
544
600
  function routeParams(options) {
545
601
  const params = { pattern: options.pattern };
546
- if (options.method !== void 0) params.method = options.method;
547
- if (options.times !== void 0) params.times = options.times;
548
- if ("status" in options && options.status !== void 0) params.status = options.status;
549
- if ("body" in options && options.body !== void 0) params.body = options.body;
550
- if ("contentType" in options && options.contentType !== void 0) {
602
+ if (options.method !== undefined)
603
+ params.method = options.method;
604
+ if (options.times !== undefined)
605
+ params.times = options.times;
606
+ if ("status" in options && options.status !== undefined)
607
+ params.status = options.status;
608
+ if ("body" in options && options.body !== undefined)
609
+ params.body = options.body;
610
+ if ("contentType" in options && options.contentType !== undefined) {
551
611
  params.contentType = options.contentType;
552
612
  }
553
613
  return params;
@@ -595,27 +655,32 @@ function expectRecord(value, context) {
595
655
  return value;
596
656
  }
597
657
  function expectString(value, context) {
598
- if (typeof value !== "string") throw new Error(`${context} must be a string`);
658
+ if (typeof value !== "string")
659
+ throw new Error(`${context} must be a string`);
599
660
  return value;
600
661
  }
601
662
  function expectBoolean(value, context) {
602
- if (typeof value !== "boolean") throw new Error(`${context} must be a boolean`);
663
+ if (typeof value !== "boolean")
664
+ throw new Error(`${context} must be a boolean`);
603
665
  return value;
604
666
  }
605
667
  function expectNumber(value, context) {
606
- if (typeof value !== "number") throw new Error(`${context} must be a number`);
668
+ if (typeof value !== "number")
669
+ throw new Error(`${context} must be a number`);
607
670
  return value;
608
671
  }
609
672
  function expectArray(value, context) {
610
- if (!Array.isArray(value)) throw new Error(`${context} must be an array`);
673
+ if (!Array.isArray(value))
674
+ throw new Error(`${context} must be an array`);
611
675
  return value;
612
676
  }
613
677
  function expectNullableString(value, context) {
614
- if (value === null) return null;
678
+ if (value === null)
679
+ return null;
615
680
  return expectString(value, context);
616
681
  }
617
682
  function optionalString(value, context) {
618
- return value === void 0 ? void 0 : expectString(value, context);
683
+ return value === undefined ? undefined : expectString(value, context);
619
684
  }
620
685
  function parseConsoleLogEntry(value, index) {
621
686
  const entry = expectRecord(value, `console.getLogs[${index}]`);
@@ -650,7 +715,8 @@ function parseFormField(value, context) {
650
715
  name: expectString(field.name, `${context}.name`),
651
716
  value: Array.isArray(raw) ? raw.map((option, index) => expectString(option, `${context}.value[${index}]`)) : expectString(raw, `${context}.value`)
652
717
  };
653
- if (field.checked !== void 0) parsed.checked = expectBoolean(field.checked, `${context}.checked`);
718
+ if (field.checked !== undefined)
719
+ parsed.checked = expectBoolean(field.checked, `${context}.checked`);
654
720
  return parsed;
655
721
  }
656
722
  function parseFormEntry(value, index) {
@@ -661,11 +727,9 @@ function parseFormEntry(value, index) {
661
727
  name: expectString(form.name, `${context}.name`),
662
728
  action: expectString(form.action, `${context}.action`),
663
729
  method: expectString(form.method, `${context}.method`),
664
- fields: expectArray(form.fields, `${context}.fields`).map(
665
- (field, fieldIndex) => parseFormField(field, `${context}.fields[${fieldIndex}]`)
666
- )
730
+ fields: expectArray(form.fields, `${context}.fields`).map((field, fieldIndex) => parseFormField(field, `${context}.fields[${fieldIndex}]`))
667
731
  };
668
- if (form.fieldsTruncated !== void 0) {
732
+ if (form.fieldsTruncated !== undefined) {
669
733
  parsed.fieldsTruncated = expectBoolean(form.fieldsTruncated, `${context}.fieldsTruncated`);
670
734
  }
671
735
  return parsed;
@@ -674,15 +738,20 @@ function parseWatchNode(value, context) {
674
738
  const node = expectRecord(value, context);
675
739
  const parsed = { tag: expectString(node.tag, `${context}.tag`) };
676
740
  const id = optionalString(node.id, `${context}.id`);
677
- if (id !== void 0) parsed.id = id;
741
+ if (id !== undefined)
742
+ parsed.id = id;
678
743
  const className = optionalString(node.class, `${context}.class`);
679
- if (className !== void 0) parsed.class = className;
744
+ if (className !== undefined)
745
+ parsed.class = className;
680
746
  const text = optionalString(node.text, `${context}.text`);
681
- if (text !== void 0) parsed.text = text;
747
+ if (text !== undefined)
748
+ parsed.text = text;
682
749
  const attr = optionalString(node.attr, `${context}.attr`);
683
- if (attr !== void 0) parsed.attr = attr;
750
+ if (attr !== undefined)
751
+ parsed.attr = attr;
684
752
  const attrValue = optionalString(node.value, `${context}.value`);
685
- if (attrValue !== void 0) parsed.value = attrValue;
753
+ if (attrValue !== undefined)
754
+ parsed.value = attrValue;
686
755
  return parsed;
687
756
  }
688
757
  function parseWatchNodes(value, context) {
@@ -693,9 +762,7 @@ function parseDiffChange(value, index) {
693
762
  return {
694
763
  old: parseSnapshotElement(change.old, index),
695
764
  new: parseSnapshotElement(change.new, index),
696
- changes: expectArray(change.changes, `diff.changed[${index}].changes`).map(
697
- (entry, entryIndex) => expectString(entry, `diff.changed[${index}].changes[${entryIndex}]`)
698
- )
765
+ changes: expectArray(change.changes, `diff.changed[${index}].changes`).map((entry, entryIndex) => expectString(entry, `diff.changed[${index}].changes[${entryIndex}]`))
699
766
  };
700
767
  }
701
768
  function parseRecorderEntry(value, index) {
@@ -706,7 +773,8 @@ function parseRecorderEntry(value, index) {
706
773
  timestamp: expectNumber(entry.timestamp, `record.stop.entries[${index}].timestamp`)
707
774
  };
708
775
  }
709
- var HasgardApplication = class {
776
+
777
+ class HasgardApplication {
710
778
  rpc;
711
779
  constructor(rpc) {
712
780
  this.rpc = rpc;
@@ -720,7 +788,8 @@ var HasgardApplication = class {
720
788
  }
721
789
  async windows() {
722
790
  const value = expectRecord(await this.rpc.call("windows.list"), "windows.list result");
723
- if (!Array.isArray(value.windows)) throw new Error("windows.list.windows must be an array");
791
+ if (!Array.isArray(value.windows))
792
+ throw new Error("windows.list.windows must be an array");
724
793
  return value.windows.map((entry, index) => {
725
794
  const window = expectRecord(entry, `windows[${index}]`);
726
795
  const info = {
@@ -728,7 +797,7 @@ var HasgardApplication = class {
728
797
  url: expectString(window.url, `windows[${index}].url`),
729
798
  title: expectString(window.title, `windows[${index}].title`)
730
799
  };
731
- if (window.native_id !== void 0) {
800
+ if (window.native_id !== undefined) {
732
801
  info.nativeId = expectNumber(window.native_id, `windows[${index}].native_id`);
733
802
  }
734
803
  return info;
@@ -741,9 +810,11 @@ var HasgardApplication = class {
741
810
  const window = this.window(label);
742
811
  try {
743
812
  const state = await window.state();
744
- if (state.url !== "about:blank" && state.readyState !== "loading") return window;
813
+ if (state.url !== "about:blank" && state.readyState !== "loading")
814
+ return window;
745
815
  } catch (error) {
746
- if (!(error instanceof HasgardRpcError) || error.code !== -32002) throw error;
816
+ if (!(error instanceof HasgardRpcError) || error.code !== -32002)
817
+ throw error;
747
818
  }
748
819
  }
749
820
  await new Promise((resolve) => setTimeout(resolve, 50));
@@ -754,7 +825,8 @@ var HasgardApplication = class {
754
825
  const window = await this.waitForWindow(label, timeoutMs);
755
826
  const deadline = Date.now() + timeoutMs;
756
827
  while (Date.now() <= deadline) {
757
- if (await window.locator(readySelector).count() === 1) return window;
828
+ if (await window.locator(readySelector).count() === 1)
829
+ return window;
758
830
  await new Promise((resolve) => setTimeout(resolve, 50));
759
831
  }
760
832
  throw new Error(`Window '${label}' did not match ready selector '${readySelector}' within ${timeoutMs}ms`);
@@ -762,10 +834,6 @@ var HasgardApplication = class {
762
834
  window(label) {
763
835
  return new HasgardWindow(this.rpc, label);
764
836
  }
765
- /**
766
- * Action recorder. Captures the RPC calls this connection makes into a
767
- * replayable scenario — this is not video capture.
768
- */
769
837
  recorder = {
770
838
  start: async () => {
771
839
  await this.rpc.call("record.start");
@@ -789,8 +857,9 @@ var HasgardApplication = class {
789
857
  await this.rpc.call("record.add", entry);
790
858
  }
791
859
  };
792
- };
793
- var HasgardWindow = class {
860
+ }
861
+
862
+ class HasgardWindow {
794
863
  label;
795
864
  rpc;
796
865
  constructor(rpc, label) {
@@ -803,12 +872,6 @@ var HasgardWindow = class {
803
872
  getByRole(role, query = {}) {
804
873
  return new HasgardLocator(this, { kind: "role", role, query });
805
874
  }
806
- /**
807
- * Locate by visible text. Matching runs in the page against the live DOM, not
808
- * against a snapshot's `name`: that field collapses aria-label, alt, `<label>`,
809
- * placeholder, and text into one 50-character string, so a button with an
810
- * aria-label would never be findable by the text it actually shows.
811
- */
812
875
  getByText(text, query = {}) {
813
876
  return new HasgardLocator(this, { kind: "query", by: "text", value: text, query });
814
877
  }
@@ -824,27 +887,16 @@ var HasgardWindow = class {
824
887
  getByTitle(text, query = {}) {
825
888
  return new HasgardLocator(this, { kind: "query", by: "title", value: text, query });
826
889
  }
827
- /** Test ids always match exactly, so `save` cannot select `save-draft`. */
828
890
  getByTestId(testId) {
829
891
  return new HasgardLocator(this, { kind: "query", by: "testid", value: testId, query: {} });
830
892
  }
831
- /**
832
- * Scope locators to a same-origin `<iframe>`.
833
- *
834
- * Cross-origin frames are out of reach: the same-origin policy binds injected
835
- * script exactly as it binds the page's own, so no bridge can see into a
836
- * third-party payment or OAuth frame. Those raise an error naming the origin
837
- * problem rather than reporting a missing element.
838
- */
839
893
  frameLocator(selector) {
840
894
  return new HasgardFrameLocator(this, [selector]);
841
895
  }
842
896
  async snapshot(options = {}) {
843
- const raw = expectRecord(
844
- await this.rpc.call("snapshot", withWindow(this.label, options)),
845
- "snapshot result"
846
- );
847
- if (!Array.isArray(raw.elements)) throw new Error("snapshot.elements must be an array");
897
+ const raw = expectRecord(await this.rpc.call("snapshot", withWindow(this.label, options)), "snapshot result");
898
+ if (!Array.isArray(raw.elements))
899
+ throw new Error("snapshot.elements must be an array");
848
900
  return {
849
901
  elements: raw.elements.map((entry, index) => parseSnapshotElement(entry, index))
850
902
  };
@@ -874,31 +926,29 @@ var HasgardWindow = class {
874
926
  async title() {
875
927
  return expectString(await this.rpc.call("title", withWindow(this.label)), "title result");
876
928
  }
877
- /**
878
- * Navigate this webview by assigning `location.href`. Unlike Playwright's
879
- * `goto`, this does not wait for the load to finish and returns no response —
880
- * follow it with `waitFor` or `waitForWindowReady`.
881
- */
882
929
  async navigate(url) {
883
930
  await this.rpc.call("navigate", withWindow(this.label, { url }));
884
931
  }
885
- /** Scroll the document by a relative amount, or to `top`/`bottom`. */
886
932
  async scrollBy(options) {
887
933
  await this.rpc.call("scroll", withWindow(this.label, options));
888
934
  }
889
935
  async press(key, options) {
890
936
  const params = { key };
891
937
  if (options) {
892
- if (options.completion !== void 0) params.completion = options.completion;
893
- if (options.waitFor !== void 0) params.waitFor = options.waitFor;
894
- if (options.timeoutMs !== void 0) params.timeout = options.timeoutMs;
938
+ if (options.completion !== undefined)
939
+ params.completion = options.completion;
940
+ if (options.waitFor !== undefined)
941
+ params.waitFor = options.waitFor;
942
+ if (options.timeoutMs !== undefined)
943
+ params.timeout = options.timeoutMs;
895
944
  }
896
945
  await this.rpc.call("press", withWindow(this.label, params));
897
946
  }
898
947
  async diff(options = {}) {
899
948
  const { reference, ...snapshotOptions } = options;
900
949
  const params = { ...snapshotOptions };
901
- if (reference) params.reference = reference;
950
+ if (reference)
951
+ params.reference = reference;
902
952
  const value = expectRecord(await this.rpc.call("diff", withWindow(this.label, params)), "diff result");
903
953
  return {
904
954
  added: expectArray(value.added, "diff.added").map(parseSnapshotElement),
@@ -906,26 +956,25 @@ var HasgardWindow = class {
906
956
  changed: expectArray(value.changed, "diff.changed").map(parseDiffChange)
907
957
  };
908
958
  }
909
- /**
910
- * Poll a JavaScript expression in the webview until it returns a truthy
911
- * value, and return that value. The expression is polled rather than driven
912
- * by DOM mutations, so predicates that flip on a timer or a resolved fetch
913
- * are caught too. A predicate that throws rejects instead of being retried.
914
- */
915
959
  async waitForFunction(expression, options = {}) {
916
960
  const params = { expression };
917
- if (options.timeoutMs !== void 0) params.timeout = options.timeoutMs;
918
- if (options.pollMs !== void 0) params.poll = options.pollMs;
961
+ if (options.timeoutMs !== undefined)
962
+ params.timeout = options.timeoutMs;
963
+ if (options.pollMs !== undefined)
964
+ params.poll = options.pollMs;
919
965
  const value = expectRecord(await this.rpc.call("wait", withWindow(this.label, params)), "wait result");
920
966
  return value.value;
921
967
  }
922
- /** Observe DOM mutations until they settle, or reject when nothing changes. */
923
968
  async watch(options = {}) {
924
969
  const params = {};
925
- if (options.selector !== void 0) params.selector = options.selector;
926
- if (options.timeoutMs !== void 0) params.timeout = options.timeoutMs;
927
- if (options.stableMs !== void 0) params.stable = options.stableMs;
928
- if (options.requireMutation !== void 0) params.requireMutation = options.requireMutation;
970
+ if (options.selector !== undefined)
971
+ params.selector = options.selector;
972
+ if (options.timeoutMs !== undefined)
973
+ params.timeout = options.timeoutMs;
974
+ if (options.stableMs !== undefined)
975
+ params.stable = options.stableMs;
976
+ if (options.requireMutation !== undefined)
977
+ params.requireMutation = options.requireMutation;
929
978
  const value = expectRecord(await this.rpc.call("watch", withWindow(this.label, params)), "watch result");
930
979
  return {
931
980
  added: parseWatchNodes(value.added, "watch.added"),
@@ -935,43 +984,31 @@ var HasgardWindow = class {
935
984
  };
936
985
  }
937
986
  async consoleLogs(options = {}) {
938
- const value = await this.rpc.call(
939
- "console.getLogs",
940
- withWindow(this.label, options)
941
- );
987
+ const value = await this.rpc.call("console.getLogs", withWindow(this.label, options));
942
988
  return expectArray(value, "console.getLogs result").map(parseConsoleLogEntry);
943
989
  }
944
990
  async clearConsole() {
945
991
  await this.rpc.call("console.clear", withWindow(this.label));
946
992
  }
947
993
  async networkRequests(options = {}) {
948
- const value = await this.rpc.call(
949
- "network.getRequests",
950
- withWindow(this.label, options)
951
- );
994
+ const value = await this.rpc.call("network.getRequests", withWindow(this.label, options));
952
995
  return expectArray(value, "network.getRequests result").map(parseNetworkRequestEntry);
953
996
  }
954
997
  async clearNetwork() {
955
998
  await this.rpc.call("network.clear", withWindow(this.label));
956
999
  }
957
- /** `localStorage` by default; pass `{ session: true }` for `sessionStorage`. */
958
1000
  storage = {
959
1001
  get: async (key, options = {}) => {
960
- const value = expectRecord(
961
- await this.rpc.call("storage.get", withWindow(this.label, { key, ...options })),
962
- "storage.get result"
963
- );
964
- if (!expectBoolean(value.found, "storage.get.found")) return null;
1002
+ const value = expectRecord(await this.rpc.call("storage.get", withWindow(this.label, { key, ...options })), "storage.get result");
1003
+ if (!expectBoolean(value.found, "storage.get.found"))
1004
+ return null;
965
1005
  return expectString(value.value, "storage.get.value");
966
1006
  },
967
1007
  set: async (key, value, options = {}) => {
968
1008
  await this.rpc.call("storage.set", withWindow(this.label, { key, value, ...options }));
969
1009
  },
970
1010
  list: async (options = {}) => {
971
- const value = expectRecord(
972
- await this.rpc.call("storage.list", withWindow(this.label, { ...options })),
973
- "storage.list result"
974
- );
1011
+ const value = expectRecord(await this.rpc.call("storage.list", withWindow(this.label, { ...options })), "storage.list result");
975
1012
  return {
976
1013
  entries: expectArray(value.entries, "storage.list.entries").map((entry, index) => {
977
1014
  const parsed = expectRecord(entry, `storage.list.entries[${index}]`);
@@ -988,7 +1025,7 @@ var HasgardWindow = class {
988
1025
  }
989
1026
  };
990
1027
  async forms(selector) {
991
- const params = selector ? { selector } : void 0;
1028
+ const params = selector ? { selector } : undefined;
992
1029
  const value = expectRecord(await this.rpc.call("forms.dump", withWindow(this.label, params)), "forms.dump result");
993
1030
  return {
994
1031
  forms: expectArray(value.forms, "forms.dump.forms").map(parseFormEntry),
@@ -1002,22 +1039,21 @@ var HasgardWindow = class {
1002
1039
  return this.rpc.call("ipc", withWindow(this.label, { command, args }));
1003
1040
  }
1004
1041
  async screenshot(selector) {
1005
- const params = selector ? { selector } : void 0;
1042
+ const params = selector ? { selector } : undefined;
1006
1043
  const dataUrl = await this.rpc.call("screenshot", withWindow(this.label, params));
1007
1044
  const prefix = "data:image/png;base64,";
1008
- if (!dataUrl.startsWith(prefix)) throw new Error("screenshot result is not a PNG data URL");
1045
+ if (!dataUrl.startsWith(prefix))
1046
+ throw new Error("screenshot result is not a PNG data URL");
1009
1047
  return Buffer.from(dataUrl.slice(prefix.length), "base64");
1010
1048
  }
1011
- /** Start a bounded recording of this native window. macOS only; requires ffmpeg. */
1012
1049
  async startVideo(options) {
1013
1050
  await this.rpc.call("video.start", {
1014
1051
  window: this.label,
1015
1052
  output_path: options.outputPath,
1016
- ...options.fps === void 0 ? {} : { fps: options.fps },
1017
- ...options.maxDurationMs === void 0 ? {} : { max_duration_ms: options.maxDurationMs }
1053
+ ...options.fps === undefined ? {} : { fps: options.fps },
1054
+ ...options.maxDurationMs === undefined ? {} : { max_duration_ms: options.maxDurationMs }
1018
1055
  });
1019
1056
  }
1020
- /** Stop recording; capture and encoder failures reject rather than returning an empty artifact. */
1021
1057
  async stopVideo() {
1022
1058
  const value = expectRecord(await this.rpc.call("video.stop", { window: this.label }), "video.stop result");
1023
1059
  return {
@@ -1034,26 +1070,12 @@ var HasgardWindow = class {
1034
1070
  pendingResult: expectBoolean(value.pending_result, "video.pending_result")
1035
1071
  };
1036
1072
  }
1037
- /**
1038
- * Capture this window through the native compositor, writing a PNG to
1039
- * `outputPath`. macOS only.
1040
- *
1041
- * Unlike `screenshot`, which renders the DOM inside the page, this records
1042
- * what the window server actually shows — so it sees native chrome, layered
1043
- * content, and anything the DOM renderer cannot reproduce.
1044
- *
1045
- * The operating-system window id is resolved from this window's label, so
1046
- * callers do not have to discover it themselves.
1047
- */
1048
1073
  async screenshotNative(options) {
1049
1074
  const windowId = options.windowId ?? await this.nativeId();
1050
- const value = expectRecord(
1051
- await this.rpc.call("screenshot_native", {
1052
- window_id: windowId,
1053
- output_path: options.outputPath
1054
- }),
1055
- "screenshot_native result"
1056
- );
1075
+ const value = expectRecord(await this.rpc.call("screenshot_native", {
1076
+ window_id: windowId,
1077
+ output_path: options.outputPath
1078
+ }), "screenshot_native result");
1057
1079
  return {
1058
1080
  output_path: expectString(value.output_path, "screenshot_native.output_path"),
1059
1081
  window_id: expectNumber(value.window_id, "screenshot_native.window_id"),
@@ -1065,102 +1087,58 @@ var HasgardWindow = class {
1065
1087
  tcc_denied: expectBoolean(value.tcc_denied, "screenshot_native.tcc_denied")
1066
1088
  };
1067
1089
  }
1068
- /**
1069
- * Scroll the page by wheel.
1070
- *
1071
- * The document sees a real `wheel` event first and may cancel it; the scroll
1072
- * only follows if it does not. Resolves to whether the page cancelled — which
1073
- * is how you tell "the app intercepted my wheel" from "nothing was
1074
- * listening". Use the locator method to scroll a nested scrollport.
1075
- */
1076
1090
  async wheel(deltaX, deltaY) {
1077
1091
  const value = expectRecord(await this.call("wheel", { deltaX, deltaY }), "wheel result");
1078
1092
  return expectBoolean(value.defaultPrevented, "wheel.defaultPrevented");
1079
1093
  }
1080
- /**
1081
- * The page's modal dialogs (`alert`, `confirm`, `prompt`).
1082
- *
1083
- * These block the webview until answered, so the bridge always answers them —
1084
- * dismissing by default, as Playwright does. A test that never touches this
1085
- * namespace still cannot hang on a `confirm()`, and can read back what the
1086
- * app tried to ask.
1087
- */
1088
1094
  dialogs = {
1089
- /** Answer every subsequent dialog with OK. `promptText` fills a `prompt`. */
1090
1095
  accept: async (promptText) => {
1091
1096
  const params = { action: "accept" };
1092
- if (promptText !== void 0) params.promptText = promptText;
1097
+ if (promptText !== undefined)
1098
+ params.promptText = promptText;
1093
1099
  return parseDialogPolicy(await this.call("dialog.handle", params));
1094
1100
  },
1095
- /** Answer every subsequent dialog with Cancel. This is the default. */
1096
1101
  dismiss: async () => parseDialogPolicy(await this.call("dialog.handle", { action: "dismiss" })),
1097
- /** Every dialog the page has opened so far, oldest first. */
1098
1102
  list: async () => {
1099
1103
  const value = expectRecord(await this.call("dialog.list"), "dialog.list result");
1100
- if (!Array.isArray(value.dialogs)) throw new Error("dialog.list.dialogs must be an array");
1104
+ if (!Array.isArray(value.dialogs))
1105
+ throw new Error("dialog.list.dialogs must be an array");
1101
1106
  return {
1102
1107
  dialogs: value.dialogs.map((entry, index) => parseDialogRecord(entry, index)),
1103
1108
  policy: parseDialogPolicy(value.policy)
1104
1109
  };
1105
1110
  },
1106
- /** Forget the recorded dialogs. Leaves the accept/dismiss policy alone. */
1107
1111
  clear: async () => {
1108
1112
  await this.call("dialog.clear");
1109
1113
  }
1110
1114
  };
1111
- /**
1112
- * Shape what the page's own requests receive, without a real backend.
1113
- *
1114
- * Rules are declarative rather than Playwright's per-request callback: the
1115
- * bridge only ever answers requests, so it cannot call back into this process
1116
- * and await a handler while the page sits inside `fetch`. The same constraint
1117
- * shaped `dialogs`.
1118
- *
1119
- * Rules are checked in registration order and the first match wins, so
1120
- * register the narrow ones before a catch-all. Both `fetch` and
1121
- * `XMLHttpRequest` are covered; resources the webview loads itself -- the
1122
- * document, `<img src>`, stylesheets -- are not, since no page script sees
1123
- * them.
1124
- */
1125
1115
  routes = {
1126
- /** Answer matching requests with this response instead of the network. */
1127
1116
  fulfill: async (options) => {
1128
- const value = expectRecord(
1129
- await this.call("route.add", { ...routeParams(options), action: "fulfill" }),
1130
- "route.add result"
1131
- );
1117
+ const value = expectRecord(await this.call("route.add", { ...routeParams(options), action: "fulfill" }), "route.add result");
1132
1118
  return { id: expectNumber(value.id, "route.add.id") };
1133
1119
  },
1134
- /** Fail matching requests the way a dropped connection does. */
1135
1120
  abort: async (options) => {
1136
- const value = expectRecord(
1137
- await this.call("route.add", { ...routeParams(options), action: "abort" }),
1138
- "route.add result"
1139
- );
1121
+ const value = expectRecord(await this.call("route.add", { ...routeParams(options), action: "abort" }), "route.add result");
1140
1122
  return { id: expectNumber(value.id, "route.add.id") };
1141
1123
  },
1142
- /** The active rules, their use counts, and every request they answered. */
1143
1124
  list: async () => {
1144
1125
  const value = expectRecord(await this.call("route.list"), "route.list result");
1145
1126
  return {
1146
1127
  routes: expectArray(value.routes, "route.list.routes").map((entry, index) => parseRouteRule(entry, index)),
1147
- intercepted: expectArray(value.intercepted, "route.list.intercepted").map(
1148
- (entry, index) => parseInterceptedRequest(entry, index)
1149
- )
1128
+ intercepted: expectArray(value.intercepted, "route.list.intercepted").map((entry, index) => parseInterceptedRequest(entry, index))
1150
1129
  };
1151
1130
  },
1152
- /** Remove every rule and its interception log. */
1153
1131
  clear: async () => {
1154
1132
  const value = expectRecord(await this.call("route.clear"), "route.clear result");
1155
1133
  return { removed: expectNumber(value.removed, "route.clear.removed") };
1156
1134
  }
1157
1135
  };
1158
- /** This window's operating-system window id, where the platform exposes one. */
1159
1136
  async nativeId() {
1160
1137
  const listing = await new HasgardApplication(this.rpc).windows();
1161
1138
  const self = listing.find((entry) => entry.label === this.label);
1162
- if (!self) throw new Error(`Window '${this.label}' is no longer open`);
1163
- if (self.nativeId === void 0) {
1139
+ if (!self)
1140
+ throw new Error(`Window '${this.label}' is no longer open`);
1141
+ if (self.nativeId === undefined) {
1164
1142
  throw new Error(`Window '${this.label}' has no native window id on this platform`);
1165
1143
  }
1166
1144
  return self.nativeId;
@@ -1183,17 +1161,17 @@ var HasgardWindow = class {
1183
1161
  release();
1184
1162
  }
1185
1163
  }
1186
- };
1187
- var HasgardFrameLocator = class _HasgardFrameLocator {
1164
+ }
1165
+
1166
+ class HasgardFrameLocator {
1188
1167
  windowRef;
1189
1168
  frame;
1190
1169
  constructor(window, frame) {
1191
1170
  this.windowRef = window;
1192
1171
  this.frame = frame;
1193
1172
  }
1194
- /** Descend into a frame nested inside this one. */
1195
1173
  frameLocator(selector) {
1196
- return new _HasgardFrameLocator(this.windowRef, [...this.frame, selector]);
1174
+ return new HasgardFrameLocator(this.windowRef, [...this.frame, selector]);
1197
1175
  }
1198
1176
  locator(selector) {
1199
1177
  return new HasgardLocator(this.windowRef, { kind: "selector", selector, frame: [...this.frame] });
@@ -1219,29 +1197,21 @@ var HasgardFrameLocator = class _HasgardFrameLocator {
1219
1197
  getByTestId(testId) {
1220
1198
  return this.byDimension("testid", testId, {});
1221
1199
  }
1222
- /** Snapshot the frame's own document rather than the top one. */
1223
1200
  async snapshot(options = {}) {
1224
1201
  return this.windowRef.snapshot({ ...options, frame: [...this.frame] });
1225
1202
  }
1226
1203
  byDimension(by, value, query) {
1227
1204
  return new HasgardLocator(this.windowRef, { kind: "query", by, value, query, frame: [...this.frame] });
1228
1205
  }
1229
- };
1230
- var HasgardLocator = class _HasgardLocator {
1206
+ }
1207
+
1208
+ class HasgardLocator {
1231
1209
  window;
1232
1210
  query;
1233
1211
  constructor(window, query) {
1234
1212
  this.window = window;
1235
1213
  this.query = query;
1236
1214
  }
1237
- /**
1238
- * Click this element.
1239
- *
1240
- * A right or middle click raises `contextmenu`/`auxclick` and no `click`, and
1241
- * `clickCount: 2` escalates `detail` across the presses before a single
1242
- * `dblclick` -- both matching what the platform does, so a listener bound to
1243
- * the wrong event fails here rather than in front of a user.
1244
- */
1245
1215
  async click(options = {}) {
1246
1216
  await this.withTarget((target) => this.window.call("click", { ...this.scoped(target), ...clickParams(options) }));
1247
1217
  }
@@ -1251,61 +1221,37 @@ var HasgardLocator = class _HasgardLocator {
1251
1221
  async type(text) {
1252
1222
  await this.withTarget((target) => this.window.call("type", { ...this.scoped(target), text }));
1253
1223
  }
1254
- /** Empty the control, firing `input` and `change` as `fill` does. */
1255
1224
  async clear() {
1256
1225
  await this.fill("");
1257
1226
  }
1258
- /**
1259
- * Select files on an `<input type="file">` without a native file chooser,
1260
- * which an embedded webview gives no way to drive.
1261
- *
1262
- * Strings are read from the test machine's disk; `DropFile` payloads let you
1263
- * supply content that never existed as a file. An empty array deselects.
1264
- */
1265
1227
  async setInputFiles(files) {
1266
1228
  const payloads = await readFileInputs(files);
1267
- await this.withTarget(
1268
- (target) => this.window.call("setInputFiles", {
1269
- ...this.scoped(target),
1270
- files: payloads
1271
- })
1272
- );
1273
- }
1274
- /**
1275
- * Scroll this element by wheel.
1276
- *
1277
- * The page sees a real `wheel` event first and may cancel it; the scroll only
1278
- * follows if it does not — the same two steps a physical wheel produces.
1279
- * Resolves to whether the page cancelled.
1280
- */
1229
+ await this.withTarget((target) => this.window.call("setInputFiles", {
1230
+ ...this.scoped(target),
1231
+ files: payloads
1232
+ }));
1233
+ }
1281
1234
  async wheel(deltaX, deltaY) {
1282
1235
  return this.withTarget(async (target) => {
1283
- const value = expectRecord(
1284
- await this.window.call("wheel", { ...this.scoped(target), deltaX, deltaY }),
1285
- "wheel result"
1286
- );
1236
+ const value = expectRecord(await this.window.call("wheel", { ...this.scoped(target), deltaX, deltaY }), "wheel result");
1287
1237
  return expectBoolean(value.defaultPrevented, "wheel.defaultPrevented");
1288
1238
  });
1289
1239
  }
1290
1240
  async selectOption(value) {
1291
1241
  await this.withTarget((target) => this.window.call("select", { ...this.scoped(target), value }));
1292
1242
  }
1293
- /** Drive the checkbox to checked. A no-op when it is already checked. */
1294
1243
  async check() {
1295
1244
  await this.withTarget((target) => this.window.call("check", { ...this.scoped(target), checked: true }));
1296
1245
  }
1297
- /** Drive the checkbox to unchecked. A no-op when it is already unchecked. */
1298
1246
  async uncheck() {
1299
1247
  await this.withTarget((target) => this.window.call("check", { ...this.scoped(target), checked: false }));
1300
1248
  }
1301
- /** Flip the checkbox, whatever its current state. */
1302
1249
  async toggle() {
1303
1250
  await this.withTarget((target) => this.window.call("check", this.scoped(target)));
1304
1251
  }
1305
1252
  async hover() {
1306
1253
  await this.withTarget((target) => this.window.call("hover", this.scoped(target)));
1307
1254
  }
1308
- /** Shorthand for `click({ clickCount: 2 })`; other options still apply. */
1309
1255
  async dblclick(options = {}) {
1310
1256
  await this.click({ ...options, clickCount: 2 });
1311
1257
  }
@@ -1315,11 +1261,8 @@ var HasgardLocator = class _HasgardLocator {
1315
1261
  async blur() {
1316
1262
  await this.withTarget((target) => this.window.call("blur", this.scoped(target)));
1317
1263
  }
1318
- /** Scroll this element's own scrollport. */
1319
1264
  async scrollBy(options) {
1320
- await this.withTarget(
1321
- (target) => this.window.call("scroll", { ...this.scoped(target), ...options })
1322
- );
1265
+ await this.withTarget((target) => this.window.call("scroll", { ...this.scoped(target), ...options }));
1323
1266
  }
1324
1267
  async boundingBox() {
1325
1268
  return this.withTarget(async (target) => {
@@ -1342,25 +1285,18 @@ var HasgardLocator = class _HasgardLocator {
1342
1285
  return !await this.isDisabled();
1343
1286
  }
1344
1287
  async textContent() {
1345
- return this.withTarget(
1346
- async (target) => expectString(await this.window.call("text", this.scoped(target)), "text result")
1347
- );
1288
+ return this.withTarget(async (target) => expectString(await this.window.call("text", this.scoped(target)), "text result"));
1348
1289
  }
1349
1290
  async inputValue() {
1350
- return this.withTarget(
1351
- async (target) => expectString(await this.window.call("value", this.scoped(target)), "value result")
1352
- );
1291
+ return this.withTarget(async (target) => expectString(await this.window.call("value", this.scoped(target)), "value result"));
1353
1292
  }
1354
1293
  async innerHTML() {
1355
- return this.withTarget(
1356
- async (target) => expectString(await this.window.call("html", this.scoped(target)), "html result")
1357
- );
1294
+ return this.withTarget(async (target) => expectString(await this.window.call("html", this.scoped(target)), "html result"));
1358
1295
  }
1359
1296
  async getAttribute(name) {
1360
1297
  const attributes = await this.attributes();
1361
- return Object.prototype.hasOwnProperty.call(attributes, name) ? attributes[name] : null;
1298
+ return Object.hasOwn(attributes, name) ? attributes[name] : null;
1362
1299
  }
1363
- /** Drag this element onto another one in the same window. */
1364
1300
  async dragTo(target) {
1365
1301
  if (target.window !== this.window) {
1366
1302
  throw new Error("dragTo requires both locators to belong to the same window");
@@ -1374,33 +1310,26 @@ var HasgardLocator = class _HasgardLocator {
1374
1310
  });
1375
1311
  });
1376
1312
  }
1377
- /** Drag this element by a pixel offset from its centre. */
1378
1313
  async dragBy(offset) {
1379
- await this.withTarget(
1380
- (target) => this.window.call("drag", { source: this.scoped(target), offset: { x: offset.x, y: offset.y } })
1381
- );
1314
+ await this.withTarget((target) => this.window.call("drag", { source: this.scoped(target), offset: { x: offset.x, y: offset.y } }));
1382
1315
  }
1383
- /** Dispatch a file drop onto this element. `data` is base64-encoded. */
1384
1316
  async dropFiles(files) {
1385
- await this.withTarget(
1386
- (target) => this.window.call("drop", {
1387
- ...this.scoped(target),
1388
- files
1389
- })
1390
- );
1317
+ await this.withTarget((target) => this.window.call("drop", {
1318
+ ...this.scoped(target),
1319
+ files
1320
+ }));
1391
1321
  }
1392
1322
  async attributes() {
1393
1323
  return this.withTarget(async (target) => {
1394
1324
  const value = expectRecord(await this.window.call("attrs", this.scoped(target)), "attrs result");
1395
- return Object.fromEntries(
1396
- Object.entries(value).map(([name, attr]) => [name, expectString(attr, `attribute ${name}`)])
1397
- );
1325
+ return Object.fromEntries(Object.entries(value).map(([name, attr]) => [name, expectString(attr, `attribute ${name}`)]));
1398
1326
  });
1399
1327
  }
1400
1328
  async isVisible() {
1401
1329
  return this.window.runExclusive(async () => {
1402
1330
  const target = await this.resolveUnique(true);
1403
- if (target === null) return false;
1331
+ if (target === null)
1332
+ return false;
1404
1333
  const value = expectRecord(await this.window.call("visible", this.scoped(target)), "visible result");
1405
1334
  return expectBoolean(value.visible, "visible.visible");
1406
1335
  });
@@ -1412,19 +1341,14 @@ var HasgardLocator = class _HasgardLocator {
1412
1341
  });
1413
1342
  }
1414
1343
  async count() {
1415
- const total = this.query.kind === "selector" && !this.query.filters?.length ? expectNumber(
1416
- expectRecord(
1417
- await this.window.call("count", { selector: this.query.selector, ...this.frameParams() }),
1418
- "count result"
1419
- ).count,
1420
- "count.count"
1421
- ) : (await this.resolveAll()).length;
1422
- if (this.query.index === void 0) return total;
1344
+ const total = this.query.kind === "selector" && !this.query.filters?.length ? expectNumber(expectRecord(await this.window.call("count", { selector: this.query.selector, ...this.frameParams() }), "count result").count, "count.count") : (await this.resolveAll()).length;
1345
+ if (this.query.index === undefined)
1346
+ return total;
1423
1347
  const position = absoluteIndex(this.query.index, total);
1424
1348
  return position >= 0 && position < total ? 1 : 0;
1425
1349
  }
1426
1350
  async waitFor(options) {
1427
- if (this.query.kind === "selector" && this.query.index === void 0 && !this.query.filters?.length) {
1351
+ if (this.query.kind === "selector" && this.query.index === undefined && !this.query.filters?.length) {
1428
1352
  await this.window.call("wait", {
1429
1353
  selector: this.query.selector,
1430
1354
  gone: options.state === "detached",
@@ -1436,32 +1360,22 @@ var HasgardLocator = class _HasgardLocator {
1436
1360
  const deadline = Date.now() + options.timeoutMs;
1437
1361
  while (Date.now() <= deadline) {
1438
1362
  const found = await this.count() > 0;
1439
- if (found === (options.state === "attached")) return;
1363
+ if (found === (options.state === "attached"))
1364
+ return;
1440
1365
  await new Promise((resolve) => setTimeout(resolve, 50));
1441
1366
  }
1442
1367
  throw new Error(`Locator did not become ${options.state} within ${options.timeoutMs}ms`);
1443
1368
  }
1444
- /**
1445
- * Narrow this locator to a single match by position. A negative index counts
1446
- * back from the end, so `last()` stays one round trip.
1447
- */
1448
1369
  nth(index) {
1449
- if (!Number.isInteger(index)) throw new Error("nth requires an integer index");
1450
- return new _HasgardLocator(this.window, { ...this.query, index });
1451
- }
1452
- /**
1453
- * Narrow this locator to the elements whose text matches.
1454
- *
1455
- * Filters accumulate, so `filter({hasText}).filter({hasNotText})` applies
1456
- * both. Ordering matters against `nth`: filtering happens first, so
1457
- * `filter({hasText: "x"}).first()` is the first *matching* element, which is
1458
- * the reading that makes `first()` mean what it says.
1459
- */
1370
+ if (!Number.isInteger(index))
1371
+ throw new Error("nth requires an integer index");
1372
+ return new HasgardLocator(this.window, { ...this.query, index });
1373
+ }
1460
1374
  filter(options) {
1461
- if (options.hasText === void 0 && options.hasNotText === void 0) {
1375
+ if (options.hasText === undefined && options.hasNotText === undefined) {
1462
1376
  throw new Error("filter requires hasText or hasNotText");
1463
1377
  }
1464
- return new _HasgardLocator(this.window, {
1378
+ return new HasgardLocator(this.window, {
1465
1379
  ...this.query,
1466
1380
  filters: [...this.query.filters ?? [], options]
1467
1381
  });
@@ -1474,30 +1388,25 @@ var HasgardLocator = class _HasgardLocator {
1474
1388
  }
1475
1389
  async resolveUnique(allowMissing = false) {
1476
1390
  if (this.query.kind === "selector" && !this.query.filters?.length) {
1477
- return this.query.index === void 0 ? bySelector(this.query.selector) : { selector: this.query.selector, index: this.query.index };
1391
+ return this.query.index === undefined ? bySelector(this.query.selector) : { selector: this.query.selector, index: this.query.index };
1478
1392
  }
1479
1393
  const elements = await this.resolveAll();
1480
- if (this.query.index !== void 0) {
1394
+ if (this.query.index !== undefined) {
1481
1395
  const element = elements[absoluteIndex(this.query.index, elements.length)];
1482
1396
  if (!element) {
1483
- if (allowMissing) return null;
1397
+ if (allowMissing)
1398
+ return null;
1484
1399
  throw new Error(`Locator has no element at index ${this.query.index} (${elements.length} matched)`);
1485
1400
  }
1486
1401
  return byRef(element.ref);
1487
1402
  }
1488
- if (elements.length === 0 && allowMissing) return null;
1403
+ if (elements.length === 0 && allowMissing)
1404
+ return null;
1489
1405
  if (elements.length !== 1) {
1490
1406
  throw new Error(`Locator resolved to ${elements.length} elements; expected exactly one`);
1491
1407
  }
1492
1408
  return byRef(elements[0].ref);
1493
1409
  }
1494
- /**
1495
- * Serialize a target together with the frame chain this locator lives in.
1496
- *
1497
- * A ref carries its own document, so the bridge ignores `frame` for one; the
1498
- * chain still travels so that a selector or coordinate target -- which the
1499
- * bridge must resolve itself -- lands in the right document.
1500
- */
1501
1410
  scoped(target) {
1502
1411
  return { ...targetParams(target), ...this.frameParams() };
1503
1412
  }
@@ -1516,36 +1425,38 @@ var HasgardLocator = class _HasgardLocator {
1516
1425
  }
1517
1426
  if (this.query.kind === "query") {
1518
1427
  const params = { by: this.query.by, value: this.query.value };
1519
- if (this.query.query.exact !== void 0) params.exact = this.query.query.exact;
1428
+ if (this.query.query.exact !== undefined)
1429
+ params.exact = this.query.query.exact;
1520
1430
  return this.runQuery(params);
1521
1431
  }
1522
1432
  const query = this.query;
1523
1433
  const snapshot = await this.window.snapshot(query.frame ? { frame: [...query.frame] } : {});
1524
1434
  return snapshot.elements.filter((element) => {
1525
- if (element.role !== query.role) return false;
1435
+ if (element.role !== query.role)
1436
+ return false;
1526
1437
  const expected = query.query.name;
1527
- if (expected === void 0) return true;
1528
- if (element.name === void 0) return false;
1529
- if (expected instanceof RegExp) return expected.test(element.name);
1438
+ if (expected === undefined)
1439
+ return true;
1440
+ if (element.name === undefined)
1441
+ return false;
1442
+ if (expected instanceof RegExp)
1443
+ return expected.test(element.name);
1530
1444
  return query.query.exact ? element.name === expected : element.name.includes(expected);
1531
1445
  });
1532
1446
  }
1533
- /**
1534
- * Apply each accumulated filter, one round trip per filter.
1535
- *
1536
- * Filters run server-side rather than against a snapshot because the text a
1537
- * filter matches on is not carried in snapshot entries — deliberately, so
1538
- * that every unrelated `snapshot` call stays small.
1539
- */
1540
1447
  async applyFilters(elements) {
1541
1448
  const filters = this.query.filters ?? [];
1542
1449
  let current = elements;
1543
1450
  for (const filter of filters) {
1544
- if (current.length === 0) return current;
1451
+ if (current.length === 0)
1452
+ return current;
1545
1453
  const params = { refs: current.map((element) => element.ref) };
1546
- if (filter.hasText !== void 0) params.hasText = filter.hasText;
1547
- if (filter.hasNotText !== void 0) params.hasNotText = filter.hasNotText;
1548
- if (filter.exact !== void 0) params.exact = filter.exact;
1454
+ if (filter.hasText !== undefined)
1455
+ params.hasText = filter.hasText;
1456
+ if (filter.hasNotText !== undefined)
1457
+ params.hasNotText = filter.hasNotText;
1458
+ if (filter.exact !== undefined)
1459
+ params.exact = filter.exact;
1549
1460
  current = this.parseElements(await this.window.call("filter", params), "filter");
1550
1461
  }
1551
1462
  return current;
@@ -1555,10 +1466,11 @@ var HasgardLocator = class _HasgardLocator {
1555
1466
  }
1556
1467
  parseElements(raw, source) {
1557
1468
  const value = expectRecord(raw, `${source} result`);
1558
- if (!Array.isArray(value.elements)) throw new Error(`${source}.elements must be an array`);
1469
+ if (!Array.isArray(value.elements))
1470
+ throw new Error(`${source}.elements must be an array`);
1559
1471
  return value.elements.map((entry, index) => parseSnapshotElement(entry, index));
1560
1472
  }
1561
- };
1473
+ }
1562
1474
  function parseDialogPolicy(value) {
1563
1475
  const record = expectRecord(value, "dialog policy");
1564
1476
  const action = expectString(record.action, "dialog policy.action");
@@ -1583,10 +1495,11 @@ function parseDialogRecord(value, index) {
1583
1495
  message: expectString(entry.message, `dialogs[${index}].message`),
1584
1496
  accepted: expectBoolean(entry.accepted, `dialogs[${index}].accepted`)
1585
1497
  };
1586
- if (entry.defaultValue !== void 0) {
1498
+ if (entry.defaultValue !== undefined) {
1587
1499
  record.defaultValue = expectString(entry.defaultValue, `dialogs[${index}].defaultValue`);
1588
1500
  }
1589
- if (entry.returned !== void 0) record.returned = expectString(entry.returned, `dialogs[${index}].returned`);
1501
+ if (entry.returned !== undefined)
1502
+ record.returned = expectString(entry.returned, `dialogs[${index}].returned`);
1590
1503
  return record;
1591
1504
  }
1592
1505
  function parseSnapshotElement(value, index) {
@@ -1596,10 +1509,13 @@ function parseSnapshotElement(value, index) {
1596
1509
  role: expectString(entry.role, `snapshot.elements[${index}].role`),
1597
1510
  depth: expectNumber(entry.depth, `snapshot.elements[${index}].depth`)
1598
1511
  };
1599
- if (entry.name !== void 0) element.name = expectString(entry.name, `snapshot.elements[${index}].name`);
1600
- if (entry.value !== void 0) element.value = expectString(entry.value, `snapshot.elements[${index}].value`);
1601
- if (entry.checked !== void 0) element.checked = expectBoolean(entry.checked, `snapshot.elements[${index}].checked`);
1602
- if (entry.disabled !== void 0)
1512
+ if (entry.name !== undefined)
1513
+ element.name = expectString(entry.name, `snapshot.elements[${index}].name`);
1514
+ if (entry.value !== undefined)
1515
+ element.value = expectString(entry.value, `snapshot.elements[${index}].value`);
1516
+ if (entry.checked !== undefined)
1517
+ element.checked = expectBoolean(entry.checked, `snapshot.elements[${index}].checked`);
1518
+ if (entry.disabled !== undefined)
1603
1519
  element.disabled = expectBoolean(entry.disabled, `snapshot.elements[${index}].disabled`);
1604
1520
  return element;
1605
1521
  }
@@ -1608,23 +1524,26 @@ function parseSnapshotElement(value, index) {
1608
1524
  function createHasgardTest(config) {
1609
1525
  const base = config.test;
1610
1526
  const expect = createHasgardExpect(config.expect);
1611
- const readinessTimeoutMs = config.launch ? config.launch.timeoutMs : 3e4;
1612
- const fixtureTimeoutMs = config.launch ? config.launch.timeoutMs + 3e4 : 3e4;
1613
- const processes = /* @__PURE__ */ new WeakMap();
1527
+ const readinessTimeoutMs = config.launch ? config.launch.timeoutMs : 30000;
1528
+ const fixtureTimeoutMs = config.launch ? config.launch.timeoutMs + 30000 : 30000;
1529
+ const processes = new WeakMap;
1614
1530
  const test = base.extend({
1615
1531
  hasgard: [
1616
1532
  async ({}, use, workerInfo) => {
1617
1533
  const socketPath = typeof config.socketPath === "function" ? config.socketPath(workerInfo.workerIndex) : config.socketPath;
1618
- const process2 = new HasgardProcess();
1534
+ const process2 = new HasgardProcess;
1619
1535
  const rpc = new HasgardRpcClient(socketPath);
1620
1536
  try {
1621
- if (config.launch) await process2.start(config.launch, socketPath);
1622
- else await process2.waitForSocket(socketPath, workerInfo.project.timeout);
1537
+ if (config.launch)
1538
+ await process2.start(config.launch, socketPath);
1539
+ else
1540
+ await process2.waitForSocket(socketPath, workerInfo.project.timeout);
1623
1541
  await rpc.connect(workerInfo.project.timeout);
1624
1542
  const hasgard = new HasgardApplication(rpc);
1625
1543
  processes.set(hasgard, process2);
1626
1544
  const ping = await hasgard.ping();
1627
- if (ping.status !== "ok") throw new Error(`Unexpected Hasgard ping status: ${ping.status}`);
1545
+ if (ping.status !== "ok")
1546
+ throw new Error(`Unexpected Hasgard ping status: ${ping.status}`);
1628
1547
  await hasgard.waitForWindowReady(config.windowLabel, config.readySelector, readinessTimeoutMs);
1629
1548
  await use(hasgard);
1630
1549
  } catch (error) {
@@ -1653,7 +1572,8 @@ function createHasgardTest(config) {
1653
1572
  body: JSON.stringify(diagnostics, null, 2),
1654
1573
  contentType: "application/json"
1655
1574
  });
1656
- if (diagnostics.exit) console.error(`Hasgard application exited: ${JSON.stringify(diagnostics)}`);
1575
+ if (diagnostics.exit)
1576
+ console.error(`Hasgard application exited: ${JSON.stringify(diagnostics)}`);
1657
1577
  }
1658
1578
  try {
1659
1579
  const screenshot = await hasgard.window(config.windowLabel).screenshot();