prowl-tools 0.1.8 → 0.1.10

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/lib.d.cts CHANGED
@@ -115,6 +115,12 @@ type HistoryEntry = {
115
115
  durationMs: number;
116
116
  startedAt: string;
117
117
  runDir?: string;
118
+ /**
119
+ * Number of retry attempts beyond the first for this run (PROWL-033). Absent or
120
+ * `0` when the run did not retry — kept optional so older `history.json` files and
121
+ * runs of hunts without a `retry` block keep parsing unchanged.
122
+ */
123
+ retries?: number;
118
124
  };
119
125
  type HistoryFile = {
120
126
  entries: HistoryEntry[];
@@ -177,6 +183,13 @@ type WaitForNetworkIdleStep = {
177
183
  timeout?: number;
178
184
  };
179
185
  };
186
+ type WaitForResponseStep = {
187
+ waitForResponse: {
188
+ url: string;
189
+ status?: number;
190
+ timeout?: number;
191
+ };
192
+ };
180
193
  type SelectOptionStep = {
181
194
  selectOption: {
182
195
  selector: string;
@@ -299,7 +312,7 @@ type AssertScreenshotStep = {
299
312
  type AssertWithAiStep = {
300
313
  assertWithAI: string;
301
314
  };
302
- type Step = NavigateStep | ClickStep | FillStep | TypeStep | PressStep | WaitStep | SelectOptionStep | SelectStep | OnDialogStep | SetInputFilesStep | InlineAssertStep | RunHuntStep | WaitForSelectorStep | WaitForUrlStep | WaitForNetworkIdleStep | HoverStep | ScrollStep | ScrollToStep | ScreenshotStep | IfStep | RepeatStep | MockRouteStep | UnmockRouteStep | EvalScriptStep | RunScriptStep | AssertScreenshotStep | AssertWithAiStep | CopyTextStep | WaitForDownloadStep;
315
+ type Step = NavigateStep | ClickStep | FillStep | TypeStep | PressStep | WaitStep | SelectOptionStep | SelectStep | OnDialogStep | SetInputFilesStep | InlineAssertStep | RunHuntStep | WaitForSelectorStep | WaitForUrlStep | WaitForNetworkIdleStep | WaitForResponseStep | HoverStep | ScrollStep | ScrollToStep | ScreenshotStep | IfStep | RepeatStep | MockRouteStep | UnmockRouteStep | EvalScriptStep | RunScriptStep | AssertScreenshotStep | AssertWithAiStep | CopyTextStep | WaitForDownloadStep;
303
316
  type Assertion = {
304
317
  selectorExists: string;
305
318
  } | {
@@ -344,6 +357,25 @@ type TraceCorrelation = {
344
357
  traceId: string;
345
358
  header: string;
346
359
  };
360
+ /**
361
+ * One attempt of a retried hunt (PROWL-033). Recorded per attempt so a passed-on-retry
362
+ * run keeps the reason each earlier attempt failed, helping tell a flaky test apart from
363
+ * a slow environment or a real regression.
364
+ */
365
+ type RetryAttempt = {
366
+ /** 1-based attempt number (attempt 1 is the initial run). */
367
+ attempt: number;
368
+ status: "pass" | "fail";
369
+ durationMs: number;
370
+ /** The first failing step on this attempt; absent when the attempt passed. */
371
+ failedStep?: {
372
+ /** 0-based index into the hunt's steps. */
373
+ index: number;
374
+ type: string;
375
+ };
376
+ /** Failure reason for this attempt; absent when it passed. */
377
+ error?: string;
378
+ };
347
379
  type RunResult = {
348
380
  status: "pass" | "fail";
349
381
  exitCode: 0 | 1;
@@ -355,6 +387,17 @@ type RunResult = {
355
387
  assertions: AssertionResult[];
356
388
  artifacts: RunArtifacts;
357
389
  traceCorrelations?: TraceCorrelation[];
390
+ /**
391
+ * Per-attempt diagnostics (PROWL-033), present only when the hunt used `retry` and
392
+ * more than one attempt ran. Absent when no retry occurred so existing consumers and
393
+ * older run artifacts keep parsing. The last entry always matches the final result.
394
+ */
395
+ retryHistory?: RetryAttempt[];
396
+ /**
397
+ * Human-readable one-line retry outcome (PROWL-033), e.g. "Passed on attempt 2 of 3
398
+ * — first failure: navigate (timeout)". Present only alongside `retryHistory`.
399
+ */
400
+ retrySummary?: string;
358
401
  };
359
402
  type CiHuntResult = {
360
403
  hunt: string;
@@ -477,6 +520,10 @@ interface SessionDriver {
477
520
  waitForNetworkIdle(options?: {
478
521
  timeout?: number;
479
522
  }): Promise<void>;
523
+ /** Resolve once a network response satisfies `predicate`, or reject on timeout. */
524
+ waitForResponse(predicate: (response: DriverResponse) => boolean, options?: {
525
+ timeout?: number;
526
+ }): Promise<void>;
480
527
  evaluate<R = unknown, A = unknown>(pageFunction: string | ((arg: A) => R | Promise<R>), arg?: A): Promise<R>;
481
528
  screenshot(options: {
482
529
  path: string;
@@ -605,15 +652,38 @@ type ResolveHelperOptions = {
605
652
  declare function resolveHelperBinary(env?: NodeJS.ProcessEnv, options?: ResolveHelperOptions): string;
606
653
  /** Default per-request deadline for the helper transport. */
607
654
  declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
655
+ /**
656
+ * A server-initiated event line from the helper (ARCH-008): a JSON object with
657
+ * an `event` discriminant and **no** `id`, so it is unambiguously distinct from
658
+ * an id-matched command response. Emitted, for example, when an AXObserver
659
+ * notification fires during a `waitFor`/`openMenu` wait.
660
+ */
661
+ type MacHelperEvent = Record<string, unknown> & {
662
+ event: string;
663
+ };
608
664
  type SpawnMacHelperOptions = {
609
665
  /** Per-request deadline; a request that gets no response by then rejects. */
610
666
  requestTimeoutMs?: number;
667
+ /**
668
+ * Optional sink for server-initiated event lines. Events are informational —
669
+ * waits are resolved helper-side by the id-matched response — so they are
670
+ * forwarded here (if provided) and otherwise dropped, never touching the
671
+ * pending-request map.
672
+ */
673
+ onEvent?: (event: MacHelperEvent) => void;
674
+ /**
675
+ * Optional diagnostic sink for event handler failures. Defaults to stderr so a
676
+ * bad sink is visible without allowing it to break helper transport.
677
+ */
678
+ onEventError?: (message: string) => void;
611
679
  };
612
680
  /** A {@link MacHelperClient} backed by a spawned `prowl-macdriver serve` process. */
613
681
  declare class SpawnMacHelperClient implements MacHelperClient {
614
682
  private readonly child;
615
683
  private readonly pending;
616
684
  private readonly requestTimeoutMs;
685
+ private readonly onEvent?;
686
+ private readonly onEventError;
617
687
  private stdoutBuffer;
618
688
  private stderrBuffer;
619
689
  private nextId;
@@ -622,6 +692,7 @@ declare class SpawnMacHelperClient implements MacHelperClient {
622
692
  constructor(binaryPath: string, options?: SpawnMacHelperOptions);
623
693
  private onStdout;
624
694
  private dispatch;
695
+ private handleEvent;
625
696
  private failAll;
626
697
  private recordTerminalFailure;
627
698
  /** Number of in-flight requests awaiting a response (for teardown/tests). */
package/dist/lib.d.ts CHANGED
@@ -115,6 +115,12 @@ type HistoryEntry = {
115
115
  durationMs: number;
116
116
  startedAt: string;
117
117
  runDir?: string;
118
+ /**
119
+ * Number of retry attempts beyond the first for this run (PROWL-033). Absent or
120
+ * `0` when the run did not retry — kept optional so older `history.json` files and
121
+ * runs of hunts without a `retry` block keep parsing unchanged.
122
+ */
123
+ retries?: number;
118
124
  };
119
125
  type HistoryFile = {
120
126
  entries: HistoryEntry[];
@@ -177,6 +183,13 @@ type WaitForNetworkIdleStep = {
177
183
  timeout?: number;
178
184
  };
179
185
  };
186
+ type WaitForResponseStep = {
187
+ waitForResponse: {
188
+ url: string;
189
+ status?: number;
190
+ timeout?: number;
191
+ };
192
+ };
180
193
  type SelectOptionStep = {
181
194
  selectOption: {
182
195
  selector: string;
@@ -299,7 +312,7 @@ type AssertScreenshotStep = {
299
312
  type AssertWithAiStep = {
300
313
  assertWithAI: string;
301
314
  };
302
- type Step = NavigateStep | ClickStep | FillStep | TypeStep | PressStep | WaitStep | SelectOptionStep | SelectStep | OnDialogStep | SetInputFilesStep | InlineAssertStep | RunHuntStep | WaitForSelectorStep | WaitForUrlStep | WaitForNetworkIdleStep | HoverStep | ScrollStep | ScrollToStep | ScreenshotStep | IfStep | RepeatStep | MockRouteStep | UnmockRouteStep | EvalScriptStep | RunScriptStep | AssertScreenshotStep | AssertWithAiStep | CopyTextStep | WaitForDownloadStep;
315
+ type Step = NavigateStep | ClickStep | FillStep | TypeStep | PressStep | WaitStep | SelectOptionStep | SelectStep | OnDialogStep | SetInputFilesStep | InlineAssertStep | RunHuntStep | WaitForSelectorStep | WaitForUrlStep | WaitForNetworkIdleStep | WaitForResponseStep | HoverStep | ScrollStep | ScrollToStep | ScreenshotStep | IfStep | RepeatStep | MockRouteStep | UnmockRouteStep | EvalScriptStep | RunScriptStep | AssertScreenshotStep | AssertWithAiStep | CopyTextStep | WaitForDownloadStep;
303
316
  type Assertion = {
304
317
  selectorExists: string;
305
318
  } | {
@@ -344,6 +357,25 @@ type TraceCorrelation = {
344
357
  traceId: string;
345
358
  header: string;
346
359
  };
360
+ /**
361
+ * One attempt of a retried hunt (PROWL-033). Recorded per attempt so a passed-on-retry
362
+ * run keeps the reason each earlier attempt failed, helping tell a flaky test apart from
363
+ * a slow environment or a real regression.
364
+ */
365
+ type RetryAttempt = {
366
+ /** 1-based attempt number (attempt 1 is the initial run). */
367
+ attempt: number;
368
+ status: "pass" | "fail";
369
+ durationMs: number;
370
+ /** The first failing step on this attempt; absent when the attempt passed. */
371
+ failedStep?: {
372
+ /** 0-based index into the hunt's steps. */
373
+ index: number;
374
+ type: string;
375
+ };
376
+ /** Failure reason for this attempt; absent when it passed. */
377
+ error?: string;
378
+ };
347
379
  type RunResult = {
348
380
  status: "pass" | "fail";
349
381
  exitCode: 0 | 1;
@@ -355,6 +387,17 @@ type RunResult = {
355
387
  assertions: AssertionResult[];
356
388
  artifacts: RunArtifacts;
357
389
  traceCorrelations?: TraceCorrelation[];
390
+ /**
391
+ * Per-attempt diagnostics (PROWL-033), present only when the hunt used `retry` and
392
+ * more than one attempt ran. Absent when no retry occurred so existing consumers and
393
+ * older run artifacts keep parsing. The last entry always matches the final result.
394
+ */
395
+ retryHistory?: RetryAttempt[];
396
+ /**
397
+ * Human-readable one-line retry outcome (PROWL-033), e.g. "Passed on attempt 2 of 3
398
+ * — first failure: navigate (timeout)". Present only alongside `retryHistory`.
399
+ */
400
+ retrySummary?: string;
358
401
  };
359
402
  type CiHuntResult = {
360
403
  hunt: string;
@@ -477,6 +520,10 @@ interface SessionDriver {
477
520
  waitForNetworkIdle(options?: {
478
521
  timeout?: number;
479
522
  }): Promise<void>;
523
+ /** Resolve once a network response satisfies `predicate`, or reject on timeout. */
524
+ waitForResponse(predicate: (response: DriverResponse) => boolean, options?: {
525
+ timeout?: number;
526
+ }): Promise<void>;
480
527
  evaluate<R = unknown, A = unknown>(pageFunction: string | ((arg: A) => R | Promise<R>), arg?: A): Promise<R>;
481
528
  screenshot(options: {
482
529
  path: string;
@@ -605,15 +652,38 @@ type ResolveHelperOptions = {
605
652
  declare function resolveHelperBinary(env?: NodeJS.ProcessEnv, options?: ResolveHelperOptions): string;
606
653
  /** Default per-request deadline for the helper transport. */
607
654
  declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
655
+ /**
656
+ * A server-initiated event line from the helper (ARCH-008): a JSON object with
657
+ * an `event` discriminant and **no** `id`, so it is unambiguously distinct from
658
+ * an id-matched command response. Emitted, for example, when an AXObserver
659
+ * notification fires during a `waitFor`/`openMenu` wait.
660
+ */
661
+ type MacHelperEvent = Record<string, unknown> & {
662
+ event: string;
663
+ };
608
664
  type SpawnMacHelperOptions = {
609
665
  /** Per-request deadline; a request that gets no response by then rejects. */
610
666
  requestTimeoutMs?: number;
667
+ /**
668
+ * Optional sink for server-initiated event lines. Events are informational —
669
+ * waits are resolved helper-side by the id-matched response — so they are
670
+ * forwarded here (if provided) and otherwise dropped, never touching the
671
+ * pending-request map.
672
+ */
673
+ onEvent?: (event: MacHelperEvent) => void;
674
+ /**
675
+ * Optional diagnostic sink for event handler failures. Defaults to stderr so a
676
+ * bad sink is visible without allowing it to break helper transport.
677
+ */
678
+ onEventError?: (message: string) => void;
611
679
  };
612
680
  /** A {@link MacHelperClient} backed by a spawned `prowl-macdriver serve` process. */
613
681
  declare class SpawnMacHelperClient implements MacHelperClient {
614
682
  private readonly child;
615
683
  private readonly pending;
616
684
  private readonly requestTimeoutMs;
685
+ private readonly onEvent?;
686
+ private readonly onEventError;
617
687
  private stdoutBuffer;
618
688
  private stderrBuffer;
619
689
  private nextId;
@@ -622,6 +692,7 @@ declare class SpawnMacHelperClient implements MacHelperClient {
622
692
  constructor(binaryPath: string, options?: SpawnMacHelperOptions);
623
693
  private onStdout;
624
694
  private dispatch;
695
+ private handleEvent;
625
696
  private failAll;
626
697
  private recordTerminalFailure;
627
698
  /** Number of in-flight requests awaiting a response (for teardown/tests). */
package/dist/lib.js CHANGED
@@ -149,7 +149,7 @@ import {
149
149
  wdaTestRunArgs,
150
150
  webOnlyReason,
151
151
  zipinfoArchiveLister
152
- } from "./chunk-CWLRDV5P.js";
152
+ } from "./chunk-SRWAF2RI.js";
153
153
  import {
154
154
  configSchema,
155
155
  huntSchema,
@@ -160,7 +160,7 @@ import {
160
160
  loadHuntMeta,
161
161
  loadHuntTags,
162
162
  stepSchema
163
- } from "./chunk-JFJQNJSJ.js";
163
+ } from "./chunk-TTZ2IL3P.js";
164
164
  export {
165
165
  ANDROID_INTERACTIVE_CLASSES,
166
166
  ANDROID_KEYCODES,
@@ -10,7 +10,7 @@ import {
10
10
  loadHuntTags,
11
11
  resolveViewport,
12
12
  warnLegacyConfigDir
13
- } from "./chunk-JFJQNJSJ.js";
13
+ } from "./chunk-TTZ2IL3P.js";
14
14
  export {
15
15
  CONFIG_DIR,
16
16
  LEGACY_CONFIG_DIR,
@@ -24,4 +24,4 @@ export {
24
24
  resolveViewport,
25
25
  warnLegacyConfigDir
26
26
  };
27
- //# sourceMappingURL=loader-JTHA4BYG.js.map
27
+ //# sourceMappingURL=loader-CAE4FPBK.js.map
@@ -0,0 +1,53 @@
1
+ # Form Submission
2
+ # ---
3
+ # Pattern: Forms — fill text fields, pick a dropdown option, submit, verify result.
4
+ # What it tests: Complete a form and confirm the success state renders.
5
+ # Customize:
6
+ # - Point `navigate` at your form's path
7
+ # - Update the field labels, the dropdown label/option, and the button text
8
+ # to match your form
9
+ #
10
+ # Tip: Prefer stable selectors — an accessible label or a data-testid — over
11
+ # brittle CSS. Run `prowl analyze` to dump ranked selector candidates for a page.
12
+
13
+ name: form
14
+ description: Fill and submit a form, verify the success message
15
+
16
+ tags:
17
+ - forms
18
+ - input
19
+
20
+ steps:
21
+ - navigate: "/signup"
22
+
23
+ # Shorthand fill — Prowl finds the input by its label or placeholder text.
24
+ # Equivalent explicit form:
25
+ # fill:
26
+ # selector: "input[name='name']"
27
+ # value: "Ada Lovelace"
28
+ - fill:
29
+ "Full name": "Ada Lovelace"
30
+
31
+ - fill:
32
+ "Email": "ada@example.com"
33
+
34
+ # Shorthand select — finds the <select> by its label and picks the option by
35
+ # its visible text. Equivalent explicit form:
36
+ # selectOption:
37
+ # selector: "select[name='plan']"
38
+ # value: "Pro"
39
+ - select:
40
+ "Plan": "Pro"
41
+
42
+ # Shorthand click — Prowl finds a checkbox or button by its text content.
43
+ - click: "I agree to the terms"
44
+
45
+ - click: "Create account"
46
+
47
+ # Mid-flow assertion — verify the success state rendered.
48
+ - assert:
49
+ visible: "Welcome, Ada"
50
+
51
+ assertions:
52
+ - urlIncludes: "/welcome"
53
+ - noConsoleErrors: true
@@ -0,0 +1,49 @@
1
+ # macOS Hello — Your First Desktop Hunt (Experimental)
2
+ # ---
3
+ # Prowl drives native macOS apps through Apple's Accessibility API, from the same
4
+ # YAML as web hunts. This starter targets TextEdit — present on every Mac — so you
5
+ # can try the desktop target without wiring up your own app first.
6
+ #
7
+ # ── Before this hunt will run ───────────────────────────────────────────────
8
+ # 1. Install the helper: prowl macdriver install
9
+ # Until the first signed release ships, `install` returns a 404 — build from
10
+ # source instead (needs the Swift toolchain / Xcode CLT):
11
+ # cd macdriver && swift build -c release
12
+ # 2. Grant Accessibility permission to the app hosting your terminal (Terminal,
13
+ # iTerm, VS Code, …): System Settings → Privacy & Security → Accessibility.
14
+ # `prowl macdriver status` prints the resolved binary and this guidance.
15
+ # 3. Point .prowl/config.yml at a macOS target — init's default config targets
16
+ # the web. Replace its `target:` block, and scope the app under guardrails:
17
+ #
18
+ # target:
19
+ # type: macos
20
+ # app: "com.apple.TextEdit" # bundle id, or an absolute /path/to/App.app
21
+ # guardrails:
22
+ # allowedApps:
23
+ # - "com.apple.TextEdit"
24
+ #
25
+ # The macOS target is EXPERIMENTAL — the selector dialect and step coverage may
26
+ # still change. `navigate`, `waitForUrl`, and other web-only steps are rejected
27
+ # on it; the portable steps below (`type`, `assert: visible`) run on both targets.
28
+ #
29
+ # ── Finding selectors ───────────────────────────────────────────────────────
30
+ # Don't guess selectors — dump them: prowl analyze --app com.apple.TextEdit
31
+ # It walks the Accessibility tree and prints every element with ranked selector
32
+ # candidates (prefer `id=` — the native analog of data-testid). TextEdit's
33
+ # controls aren't ours, so treat the steps below as a starting point and adjust
34
+ # to what `analyze` reports on your macOS version.
35
+
36
+ name: macos-hello
37
+ description: Type into TextEdit and verify the text appears (experimental macOS target)
38
+
39
+ tags:
40
+ - macos
41
+ - smoke
42
+
43
+ steps:
44
+ # `type` sends keystrokes to the focused element — a fresh TextEdit document.
45
+ - type: "Hello from Prowl!"
46
+
47
+ # Portable assertion — the document's text should now contain what we typed.
48
+ - assert:
49
+ visible: "Hello from Prowl!"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prowl-tools",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "E2E testing for native macOS apps and web apps from declarative YAML hunts.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -59,7 +59,8 @@
59
59
  "build": "tsup",
60
60
  "lint": "eslint .",
61
61
  "test": "vitest run",
62
- "test:watch": "vitest"
62
+ "test:watch": "vitest",
63
+ "audit:licenses": "node scripts/audit-licenses.mjs"
63
64
  },
64
65
  "dependencies": {
65
66
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -83,6 +84,7 @@
83
84
  "@typescript-eslint/eslint-plugin": "^7.18.0",
84
85
  "@typescript-eslint/parser": "^7.18.0",
85
86
  "eslint": "^8.57.1",
87
+ "license-checker-rseidelsohn": "^4.4.2",
86
88
  "tsup": "^8.3.5",
87
89
  "typescript": "^5.7.3",
88
90
  "vitest": "^2.1.8"