prowl-tools 0.1.4 → 0.1.6
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/NOTICE +10 -0
- package/README.md +519 -2
- package/dist/{chunk-ZEFVTKQT.js → chunk-5KQR3IR3.js} +3321 -346
- package/dist/chunk-5KQR3IR3.js.map +1 -0
- package/dist/{chunk-MBAIGNVO.js → chunk-WHAMB4TY.js} +43 -3
- package/dist/chunk-WHAMB4TY.js.map +1 -0
- package/dist/index.cjs +3527 -481
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +325 -67
- package/dist/index.js.map +1 -1
- package/dist/lib.cjs +3444 -325
- package/dist/lib.cjs.map +1 -1
- package/dist/lib.d.cts +1236 -11
- package/dist/lib.d.ts +1236 -11
- package/dist/lib.js +194 -2
- package/dist/{loader-XMTDB6OL.js → loader-PBBYV3U7.js} +2 -2
- package/package.json +5 -1
- package/dist/chunk-MBAIGNVO.js.map +0 -1
- package/dist/chunk-ZEFVTKQT.js.map +0 -1
- /package/dist/{loader-XMTDB6OL.js.map → loader-PBBYV3U7.js.map} +0 -0
package/dist/lib.d.ts
CHANGED
|
@@ -22,7 +22,40 @@ type MacosTarget = {
|
|
|
22
22
|
type: "macos";
|
|
23
23
|
app: string;
|
|
24
24
|
};
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Android native execution target (experimental, PROWL-058). `app` is an Android
|
|
27
|
+
* package name (e.g. `com.example.app`) or an absolute/relative path to an `.apk`
|
|
28
|
+
* file, which Prowl installs before launch. Optional `deviceSerial` selects one of
|
|
29
|
+
* several attached devices/emulators; `coldStart` opts into a deterministic
|
|
30
|
+
* `pm clear` before launch (default off). Android guardrails match package IDs
|
|
31
|
+
* or canonical full APK paths; APK package IDs are resolved before install.
|
|
32
|
+
*/
|
|
33
|
+
type AndroidTarget = {
|
|
34
|
+
type: "android";
|
|
35
|
+
app: string;
|
|
36
|
+
/** adb device serial to target when more than one device is attached. */
|
|
37
|
+
deviceSerial?: string;
|
|
38
|
+
/** Run `pm clear <package>` before launch for a deterministic cold start (default off). */
|
|
39
|
+
coldStart?: boolean;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* iOS simulator native execution target (experimental, PROWL-059). `app` is an
|
|
43
|
+
* iOS bundle identifier (e.g. `com.example.App`) or an absolute/relative path to
|
|
44
|
+
* a built `.app` bundle, which Prowl installs onto the simulator before launch.
|
|
45
|
+
* Optional `udid` selects a specific booted simulator; `coldStart` opts into an
|
|
46
|
+
* uninstall+reinstall before launch (default off, and only possible with a `.app`
|
|
47
|
+
* path). Real devices are out of scope (PROWL-062) — simulators only. iOS
|
|
48
|
+
* guardrails match bundle ids, the bundle name, or the canonical `.app` path.
|
|
49
|
+
*/
|
|
50
|
+
type IosTarget = {
|
|
51
|
+
type: "ios";
|
|
52
|
+
app: string;
|
|
53
|
+
/** UDID of the booted simulator to target when more than one is booted. */
|
|
54
|
+
udid?: string;
|
|
55
|
+
/** Uninstall+reinstall the app before launch for a deterministic cold start (default off). */
|
|
56
|
+
coldStart?: boolean;
|
|
57
|
+
};
|
|
58
|
+
type Target = WebTarget | MacosTarget | AndroidTarget | IosTarget;
|
|
26
59
|
type Config = {
|
|
27
60
|
target: Target;
|
|
28
61
|
browser: {
|
|
@@ -48,7 +81,7 @@ type Config = {
|
|
|
48
81
|
guardrails: {
|
|
49
82
|
maxSteps: number;
|
|
50
83
|
allowedDomains: string[];
|
|
51
|
-
/** Native scope analog of `allowedDomains`: allowed bundle IDs
|
|
84
|
+
/** Native scope analog of `allowedDomains`: allowed bundle/package IDs or canonical app artifact paths. */
|
|
52
85
|
allowedApps: string[];
|
|
53
86
|
forbiddenSelectors: string[];
|
|
54
87
|
selfHealing: boolean;
|
|
@@ -263,7 +296,10 @@ type AssertScreenshotStep = {
|
|
|
263
296
|
threshold?: number;
|
|
264
297
|
};
|
|
265
298
|
};
|
|
266
|
-
type
|
|
299
|
+
type AssertWithAiStep = {
|
|
300
|
+
assertWithAI: string;
|
|
301
|
+
};
|
|
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;
|
|
267
303
|
type Assertion = {
|
|
268
304
|
selectorExists: string;
|
|
269
305
|
} | {
|
|
@@ -279,7 +315,7 @@ type Assertion = {
|
|
|
279
315
|
};
|
|
280
316
|
type StepResult = {
|
|
281
317
|
type: string;
|
|
282
|
-
status: "pass" | "fail";
|
|
318
|
+
status: "pass" | "fail" | "warn";
|
|
283
319
|
durationMs: number;
|
|
284
320
|
selector?: string;
|
|
285
321
|
value?: string;
|
|
@@ -558,6 +594,758 @@ declare function launchMacSession(options: LaunchMacOptions): Promise<MacSession
|
|
|
558
594
|
/** Quit the target app (best effort) and shut the helper down. */
|
|
559
595
|
declare function closeMacSession(session: MacSession): Promise<void>;
|
|
560
596
|
|
|
597
|
+
/** The selector kinds the native dialect understands. */
|
|
598
|
+
type NativeSelectorKind = "id" | "label" | "text" | "role" | "focused";
|
|
599
|
+
/**
|
|
600
|
+
* A Prowl native selector parsed into a neutral, platform-independent shape.
|
|
601
|
+
* `role` optionally carries a `name` (the `role=Type[name="…"]` form); `name` is
|
|
602
|
+
* present only when the bracket was supplied and non-empty.
|
|
603
|
+
*/
|
|
604
|
+
type NativeSelector = {
|
|
605
|
+
kind: "id";
|
|
606
|
+
value: string;
|
|
607
|
+
} | {
|
|
608
|
+
kind: "label";
|
|
609
|
+
value: string;
|
|
610
|
+
} | {
|
|
611
|
+
kind: "text";
|
|
612
|
+
value: string;
|
|
613
|
+
} | {
|
|
614
|
+
kind: "role";
|
|
615
|
+
role: string;
|
|
616
|
+
name?: string;
|
|
617
|
+
} | {
|
|
618
|
+
kind: "focused";
|
|
619
|
+
};
|
|
620
|
+
/**
|
|
621
|
+
* Strip one layer of matching single/double quotes from a selector value. Mirrors
|
|
622
|
+
* the historical per-driver `unquote`, kept here so every native target unquotes
|
|
623
|
+
* identically.
|
|
624
|
+
*/
|
|
625
|
+
declare function unquoteSelectorValue(value: string): string;
|
|
626
|
+
/** Wrap a selector value in double quotes (the ranker's canonical emitted form). */
|
|
627
|
+
declare function quoteSelectorValue(value: string): string;
|
|
628
|
+
/**
|
|
629
|
+
* Parse a Prowl selector string into a neutral {@link NativeSelector}. This is
|
|
630
|
+
* the single grammar shared by both mobile drivers (which then map the neutral
|
|
631
|
+
* kind onto their own wire query). Precedence — `:focus`, then `id=`, `role=`,
|
|
632
|
+
* `label=`, `text=`, and finally a bare string treated as `text=` — matches the
|
|
633
|
+
* behavior the per-driver parsers had before consolidation.
|
|
634
|
+
*/
|
|
635
|
+
declare function parseNativeSelector(selector: string): NativeSelector;
|
|
636
|
+
/**
|
|
637
|
+
* The literal a `text=` selector matches, or null when `selector` is not an
|
|
638
|
+
* explicit `text=` form (a bare string is intentionally excluded — it mirrors the
|
|
639
|
+
* web driver's `parseTextSelector`, which `forbiddenSelectors` relies on). Shared
|
|
640
|
+
* so Android and iOS unwrap text selectors identically.
|
|
641
|
+
*/
|
|
642
|
+
declare function unwrapNativeTextSelector(selector: string): string | null;
|
|
643
|
+
/**
|
|
644
|
+
* Qualify a bare Android `resource-id` with the app's package. The raw
|
|
645
|
+
* uiautomator2 server matches resource-ids exactly (bare names return no
|
|
646
|
+
* elements), so `id=save` becomes `<appPackage>:id/save`. Values that already
|
|
647
|
+
* contain a `:` (e.g. `android:id/title`) — or calls without a package — pass
|
|
648
|
+
* through untouched. Both the Android driver's locator translation and the
|
|
649
|
+
* host-side matcher qualify through this one function.
|
|
650
|
+
*/
|
|
651
|
+
declare function qualifyResourceId(value: string, appPackage?: string): string;
|
|
652
|
+
/** Prefix `XCUIElementType` onto an iOS role shorthand (e.g. `Button`) when missing. */
|
|
653
|
+
declare function normalizeXcuiClassName(role: string): string;
|
|
654
|
+
/** Strip the `XCUIElementType` prefix for a friendlier `role=` shorthand. */
|
|
655
|
+
declare function shortIosType(type: string): string;
|
|
656
|
+
/**
|
|
657
|
+
* The per-node ingredients the ranker needs, already projected out of a
|
|
658
|
+
* platform's own node shape:
|
|
659
|
+
* - `id` the native identifier (Android resource-id, iOS accessibility id,
|
|
660
|
+
* macOS AXIdentifier) → emitted as `id=`.
|
|
661
|
+
* - `label` the exact accessibility label (Android content-desc, iOS label,
|
|
662
|
+
* macOS title/description) → emitted as `label="…"`.
|
|
663
|
+
* - `role` the class/type/role string to emit verbatim (already shortened for
|
|
664
|
+
* iOS) → emitted as `role=` and in `role=…[name="…"]`.
|
|
665
|
+
* - `name` the representative visible-text name used for `role=…[name]` and
|
|
666
|
+
* the `text=` fallback.
|
|
667
|
+
*/
|
|
668
|
+
type NativeRankFields = {
|
|
669
|
+
id?: string;
|
|
670
|
+
label?: string;
|
|
671
|
+
role?: string;
|
|
672
|
+
name?: string;
|
|
673
|
+
};
|
|
674
|
+
/**
|
|
675
|
+
* Ranked selector candidates for a node, best → last resort:
|
|
676
|
+
* `id=` > `label="…"` > `role=…[name="…"]` > `text="…"`, with a bare `role=`
|
|
677
|
+
* fallback so any node carrying a role stays addressable. This is the one ranking
|
|
678
|
+
* algorithm every native analyzer emits through, so a change to selector priority
|
|
679
|
+
* happens in exactly one place. Returns an empty array when a node exposes nothing
|
|
680
|
+
* addressable.
|
|
681
|
+
*/
|
|
682
|
+
declare function rankNativeSelectors(fields: NativeRankFields): string[];
|
|
683
|
+
/** The native targets whose selector dialect this module defines. */
|
|
684
|
+
type NativePlatform = "android" | "ios" | "macos";
|
|
685
|
+
/** How a selector kind resolves on one platform (mirrors the matrix above). */
|
|
686
|
+
type SelectorKindMapping = {
|
|
687
|
+
/** The native attribute(s) the kind targets. */
|
|
688
|
+
attribute: string;
|
|
689
|
+
/** The comparison mode against that attribute. */
|
|
690
|
+
match: "exact" | "exact (package-qualified)" | "substring";
|
|
691
|
+
};
|
|
692
|
+
/**
|
|
693
|
+
* The per-platform attribute mapping tables — the machine-readable form of the
|
|
694
|
+
* compatibility matrix, so the mapping is documented once and can be asserted in
|
|
695
|
+
* tests. Keyed by platform, then by the four addressable selector kinds. (`role`
|
|
696
|
+
* describes the bare `role=` form; the `role=…[name]` composite pairs an exact
|
|
697
|
+
* role with a substring name, per the matrix.)
|
|
698
|
+
*/
|
|
699
|
+
declare const NATIVE_ATTRIBUTE_MAP: Readonly<Record<NativePlatform, Readonly<Record<"id" | "label" | "text" | "role", SelectorKindMapping>>>>;
|
|
700
|
+
/**
|
|
701
|
+
* A hierarchy node projected into the neutral attributes the matcher compares
|
|
702
|
+
* against. Platform code (analyzers, and later the runners/macdriver) projects
|
|
703
|
+
* its own node shape into this:
|
|
704
|
+
* - `id` native identifier for exact `id=` matching.
|
|
705
|
+
* - `label` exact accessibility label for exact `label=` matching.
|
|
706
|
+
* - `role` class/type/role in the platform's canonical (full) form; the
|
|
707
|
+
* dialect's {@link NativeMatchDialect.normalizeRole} reconciles a
|
|
708
|
+
* shorthand selector (`Button`) with a full node type.
|
|
709
|
+
* - `textValues` every string a `text=` / `[name]` substring should test
|
|
710
|
+
* (Android: `[text]`; iOS: `[label, value]`; macOS:
|
|
711
|
+
* `[title, description, value]`).
|
|
712
|
+
* - `focused` whether the node currently holds keyboard focus (`:focus`).
|
|
713
|
+
*/
|
|
714
|
+
type NativeNode = {
|
|
715
|
+
id?: string;
|
|
716
|
+
label?: string;
|
|
717
|
+
role?: string;
|
|
718
|
+
textValues: string[];
|
|
719
|
+
focused?: boolean;
|
|
720
|
+
};
|
|
721
|
+
/** Options threaded into matching (currently only Android id package-qualification). */
|
|
722
|
+
type NativeMatchOptions = {
|
|
723
|
+
/** App package used to qualify a bare Android `id=` before an exact compare. */
|
|
724
|
+
appPackage?: string;
|
|
725
|
+
};
|
|
726
|
+
/**
|
|
727
|
+
* The platform-specific part of matching: how to normalize a role/type string for
|
|
728
|
+
* comparison, and how to normalize an `id=` value before an exact id compare.
|
|
729
|
+
* Everything else (exact id/label, substring text, focus) is platform-independent.
|
|
730
|
+
*/
|
|
731
|
+
type NativeMatchDialect = {
|
|
732
|
+
platform: NativePlatform;
|
|
733
|
+
/** Canonicalize a role/type string so a shorthand selector matches a full node type. */
|
|
734
|
+
normalizeRole: (role: string) => string;
|
|
735
|
+
/** Canonicalize an `id=` value (Android package-qualifies bare ids; others identity). */
|
|
736
|
+
normalizeId: (value: string, options: NativeMatchOptions) => string;
|
|
737
|
+
};
|
|
738
|
+
/** Android matching dialect: identity role compare, package-qualified ids. */
|
|
739
|
+
declare const ANDROID_MATCH_DIALECT: NativeMatchDialect;
|
|
740
|
+
/** iOS matching dialect: `XCUIElementType…`-normalized role compare, identity ids. */
|
|
741
|
+
declare const IOS_MATCH_DIALECT: NativeMatchDialect;
|
|
742
|
+
/** macOS matching dialect (exposed for the future macdriver migration). */
|
|
743
|
+
declare const MACOS_MATCH_DIALECT: NativeMatchDialect;
|
|
744
|
+
/**
|
|
745
|
+
* Decide whether a single projected {@link NativeNode} satisfies a parsed
|
|
746
|
+
* {@link NativeSelector}, using the platform's attribute + match-mode semantics:
|
|
747
|
+
* - `id` exact match on `node.id` (Android value package-qualified first).
|
|
748
|
+
* - `label` exact match on `node.label`.
|
|
749
|
+
* - `text` substring match against any of `node.textValues`.
|
|
750
|
+
* - `role` exact (normalized) role match; with `name`, additionally a
|
|
751
|
+
* substring match against any of `node.textValues`.
|
|
752
|
+
* - `focused` node currently holds keyboard focus.
|
|
753
|
+
*/
|
|
754
|
+
declare function nodeMatchesSelector(dialect: NativeMatchDialect, selector: NativeSelector, node: NativeNode, options?: NativeMatchOptions): boolean;
|
|
755
|
+
/**
|
|
756
|
+
* Walk a hierarchy of platform nodes depth-first and collect every node matching
|
|
757
|
+
* `selector`, in document order. Generic over the caller's own node type so the
|
|
758
|
+
* analyzers get their original, fully-typed nodes back: pass a `project` that maps
|
|
759
|
+
* a node to its {@link NativeNode} attributes and a `children` accessor.
|
|
760
|
+
*/
|
|
761
|
+
declare function matchNativeTree<T>(dialect: NativeMatchDialect, selector: NativeSelector, root: T, project: (node: T) => NativeNode, children: (node: T) => readonly T[], options?: NativeMatchOptions): T[];
|
|
762
|
+
/**
|
|
763
|
+
* Parse a raw agent `/source` XML dump into an element tree (best-effort, via the
|
|
764
|
+
* dependency-free {@link parseXml}) so a caller can match against a snapshot
|
|
765
|
+
* without a device. Returns null when the payload holds no element.
|
|
766
|
+
*/
|
|
767
|
+
declare function parseSnapshot(xml: string): XmlElement | null;
|
|
768
|
+
|
|
769
|
+
/** A structured query the {@link AndroidAgentClient} resolves against the device. */
|
|
770
|
+
type AndroidQuery = {
|
|
771
|
+
by: "id";
|
|
772
|
+
value: string;
|
|
773
|
+
} | {
|
|
774
|
+
by: "accessibilityId";
|
|
775
|
+
value: string;
|
|
776
|
+
} | {
|
|
777
|
+
by: "role";
|
|
778
|
+
role: string;
|
|
779
|
+
name?: string;
|
|
780
|
+
} | {
|
|
781
|
+
by: "text";
|
|
782
|
+
value: string;
|
|
783
|
+
} | {
|
|
784
|
+
by: "focused";
|
|
785
|
+
};
|
|
786
|
+
/**
|
|
787
|
+
* A locator in the uiautomator2 server's native wire shape. The raw on-device
|
|
788
|
+
* server does NOT accept W3C `{using, value}` — that translation normally lives
|
|
789
|
+
* in Appium's driver layer, which we bypass — it requires `{strategy, selector}`
|
|
790
|
+
* (plus a `context` field, empty for a root-scoped search). Device-verified
|
|
791
|
+
* against appium-uiautomator2-server 10.6.2 (2026-08-19).
|
|
792
|
+
*/
|
|
793
|
+
type AndroidLocator = {
|
|
794
|
+
strategy: string;
|
|
795
|
+
selector: string;
|
|
796
|
+
context: string;
|
|
797
|
+
};
|
|
798
|
+
/**
|
|
799
|
+
* The semantic transport `AndroidDriver` talks to: element lookups return opaque
|
|
800
|
+
* element ids, and interactions take those ids. The HTTP/UiAutomator2
|
|
801
|
+
* implementation lives in {@link ./android-agent.js}; tests fake this interface.
|
|
802
|
+
*/
|
|
803
|
+
interface AndroidAgentClient {
|
|
804
|
+
/** Resolve the first element matching `query`, or null when none match. */
|
|
805
|
+
findElement(query: AndroidQuery): Promise<string | null>;
|
|
806
|
+
/** Resolve every element id matching `query` (empty when none match). */
|
|
807
|
+
findElements(query: AndroidQuery): Promise<string[]>;
|
|
808
|
+
click(elementId: string): Promise<void>;
|
|
809
|
+
/** Replace an element's text (unicode-safe; W3C `element/value`). */
|
|
810
|
+
setValue(elementId: string, text: string): Promise<void>;
|
|
811
|
+
getText(elementId: string): Promise<string | null>;
|
|
812
|
+
/** Dispatch a global key event by Android key code (goes to the focused view). */
|
|
813
|
+
pressKeyCode(keyCode: number): Promise<void>;
|
|
814
|
+
/** Capture the current screen as PNG bytes. */
|
|
815
|
+
screenshotPng(): Promise<Buffer>;
|
|
816
|
+
/**
|
|
817
|
+
* Return the current UI hierarchy as uiautomator2 `/source` XML. Present on
|
|
818
|
+
* live clients and consumed by the analyzer (PROWL-061); optional so lighter
|
|
819
|
+
* fakes that only drive/query need not implement it.
|
|
820
|
+
*/
|
|
821
|
+
source?(): Promise<string>;
|
|
822
|
+
close(): Promise<void>;
|
|
823
|
+
}
|
|
824
|
+
type AndroidDriverOptions = {
|
|
825
|
+
/** Package name, used only for the informational `currentUrl()` value. */
|
|
826
|
+
appLabel?: string;
|
|
827
|
+
};
|
|
828
|
+
/**
|
|
829
|
+
* Android key names accepted by the `press` step, mapped to KeyEvent key codes.
|
|
830
|
+
* Names are matched case-insensitively.
|
|
831
|
+
*/
|
|
832
|
+
declare const ANDROID_KEYCODES: Readonly<Record<string, number>>;
|
|
833
|
+
/** Escape a string for embedding inside a `new UiSelector()...("...")` argument. */
|
|
834
|
+
declare function escapeUiSelectorArg(value: string): string;
|
|
835
|
+
/**
|
|
836
|
+
* Parse a Prowl selector string into an {@link AndroidQuery}. Bare text matches by
|
|
837
|
+
* text. The grammar (and its `label=`-in-assertions trap) is defined once in the
|
|
838
|
+
* shared native selector engine ({@link parseNativeSelector}, PROWL-060); this
|
|
839
|
+
* only maps the neutral result onto Android's on-device query.
|
|
840
|
+
*/
|
|
841
|
+
declare function parseAndroidSelector(selector: string): AndroidQuery;
|
|
842
|
+
/**
|
|
843
|
+
* Translate an {@link AndroidQuery} into the uiautomator2 server's native
|
|
844
|
+
* locator shape. `id`/`accessibility id` are native strategies;
|
|
845
|
+
* text/role(+name)/focused compose a `-android uiautomator` `UiSelector`.
|
|
846
|
+
* `appPackage` qualifies bare `id=` names ({@link qualifyResourceId}).
|
|
847
|
+
*/
|
|
848
|
+
declare function androidQueryToLocator(query: AndroidQuery, options?: {
|
|
849
|
+
appPackage?: string;
|
|
850
|
+
}): AndroidLocator;
|
|
851
|
+
/** The literal text a `text=` selector matches, else null (mirrors the web driver). */
|
|
852
|
+
declare function unwrapAndroidTextSelector(selector: string): string | null;
|
|
853
|
+
/** Wrap a live {@link AndroidAgentClient} as a {@link SessionDriver}. */
|
|
854
|
+
declare function createAndroidDriver(client: AndroidAgentClient, options?: AndroidDriverOptions): SessionDriver;
|
|
855
|
+
|
|
856
|
+
/** Result of one adb invocation. */
|
|
857
|
+
type AdbResult = {
|
|
858
|
+
stdout: string;
|
|
859
|
+
stderr: string;
|
|
860
|
+
code: number;
|
|
861
|
+
};
|
|
862
|
+
/** Runs one adb command to completion and resolves with its captured output. */
|
|
863
|
+
type AdbRunner = (args: string[], options?: {
|
|
864
|
+
timeoutMs?: number;
|
|
865
|
+
}) => Promise<AdbResult>;
|
|
866
|
+
/** A handle to a spawned long-running adb process (the instrumentation server). */
|
|
867
|
+
type AdbProcessHandle = {
|
|
868
|
+
kill(): void;
|
|
869
|
+
};
|
|
870
|
+
/** Spawns a long-running adb command (e.g. `am instrument -w`) in the background. */
|
|
871
|
+
type AdbSpawner = (args: string[]) => AdbProcessHandle;
|
|
872
|
+
/** One row of `adb devices -l`. */
|
|
873
|
+
type AdbDevice = {
|
|
874
|
+
serial: string;
|
|
875
|
+
state: string;
|
|
876
|
+
description: Record<string, string>;
|
|
877
|
+
};
|
|
878
|
+
/**
|
|
879
|
+
* Parse `adb devices -l` output into structured rows. The header line
|
|
880
|
+
* (`List of devices attached`) and blank lines are skipped.
|
|
881
|
+
*/
|
|
882
|
+
declare function parseAdbDevices(stdout: string): AdbDevice[];
|
|
883
|
+
/** Devices that are fully booted and usable (`device` state). */
|
|
884
|
+
declare function bootedDevices(devices: AdbDevice[]): AdbDevice[];
|
|
885
|
+
/**
|
|
886
|
+
* Choose which device to drive. With `requested` set it must be attached and
|
|
887
|
+
* booted. Otherwise exactly one booted device is required; zero or many raise an
|
|
888
|
+
* actionable error listing what was found.
|
|
889
|
+
*/
|
|
890
|
+
declare function selectDeviceSerial(devices: AdbDevice[], requested?: string): string;
|
|
891
|
+
/** List attached devices via `adb devices -l`. */
|
|
892
|
+
declare function listDevices(runner: AdbRunner): Promise<AdbDevice[]>;
|
|
893
|
+
/**
|
|
894
|
+
* Parse the local port `adb forward tcp:0 tcp:<remote>` prints (dynamic port
|
|
895
|
+
* allocation). adb echoes the chosen local port on stdout.
|
|
896
|
+
*/
|
|
897
|
+
declare function parseForwardPort(stdout: string): number;
|
|
898
|
+
/** Parse the package name out of `aapt dump badging <apk>` output. */
|
|
899
|
+
declare function parseAaptPackage(stdout: string): string | null;
|
|
900
|
+
|
|
901
|
+
/** The remote port the uiautomator2 server listens on inside the device. */
|
|
902
|
+
declare const UIA2_REMOTE_PORT = 6790;
|
|
903
|
+
/** Locations of the two prebuilt agent APKs. */
|
|
904
|
+
type AgentApks = {
|
|
905
|
+
serverApk: string;
|
|
906
|
+
testApk: string;
|
|
907
|
+
};
|
|
908
|
+
/** Establishes a live {@link AndroidAgentClient} against a forwarded local port. */
|
|
909
|
+
type AgentConnector = (options: {
|
|
910
|
+
host: string;
|
|
911
|
+
port: number;
|
|
912
|
+
requestTimeoutMs: number;
|
|
913
|
+
readyDeadlineMs: number;
|
|
914
|
+
/** Target app's package name; qualifies bare `id=` selectors on the device. */
|
|
915
|
+
appPackage?: string;
|
|
916
|
+
}) => Promise<AndroidAgentClient>;
|
|
917
|
+
/** Resolve a package name from an `.apk` file, or null when it can't be determined. */
|
|
918
|
+
type AaptResolver = (apkPath: string) => Promise<string | null>;
|
|
919
|
+
/**
|
|
920
|
+
* Resolve the two agent APKs shipped inside `appium-uiautomator2-server`. They
|
|
921
|
+
* live under the package's `apks/` folder; the server APK is version-stamped.
|
|
922
|
+
*/
|
|
923
|
+
declare function resolveAgentApks(requireFn?: NodeRequire): AgentApks;
|
|
924
|
+
/** Default connector: builds the HTTP transport, waits for readiness, opens a session. */
|
|
925
|
+
declare const defaultAgentConnector: AgentConnector;
|
|
926
|
+
type AndroidSession = {
|
|
927
|
+
client: AndroidAgentClient;
|
|
928
|
+
driver: SessionDriver;
|
|
929
|
+
/** The resolved package name being driven. */
|
|
930
|
+
package: string;
|
|
931
|
+
/** The adb serial of the device being driven. */
|
|
932
|
+
serial: string;
|
|
933
|
+
/** Tear down the session, agent, port forward, and app (best effort). */
|
|
934
|
+
teardown(): Promise<void>;
|
|
935
|
+
};
|
|
936
|
+
type LaunchAndroidOptions = {
|
|
937
|
+
/** Package name or `.apk` path. */
|
|
938
|
+
app: string;
|
|
939
|
+
/** adb serial when more than one device is attached. */
|
|
940
|
+
deviceSerial?: string;
|
|
941
|
+
/** `pm clear` the package before launch for a deterministic cold start. */
|
|
942
|
+
coldStart?: boolean;
|
|
943
|
+
timeoutMs?: number;
|
|
944
|
+
runner?: AdbRunner;
|
|
945
|
+
spawner?: AdbSpawner;
|
|
946
|
+
agentConnector?: AgentConnector;
|
|
947
|
+
apks?: AgentApks;
|
|
948
|
+
aaptResolver?: AaptResolver;
|
|
949
|
+
/** Optional app scope guardrail from config.guardrails.allowedApps. */
|
|
950
|
+
allowedApps?: string[];
|
|
951
|
+
};
|
|
952
|
+
/**
|
|
953
|
+
* Preflight, install, launch, and attach the uiautomator2 agent, returning a
|
|
954
|
+
* live {@link AndroidSession}. Actionable errors cover each failure mode: adb
|
|
955
|
+
* missing / no booted device (device selection), agent unreachable (readiness).
|
|
956
|
+
*/
|
|
957
|
+
declare function launchAndroidSession(options: LaunchAndroidOptions): Promise<AndroidSession>;
|
|
958
|
+
/** Tear down an Android session (agent session, instrumentation, forward, app). */
|
|
959
|
+
declare function closeAndroidSession(session: AndroidSession): Promise<void>;
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* PROWL-058 / ARCH-009 — HTTP/JSON transport for the on-device uiautomator2 agent.
|
|
963
|
+
*
|
|
964
|
+
* `appium-uiautomator2-server` exposes W3C-WebDriver-shaped endpoints over plain
|
|
965
|
+
* HTTP. This module speaks them with the global `fetch` (no heavy WebDriver SDK,
|
|
966
|
+
* per the `ai.ts` ethos), mirroring the mac-helper client's ergonomics: a
|
|
967
|
+
* per-request deadline via `AbortController`, and cleanup on
|
|
968
|
+
* every path. It exposes the semantic {@link AndroidAgentClient} the driver
|
|
969
|
+
* consumes; tests fake either the `fetch` implementation or the client itself.
|
|
970
|
+
*/
|
|
971
|
+
|
|
972
|
+
/** The subset of `fetch` this module uses; overridable in tests. */
|
|
973
|
+
type FetchLike$1 = (url: string, init: RequestInit) => Promise<Response>;
|
|
974
|
+
/** Default per-request deadline for the agent transport. */
|
|
975
|
+
declare const DEFAULT_AGENT_REQUEST_TIMEOUT_MS = 30000;
|
|
976
|
+
type Uia2TransportOptions = {
|
|
977
|
+
/** Base URL including the `/wd/hub` prefix, e.g. `http://127.0.0.1:6790/wd/hub`. */
|
|
978
|
+
baseUrl: string;
|
|
979
|
+
requestTimeoutMs?: number;
|
|
980
|
+
fetchImpl?: FetchLike$1;
|
|
981
|
+
};
|
|
982
|
+
/** An HTTP-level failure from the agent, carrying the status and parsed body. */
|
|
983
|
+
declare class Uia2HttpError extends Error {
|
|
984
|
+
readonly status: number;
|
|
985
|
+
readonly webdriverError?: string | undefined;
|
|
986
|
+
constructor(message: string, status: number, webdriverError?: string | undefined);
|
|
987
|
+
}
|
|
988
|
+
/** Low-level request/response transport with a per-request deadline. */
|
|
989
|
+
declare class Uia2Transport {
|
|
990
|
+
private readonly baseUrl;
|
|
991
|
+
private readonly requestTimeoutMs;
|
|
992
|
+
private readonly fetchImpl;
|
|
993
|
+
constructor(options: Uia2TransportOptions);
|
|
994
|
+
/**
|
|
995
|
+
* Send one request and return the parsed `value` field. Rejects with a
|
|
996
|
+
* {@link Uia2HttpError} on a non-2xx response, or a timeout error when the
|
|
997
|
+
* per-request deadline elapses.
|
|
998
|
+
*/
|
|
999
|
+
request(method: string, path: string, body?: unknown, timeoutMs?: number): Promise<unknown>;
|
|
1000
|
+
}
|
|
1001
|
+
/** Extract an element id from a W3C element-reference object, or null. */
|
|
1002
|
+
declare function extractElementId(value: unknown): string | null;
|
|
1003
|
+
/**
|
|
1004
|
+
* Create a uiautomator2 session and return its id. Uses the W3C `capabilities`
|
|
1005
|
+
* envelope; the server ignores the empty match set and starts a default session.
|
|
1006
|
+
*/
|
|
1007
|
+
declare function createUia2Session(transport: Uia2Transport): Promise<string>;
|
|
1008
|
+
/** Poll `GET /status` until the agent reports ready or the deadline elapses. */
|
|
1009
|
+
declare function waitForAgentReady(transport: Uia2Transport, options?: {
|
|
1010
|
+
deadlineMs: number;
|
|
1011
|
+
intervalMs?: number;
|
|
1012
|
+
}): Promise<void>;
|
|
1013
|
+
/**
|
|
1014
|
+
* Build the semantic {@link AndroidAgentClient} over a live session. `close`
|
|
1015
|
+
* deletes the session (best effort); the transport itself is stateless.
|
|
1016
|
+
*/
|
|
1017
|
+
declare function createUia2AgentClient(transport: Uia2Transport, sessionId: string, options?: {
|
|
1018
|
+
appPackage?: string;
|
|
1019
|
+
}): AndroidAgentClient;
|
|
1020
|
+
|
|
1021
|
+
/** A structured query the {@link IosAgentClient} resolves against the simulator. */
|
|
1022
|
+
type IosQuery = {
|
|
1023
|
+
by: "accessibilityId";
|
|
1024
|
+
value: string;
|
|
1025
|
+
} | {
|
|
1026
|
+
by: "label";
|
|
1027
|
+
value: string;
|
|
1028
|
+
} | {
|
|
1029
|
+
by: "role";
|
|
1030
|
+
role: string;
|
|
1031
|
+
name?: string;
|
|
1032
|
+
} | {
|
|
1033
|
+
by: "text";
|
|
1034
|
+
value: string;
|
|
1035
|
+
} | {
|
|
1036
|
+
by: "focused";
|
|
1037
|
+
};
|
|
1038
|
+
/** A WDA locator strategy ({@code using}) + its value, as the agent expects. */
|
|
1039
|
+
type IosLocator = {
|
|
1040
|
+
using: string;
|
|
1041
|
+
value: string;
|
|
1042
|
+
};
|
|
1043
|
+
/**
|
|
1044
|
+
* The semantic transport `IosDriver` talks to: element lookups return opaque
|
|
1045
|
+
* element ids, and interactions take those ids. The HTTP/WDA implementation lives
|
|
1046
|
+
* in {@link ./ios-agent.js}; tests fake this interface.
|
|
1047
|
+
*/
|
|
1048
|
+
interface IosAgentClient {
|
|
1049
|
+
/** Resolve the first element matching `query`, or null when none match. */
|
|
1050
|
+
findElement(query: IosQuery): Promise<string | null>;
|
|
1051
|
+
/** Resolve every element id matching `query` (empty when none match). */
|
|
1052
|
+
findElements(query: IosQuery): Promise<string[]>;
|
|
1053
|
+
click(elementId: string): Promise<void>;
|
|
1054
|
+
/** Replace an element's text (W3C `element/value`). */
|
|
1055
|
+
setValue(elementId: string, text: string): Promise<void>;
|
|
1056
|
+
getText(elementId: string): Promise<string | null>;
|
|
1057
|
+
/** Send raw key sequences to the focused element (WDA `/wda/keys`). */
|
|
1058
|
+
sendKeys(keys: string[]): Promise<void>;
|
|
1059
|
+
/** Return to the springboard home screen (WDA `/wda/homescreen`). */
|
|
1060
|
+
homescreen(): Promise<void>;
|
|
1061
|
+
/**
|
|
1062
|
+
* Return the current UI hierarchy as WebDriverAgent `/source` XML. Present on
|
|
1063
|
+
* live clients and consumed by the analyzer (PROWL-061); optional so lighter
|
|
1064
|
+
* fakes that only drive/query need not implement it.
|
|
1065
|
+
*/
|
|
1066
|
+
source?(): Promise<string>;
|
|
1067
|
+
close(): Promise<void>;
|
|
1068
|
+
}
|
|
1069
|
+
type IosDriverOptions = {
|
|
1070
|
+
/** Bundle id, used only for the informational `currentUrl()` value. */
|
|
1071
|
+
appLabel?: string;
|
|
1072
|
+
/** Capture a PNG screenshot to `path` (injected simctl capture). */
|
|
1073
|
+
captureScreenshot: (path: string) => Promise<void>;
|
|
1074
|
+
};
|
|
1075
|
+
/**
|
|
1076
|
+
* Key names the `press` step supports on iOS, sorted for error messages. `enter`/
|
|
1077
|
+
* `return` send a newline, `delete`/`backspace` send a backspace (both via WDA's
|
|
1078
|
+
* key endpoint), and `home` returns to the springboard.
|
|
1079
|
+
*/
|
|
1080
|
+
declare const IOS_PRESS_KEYS: readonly string[];
|
|
1081
|
+
/** Escape a string for embedding inside a double-quoted NSPredicate string literal. */
|
|
1082
|
+
declare function escapePredicateArg(value: string): string;
|
|
1083
|
+
/**
|
|
1084
|
+
* Parse a Prowl selector string into an {@link IosQuery}. Bare text matches by
|
|
1085
|
+
* text. The grammar (and its `label=`-in-assertions trap) is defined once in the
|
|
1086
|
+
* shared native selector engine ({@link parseNativeSelector}, PROWL-060); this
|
|
1087
|
+
* only maps the neutral result onto iOS's WDA query.
|
|
1088
|
+
*/
|
|
1089
|
+
declare function parseIosSelector(selector: string): IosQuery;
|
|
1090
|
+
/**
|
|
1091
|
+
* Translate an {@link IosQuery} into a WDA locator strategy. `id` uses the native
|
|
1092
|
+
* `accessibility id` strategy and bare roles use `class name`; everything else
|
|
1093
|
+
* composes an NSPredicate string (WDA's `predicate string` strategy).
|
|
1094
|
+
*/
|
|
1095
|
+
declare function iosQueryToLocator(query: IosQuery): IosLocator;
|
|
1096
|
+
/** The literal text a `text=` selector matches, else null (mirrors the web driver). */
|
|
1097
|
+
declare function unwrapIosTextSelector(selector: string): string | null;
|
|
1098
|
+
/** Wrap a live {@link IosAgentClient} as a {@link SessionDriver}. */
|
|
1099
|
+
declare function createIosDriver(client: IosAgentClient, options: IosDriverOptions): SessionDriver;
|
|
1100
|
+
|
|
1101
|
+
/** Result of one `xcrun` invocation. */
|
|
1102
|
+
type SimctlResult = {
|
|
1103
|
+
stdout: string;
|
|
1104
|
+
stderr: string;
|
|
1105
|
+
code: number;
|
|
1106
|
+
};
|
|
1107
|
+
/** Runs one `xcrun <args>` command to completion and resolves with its output. */
|
|
1108
|
+
type SimctlRunner = (args: string[], options?: {
|
|
1109
|
+
timeoutMs?: number;
|
|
1110
|
+
env?: NodeJS.ProcessEnv;
|
|
1111
|
+
}) => Promise<SimctlResult>;
|
|
1112
|
+
/** A handle to a spawned long-running `xcrun` process (the WDA xcodebuild test host). */
|
|
1113
|
+
type XcrunProcessHandle = {
|
|
1114
|
+
kill(): void;
|
|
1115
|
+
};
|
|
1116
|
+
/**
|
|
1117
|
+
* Spawns a long-running `xcrun` command in the background — used for the
|
|
1118
|
+
* `xcodebuild test-without-building` run that hosts WebDriverAgent (which never
|
|
1119
|
+
* exits on its own; it serves HTTP until killed). Mirrors the Android
|
|
1120
|
+
* `AdbSpawner` so the launch flow stays unit-testable with a fake.
|
|
1121
|
+
*/
|
|
1122
|
+
type XcrunSpawner = (args: string[], options?: {
|
|
1123
|
+
env?: NodeJS.ProcessEnv;
|
|
1124
|
+
}) => XcrunProcessHandle;
|
|
1125
|
+
/** One simulator device from `simctl list devices --json`. */
|
|
1126
|
+
type SimDevice = {
|
|
1127
|
+
udid: string;
|
|
1128
|
+
name: string;
|
|
1129
|
+
state: string;
|
|
1130
|
+
runtime: string;
|
|
1131
|
+
isAvailable: boolean;
|
|
1132
|
+
};
|
|
1133
|
+
type SimulatorReservation = {
|
|
1134
|
+
udid: string;
|
|
1135
|
+
release(): Promise<void>;
|
|
1136
|
+
};
|
|
1137
|
+
type ReserveSimulatorOptions = {
|
|
1138
|
+
/** Override the lock directory root; intended for tests. */
|
|
1139
|
+
lockRoot?: string;
|
|
1140
|
+
};
|
|
1141
|
+
declare const DEFAULT_SIMULATOR_LOCK_ROOT: string;
|
|
1142
|
+
/**
|
|
1143
|
+
* Reserve a simulator UDID across processes. The lock is held until `release` is
|
|
1144
|
+
* called, preventing one session's teardown from terminating another session's
|
|
1145
|
+
* WDA runner or target app on the same simulator.
|
|
1146
|
+
*/
|
|
1147
|
+
declare function reserveSimulatorUdid(udid: string, options?: ReserveSimulatorOptions): Promise<SimulatorReservation>;
|
|
1148
|
+
/**
|
|
1149
|
+
* Allocate a free local TCP port by binding to :0 and releasing it. The returned
|
|
1150
|
+
* port is advisory after release; callers that hand it to another process must
|
|
1151
|
+
* handle a later bind/readiness failure.
|
|
1152
|
+
*/
|
|
1153
|
+
declare function findFreePort(): Promise<number>;
|
|
1154
|
+
/**
|
|
1155
|
+
* Parse `simctl list devices --json` into a flat device list. The JSON maps
|
|
1156
|
+
* runtime identifiers to arrays of devices; the runtime is folded into each row.
|
|
1157
|
+
*/
|
|
1158
|
+
declare function parseSimctlDevices(json: string): SimDevice[];
|
|
1159
|
+
/** Simulators that are currently booted. */
|
|
1160
|
+
declare function bootedSimulators(devices: SimDevice[]): SimDevice[];
|
|
1161
|
+
/**
|
|
1162
|
+
* Choose which simulator to drive. With `requested` set it must exist and be
|
|
1163
|
+
* booted. Otherwise exactly one booted simulator is required; zero or many raise
|
|
1164
|
+
* an actionable error listing what was found.
|
|
1165
|
+
*/
|
|
1166
|
+
declare function selectSimulatorUdid(devices: SimDevice[], requested?: string): string;
|
|
1167
|
+
/** List all simulators via `simctl list devices --json`. */
|
|
1168
|
+
declare function listSimulators(runner: SimctlRunner): Promise<SimDevice[]>;
|
|
1169
|
+
/** Parse the `x.y[.z]` version out of `xcodebuild -version` output. */
|
|
1170
|
+
declare function parseXcodeVersion(stdout: string): string | null;
|
|
1171
|
+
|
|
1172
|
+
/** Bundle id of the prebuilt WebDriverAgent runner (its xctest host app). */
|
|
1173
|
+
declare const WDA_RUNNER_BUNDLE_ID = "com.facebook.WebDriverAgentRunner.xctrunner";
|
|
1174
|
+
/** Env var WDA reads to pick its HTTP port (injected into the runner's xctestrun env). */
|
|
1175
|
+
declare const WDA_USE_PORT_ENV = "USE_PORT";
|
|
1176
|
+
/** Establishes a live {@link IosAgentClient} against a running WDA HTTP server. */
|
|
1177
|
+
type IosAgentConnector = (options: {
|
|
1178
|
+
host: string;
|
|
1179
|
+
port: number;
|
|
1180
|
+
bundleId: string;
|
|
1181
|
+
requestTimeoutMs: number;
|
|
1182
|
+
readyDeadlineMs: number;
|
|
1183
|
+
}) => Promise<IosAgentClient>;
|
|
1184
|
+
/** Default connector: builds the HTTP transport, waits for readiness, opens a session. */
|
|
1185
|
+
declare const defaultIosAgentConnector: IosAgentConnector;
|
|
1186
|
+
/** Resolve the WDA Xcode project bundled inside `appium-webdriveragent`. */
|
|
1187
|
+
declare function resolveWdaProject(requireFn?: NodeRequire): {
|
|
1188
|
+
projectPath: string;
|
|
1189
|
+
version: string;
|
|
1190
|
+
};
|
|
1191
|
+
/** Cache directory for a built WDA runner, keyed on the WDA + Xcode versions. */
|
|
1192
|
+
declare function wdaCacheDir(wdaVersion: string, xcode: string, homeDir?: string): string;
|
|
1193
|
+
type ResolveWdaTestRunOptions = {
|
|
1194
|
+
runner?: SimctlRunner;
|
|
1195
|
+
env?: NodeJS.ProcessEnv;
|
|
1196
|
+
homeDir?: string;
|
|
1197
|
+
requireFn?: NodeRequire;
|
|
1198
|
+
/** One-line progress notice sink (defaults to stderr). */
|
|
1199
|
+
logger?: (message: string) => void;
|
|
1200
|
+
};
|
|
1201
|
+
/**
|
|
1202
|
+
* Resolve a WebDriverAgent `.xctestrun` file (the input to the XCTest host
|
|
1203
|
+
* launch). Order: (a) the `PROWL_WDA_RUNNER` env override; (b) a previously built
|
|
1204
|
+
* xctestrun in the version-keyed cache; (c) a one-time
|
|
1205
|
+
* `xcodebuild build-for-testing` that populates the cache. Simulators need no
|
|
1206
|
+
* code signing. Throws actionable errors when Xcode is missing or the build fails.
|
|
1207
|
+
*/
|
|
1208
|
+
declare function resolveWdaTestRun(options?: ResolveWdaTestRunOptions): Promise<string>;
|
|
1209
|
+
/**
|
|
1210
|
+
* Return a deep copy of a parsed `.xctestrun` plist with the WDA HTTP port
|
|
1211
|
+
* (`USE_PORT`) injected into every test target's `EnvironmentVariables`. WDA
|
|
1212
|
+
* reads `USE_PORT` from its process environment; under `xcodebuild test` that
|
|
1213
|
+
* environment comes from the xctestrun, so the dynamic port must be written here.
|
|
1214
|
+
*
|
|
1215
|
+
* Handles both xctestrun layouts: format 1 (top-level dict keyed by test-target
|
|
1216
|
+
* name) and format 2 (a `TestConfigurations[].TestTargets[]` tree). A test target
|
|
1217
|
+
* is recognized by a `TestBundlePath`/`TestHostPath` string; any object that
|
|
1218
|
+
* already carries an `EnvironmentVariables` dict is updated too. Throws when no
|
|
1219
|
+
* target is found, so a future format change fails loudly rather than launching
|
|
1220
|
+
* WDA on the wrong port.
|
|
1221
|
+
*/
|
|
1222
|
+
declare function injectUsePortIntoXctestrun(plist: unknown, port: number): unknown;
|
|
1223
|
+
/**
|
|
1224
|
+
* Produce a launch-specific `.xctestrun` with `USE_PORT` set to `port`, returning
|
|
1225
|
+
* its path. The base xctestrun (built/cached) is never mutated in place.
|
|
1226
|
+
*/
|
|
1227
|
+
type WdaTestRunPreparer = (options: {
|
|
1228
|
+
xctestrunPath: string;
|
|
1229
|
+
port: number;
|
|
1230
|
+
}) => Promise<string>;
|
|
1231
|
+
/**
|
|
1232
|
+
* Default preparer: reads the base xctestrun with `plutil` (JSON), injects
|
|
1233
|
+
* `USE_PORT`, and writes a fresh, uniquely-named xctestrun **next to the base
|
|
1234
|
+
* one**. This placement is required, not incidental: an xctestrun's product
|
|
1235
|
+
* paths are relative to `__TESTROOT__`, which xcodebuild resolves to the
|
|
1236
|
+
* directory containing the xctestrun file — so a copy written to a temp dir
|
|
1237
|
+
* makes `xcodebuild test-without-building` fail with "Missing test product".
|
|
1238
|
+
* Writing the sibling into the build's Products dir keeps `__TESTROOT__` pointing
|
|
1239
|
+
* at the real products. The intermediate JSON goes to a temp dir; only the
|
|
1240
|
+
* `.xctestrun` lands beside the products and is removed on teardown. `plutil`
|
|
1241
|
+
* ships with macOS, so no extra dependency is added.
|
|
1242
|
+
*/
|
|
1243
|
+
declare const defaultWdaTestRunPreparer: WdaTestRunPreparer;
|
|
1244
|
+
/** The `xcodebuild test-without-building` args that host WDA against `udid`. */
|
|
1245
|
+
declare function wdaTestRunArgs(xctestrunPath: string, udid: string): string[];
|
|
1246
|
+
type IosSession = {
|
|
1247
|
+
client: IosAgentClient;
|
|
1248
|
+
driver: SessionDriver;
|
|
1249
|
+
/** The resolved bundle id being driven. */
|
|
1250
|
+
bundleId: string;
|
|
1251
|
+
/** The UDID of the simulator being driven. */
|
|
1252
|
+
udid: string;
|
|
1253
|
+
/** Tear down the session, WDA runner, and target app (best effort). */
|
|
1254
|
+
teardown(): Promise<void>;
|
|
1255
|
+
};
|
|
1256
|
+
type LaunchIosOptions = {
|
|
1257
|
+
/** Bundle id or `.app` path. */
|
|
1258
|
+
app: string;
|
|
1259
|
+
/** Simulator UDID when more than one is booted. */
|
|
1260
|
+
udid?: string;
|
|
1261
|
+
/** Uninstall+reinstall the app before launch (requires a `.app` path). */
|
|
1262
|
+
coldStart?: boolean;
|
|
1263
|
+
timeoutMs?: number;
|
|
1264
|
+
runner?: SimctlRunner;
|
|
1265
|
+
/** Spawner for the long-running `xcodebuild test-without-building` WDA host. */
|
|
1266
|
+
spawner?: XcrunSpawner;
|
|
1267
|
+
/** Builds the per-launch port-injected xctestrun. */
|
|
1268
|
+
testRunPreparer?: WdaTestRunPreparer;
|
|
1269
|
+
portAllocator?: () => Promise<number>;
|
|
1270
|
+
agentConnector?: IosAgentConnector;
|
|
1271
|
+
/** Skip WDA build/resolution by supplying the base `.xctestrun` path directly. */
|
|
1272
|
+
wdaTestRun?: string;
|
|
1273
|
+
/** Optional app scope guardrail from config.guardrails.allowedApps. */
|
|
1274
|
+
allowedApps?: string[];
|
|
1275
|
+
/** Override the simulator lock root; intended for tests. */
|
|
1276
|
+
simulatorLockRoot?: string;
|
|
1277
|
+
logger?: (message: string) => void;
|
|
1278
|
+
};
|
|
1279
|
+
/**
|
|
1280
|
+
* Preflight, host WDA via `xcodebuild test-without-building`, launch the target
|
|
1281
|
+
* app, and attach, returning a live {@link IosSession}. Actionable errors cover
|
|
1282
|
+
* each failure mode: Xcode/simctl missing, no booted simulator (device
|
|
1283
|
+
* selection), WDA build failures, agent unreachable (readiness).
|
|
1284
|
+
*/
|
|
1285
|
+
declare function launchIosSession(options: LaunchIosOptions): Promise<IosSession>;
|
|
1286
|
+
/** Tear down an iOS session (agent session, WDA runner, target app). */
|
|
1287
|
+
declare function closeIosSession(session: IosSession): Promise<void>;
|
|
1288
|
+
|
|
1289
|
+
/**
|
|
1290
|
+
* PROWL-059 / ARCH-010 — HTTP/JSON transport for the on-simulator WebDriverAgent.
|
|
1291
|
+
*
|
|
1292
|
+
* WebDriverAgent (WDA) exposes W3C-WebDriver-shaped endpoints over plain HTTP. On
|
|
1293
|
+
* a simulator it is reachable directly at `http://127.0.0.1:<port>` (no tunnel),
|
|
1294
|
+
* so this module speaks it with the global `fetch` (no heavy WebDriver SDK, per
|
|
1295
|
+
* the `ai.ts` ethos), mirroring the Android agent's ergonomics: a per-request
|
|
1296
|
+
* deadline via `AbortController`, unref'd timers, and W3C element-key extraction.
|
|
1297
|
+
* It exposes the semantic {@link IosAgentClient} the driver consumes; tests fake
|
|
1298
|
+
* either the `fetch` implementation or the client itself.
|
|
1299
|
+
*/
|
|
1300
|
+
|
|
1301
|
+
/** The subset of `fetch` this module uses; overridable in tests. */
|
|
1302
|
+
type FetchLike = (url: string, init: RequestInit) => Promise<Response>;
|
|
1303
|
+
/** Default per-request deadline for the agent transport. */
|
|
1304
|
+
declare const DEFAULT_WDA_REQUEST_TIMEOUT_MS = 30000;
|
|
1305
|
+
type WdaTransportOptions = {
|
|
1306
|
+
/** Base URL, e.g. `http://127.0.0.1:8100`. */
|
|
1307
|
+
baseUrl: string;
|
|
1308
|
+
requestTimeoutMs?: number;
|
|
1309
|
+
fetchImpl?: FetchLike;
|
|
1310
|
+
};
|
|
1311
|
+
/** An HTTP-level failure from WDA, carrying the status and parsed body. */
|
|
1312
|
+
declare class WdaHttpError extends Error {
|
|
1313
|
+
readonly status: number;
|
|
1314
|
+
readonly webdriverError?: string | undefined;
|
|
1315
|
+
constructor(message: string, status: number, webdriverError?: string | undefined);
|
|
1316
|
+
}
|
|
1317
|
+
/** Low-level request/response transport with a per-request deadline. */
|
|
1318
|
+
declare class WdaTransport {
|
|
1319
|
+
private readonly baseUrl;
|
|
1320
|
+
private readonly requestTimeoutMs;
|
|
1321
|
+
private readonly fetchImpl;
|
|
1322
|
+
constructor(options: WdaTransportOptions);
|
|
1323
|
+
/**
|
|
1324
|
+
* Send one request and return the full parsed JSON body. Rejects with a
|
|
1325
|
+
* {@link WdaHttpError} on a non-2xx response, or a timeout error when the
|
|
1326
|
+
* per-request deadline elapses.
|
|
1327
|
+
*/
|
|
1328
|
+
requestFull(method: string, path: string, body?: unknown, timeoutMs?: number): Promise<unknown>;
|
|
1329
|
+
/** Like {@link requestFull} but returns just the `value` field. */
|
|
1330
|
+
request(method: string, path: string, body?: unknown, timeoutMs?: number): Promise<unknown>;
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Create a WDA session bound to `bundleId` and return its id. WDA activates the
|
|
1334
|
+
* app under test named in `alwaysMatch.bundleId`. The session id may arrive at
|
|
1335
|
+
* the envelope root or inside `value`, so both are checked.
|
|
1336
|
+
*/
|
|
1337
|
+
declare function createWdaSession(transport: WdaTransport, bundleId: string): Promise<string>;
|
|
1338
|
+
/** Poll `GET /status` until WDA reports ready or the deadline elapses. */
|
|
1339
|
+
declare function waitForWdaReady(transport: WdaTransport, options?: {
|
|
1340
|
+
deadlineMs: number;
|
|
1341
|
+
intervalMs?: number;
|
|
1342
|
+
}): Promise<void>;
|
|
1343
|
+
/**
|
|
1344
|
+
* Build the semantic {@link IosAgentClient} over a live session. `close` deletes
|
|
1345
|
+
* the session (best effort); the transport itself is stateless.
|
|
1346
|
+
*/
|
|
1347
|
+
declare function createWdaAgentClient(transport: WdaTransport, sessionId: string): IosAgentClient;
|
|
1348
|
+
|
|
561
1349
|
/**
|
|
562
1350
|
* PROWL-048 / ARCH-002 — target ⇄ step compatibility.
|
|
563
1351
|
*
|
|
@@ -579,9 +1367,18 @@ declare function webOnlyReason(step: Step): string | null;
|
|
|
579
1367
|
/**
|
|
580
1368
|
* Throw if any step in `steps` (recursing into `if`/`repeat` bodies) is not
|
|
581
1369
|
* supported by `target`. `runHunt` references are validated when the referenced
|
|
582
|
-
* hunt itself runs. No-op for the web target
|
|
1370
|
+
* hunt itself runs. No-op for the web target; every native target (macOS,
|
|
1371
|
+
* Android, iOS) rejects the same web-only step vocabulary.
|
|
583
1372
|
*/
|
|
584
1373
|
declare function assertStepsSupportedByTarget(steps: Step[], target: Target["type"]): void;
|
|
1374
|
+
/** Read the bundle id from an iOS `.app` (root `Info.plist`), or null. */
|
|
1375
|
+
declare function readIosBundleIdentifier(appPath: string): string | null;
|
|
1376
|
+
/**
|
|
1377
|
+
* Accepted identities for an iOS target app. A bare bundle id matches itself; a
|
|
1378
|
+
* `.app` path is authorized by its path, bundle name, or the `CFBundleIdentifier`
|
|
1379
|
+
* read from the bundle's root `Info.plist`.
|
|
1380
|
+
*/
|
|
1381
|
+
declare function iosAppAllowedIdentities(app: string): string[];
|
|
585
1382
|
/**
|
|
586
1383
|
* Native scope guardrail: if `allowedApps` is non-empty it must include an
|
|
587
1384
|
* accepted identity for the target app: bundle id, app bundle name, or `.app`
|
|
@@ -589,6 +1386,39 @@ declare function assertStepsSupportedByTarget(steps: Step[], target: Target["typ
|
|
|
589
1386
|
* allowed — mirroring how allowedDomains auto-includes the web target's host.
|
|
590
1387
|
*/
|
|
591
1388
|
declare function assertTargetAppAllowed(allowedApps: string[], app: string): void;
|
|
1389
|
+
/**
|
|
1390
|
+
* iOS scope guardrail (PROWL-059): mirrors {@link assertTargetAppAllowed} but
|
|
1391
|
+
* resolves identities via {@link iosAppAllowedIdentities} (bundle id or `.app`
|
|
1392
|
+
* path with the id read from the bundle's root `Info.plist`).
|
|
1393
|
+
*/
|
|
1394
|
+
declare function assertIosAppAllowed(allowedApps: string[], app: string): void;
|
|
1395
|
+
/**
|
|
1396
|
+
* Accepted identities for an Android target app. A bare package name matches
|
|
1397
|
+
* itself. An `.apk` path is authorized only by its canonical full path; pass the
|
|
1398
|
+
* resolved package name when validating an APK after `aapt` resolution so
|
|
1399
|
+
* package-ID allowlists can authorize it before install.
|
|
1400
|
+
*/
|
|
1401
|
+
declare function androidAppAllowedIdentities(app: string, resolvedPackage?: string): string[];
|
|
1402
|
+
/**
|
|
1403
|
+
* Android scope guardrail (PROWL-058): mirrors {@link assertTargetAppAllowed} but
|
|
1404
|
+
* resolves identities via {@link androidAppAllowedIdentities} (package name or
|
|
1405
|
+
* canonical APK path) rather than macOS bundle identities.
|
|
1406
|
+
*/
|
|
1407
|
+
declare function assertAndroidAppAllowed(allowedApps: string[], app: string, resolvedPackage?: string): void;
|
|
1408
|
+
|
|
1409
|
+
type AiProvider = "anthropic" | "openai";
|
|
1410
|
+
type AiConfig = {
|
|
1411
|
+
provider: AiProvider;
|
|
1412
|
+
model: string;
|
|
1413
|
+
apiKey: string;
|
|
1414
|
+
/**
|
|
1415
|
+
* API root the request is sent to (no trailing slash, no path). When omitted,
|
|
1416
|
+
* falls back to the provider's public API. Set via `PROWL_AI_BASE_URL` so a
|
|
1417
|
+
* self-hosted gateway or (later) a managed Prowl proxy can slot in without
|
|
1418
|
+
* code changes.
|
|
1419
|
+
*/
|
|
1420
|
+
baseUrl?: string;
|
|
1421
|
+
};
|
|
592
1422
|
|
|
593
1423
|
type StepCallback = (result: StepResult, step: Step, index: number) => void;
|
|
594
1424
|
|
|
@@ -606,6 +1436,10 @@ type RunOptions = {
|
|
|
606
1436
|
junit?: boolean;
|
|
607
1437
|
/** Inject a macOS helper client (tests / a prebuilt binary); defaults to spawning the helper. */
|
|
608
1438
|
macClientFactory?: () => MacHelperClient;
|
|
1439
|
+
/** Inject an Android session factory (tests); defaults to {@link launchAndroidSession}. */
|
|
1440
|
+
androidSessionFactory?: (options: LaunchAndroidOptions) => Promise<AndroidSession>;
|
|
1441
|
+
/** Inject an iOS session factory (tests); defaults to {@link launchIosSession}. */
|
|
1442
|
+
iosSessionFactory?: (options: LaunchIosOptions) => Promise<IosSession>;
|
|
609
1443
|
};
|
|
610
1444
|
declare function runHunt(options: RunOptions): Promise<{
|
|
611
1445
|
result: RunResult;
|
|
@@ -813,6 +1647,36 @@ declare const configSchema: z.ZodObject<{
|
|
|
813
1647
|
}, {
|
|
814
1648
|
app: string;
|
|
815
1649
|
type: "macos";
|
|
1650
|
+
}>, z.ZodObject<{
|
|
1651
|
+
type: z.ZodLiteral<"android">;
|
|
1652
|
+
app: z.ZodString;
|
|
1653
|
+
deviceSerial: z.ZodOptional<z.ZodString>;
|
|
1654
|
+
coldStart: z.ZodOptional<z.ZodBoolean>;
|
|
1655
|
+
}, "strict", z.ZodTypeAny, {
|
|
1656
|
+
app: string;
|
|
1657
|
+
type: "android";
|
|
1658
|
+
deviceSerial?: string | undefined;
|
|
1659
|
+
coldStart?: boolean | undefined;
|
|
1660
|
+
}, {
|
|
1661
|
+
app: string;
|
|
1662
|
+
type: "android";
|
|
1663
|
+
deviceSerial?: string | undefined;
|
|
1664
|
+
coldStart?: boolean | undefined;
|
|
1665
|
+
}>, z.ZodObject<{
|
|
1666
|
+
type: z.ZodLiteral<"ios">;
|
|
1667
|
+
app: z.ZodString;
|
|
1668
|
+
udid: z.ZodOptional<z.ZodString>;
|
|
1669
|
+
coldStart: z.ZodOptional<z.ZodBoolean>;
|
|
1670
|
+
}, "strict", z.ZodTypeAny, {
|
|
1671
|
+
app: string;
|
|
1672
|
+
type: "ios";
|
|
1673
|
+
udid?: string | undefined;
|
|
1674
|
+
coldStart?: boolean | undefined;
|
|
1675
|
+
}, {
|
|
1676
|
+
app: string;
|
|
1677
|
+
type: "ios";
|
|
1678
|
+
udid?: string | undefined;
|
|
1679
|
+
coldStart?: boolean | undefined;
|
|
816
1680
|
}>, z.ZodObject<{
|
|
817
1681
|
type: z.ZodOptional<z.ZodLiteral<"web">>;
|
|
818
1682
|
url: z.ZodString;
|
|
@@ -959,6 +1823,16 @@ declare const configSchema: z.ZodObject<{
|
|
|
959
1823
|
} | {
|
|
960
1824
|
app: string;
|
|
961
1825
|
type: "macos";
|
|
1826
|
+
} | {
|
|
1827
|
+
app: string;
|
|
1828
|
+
type: "android";
|
|
1829
|
+
deviceSerial?: string | undefined;
|
|
1830
|
+
coldStart?: boolean | undefined;
|
|
1831
|
+
} | {
|
|
1832
|
+
app: string;
|
|
1833
|
+
type: "ios";
|
|
1834
|
+
udid?: string | undefined;
|
|
1835
|
+
coldStart?: boolean | undefined;
|
|
962
1836
|
};
|
|
963
1837
|
browser?: {
|
|
964
1838
|
timeout?: number | undefined;
|
|
@@ -1014,6 +1888,16 @@ declare const configSchema: z.ZodObject<{
|
|
|
1014
1888
|
} | {
|
|
1015
1889
|
app: string;
|
|
1016
1890
|
type: "macos";
|
|
1891
|
+
} | {
|
|
1892
|
+
app: string;
|
|
1893
|
+
type: "android";
|
|
1894
|
+
deviceSerial?: string | undefined;
|
|
1895
|
+
coldStart?: boolean | undefined;
|
|
1896
|
+
} | {
|
|
1897
|
+
app: string;
|
|
1898
|
+
type: "ios";
|
|
1899
|
+
udid?: string | undefined;
|
|
1900
|
+
coldStart?: boolean | undefined;
|
|
1017
1901
|
};
|
|
1018
1902
|
browser?: {
|
|
1019
1903
|
timeout?: number | undefined;
|
|
@@ -1212,12 +2096,353 @@ type AnalysisResult = {
|
|
|
1212
2096
|
};
|
|
1213
2097
|
declare function analyzePage(page: DomEvaluator): Promise<AnalysisResult>;
|
|
1214
2098
|
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
2099
|
+
/**
|
|
2100
|
+
* PROWL-055 / ARCH-007 — macOS analyzer.
|
|
2101
|
+
*
|
|
2102
|
+
* The native analog of {@link analyzePage}: dumps a macOS app's interactive
|
|
2103
|
+
* elements with ranked selector candidates so hunt authors don't have to guess
|
|
2104
|
+
* accessibility identifiers. It talks to the same `prowl-macdriver` helper the
|
|
2105
|
+
* runner uses ({@link MacHelperClient}) — reading the AX tree, the window list,
|
|
2106
|
+
* and (read-only) the status-item menu — and shapes the result to mirror the web
|
|
2107
|
+
* analyzer's feel.
|
|
2108
|
+
*
|
|
2109
|
+
* Selector ranking (best → last resort), matching the driver's selector dialect
|
|
2110
|
+
* so the emitted selectors are directly usable in hunts:
|
|
2111
|
+
* id=<AXIdentifier> (best — the native `data-testid`)
|
|
2112
|
+
* label="<exact title/desc>" (exact accessibility label)
|
|
2113
|
+
* role=<role>[name="<name>"] (role + accessible name)
|
|
2114
|
+
* text="<substring>" (last resort — substring match)
|
|
2115
|
+
*
|
|
2116
|
+
* Read-only: the ONLY state-changing interaction is opening the status-item menu
|
|
2117
|
+
* to read its items (their identifiers are gold) and immediately closing it.
|
|
2118
|
+
*/
|
|
2119
|
+
|
|
2120
|
+
/**
|
|
2121
|
+
* The interactive AX roles the analyzer surfaces from the window tree. Menu
|
|
2122
|
+
* items are collected separately via the status-item menu; windows are listed
|
|
2123
|
+
* as navigable surfaces. Tuned from the roles the helper's `tree`/`openMenu`
|
|
2124
|
+
* payloads actually expose for controls.
|
|
2125
|
+
*/
|
|
2126
|
+
declare const INTERACTIVE_ROLES: ReadonlySet<string>;
|
|
2127
|
+
/** Default AX-tree depth requested from the helper (deeper than the run default). */
|
|
2128
|
+
declare const DEFAULT_ANALYZE_TREE_DEPTH = 20;
|
|
2129
|
+
/** A single element the `axInfo` snapshot describes (tree / menu / window node). */
|
|
2130
|
+
type MacAxNode = {
|
|
2131
|
+
role?: string;
|
|
2132
|
+
title?: string;
|
|
2133
|
+
description?: string;
|
|
2134
|
+
value?: string;
|
|
2135
|
+
identifier?: string;
|
|
2136
|
+
enabled?: boolean;
|
|
2137
|
+
children?: MacAxNode[];
|
|
1220
2138
|
};
|
|
2139
|
+
/** An interactive element with ranked selector candidates (best first). */
|
|
2140
|
+
type MacAnalysisElement = {
|
|
2141
|
+
role: string;
|
|
2142
|
+
title?: string;
|
|
2143
|
+
description?: string;
|
|
2144
|
+
value?: string;
|
|
2145
|
+
identifier?: string;
|
|
2146
|
+
enabled?: boolean;
|
|
2147
|
+
/** Where the element was discovered: the app's window tree or the status menu. */
|
|
2148
|
+
source: "window" | "menu";
|
|
2149
|
+
/** Ranked selector candidates, best first. Always at least one entry. */
|
|
2150
|
+
selectors: string[];
|
|
2151
|
+
};
|
|
2152
|
+
/** A top-level window, exposed as a navigable surface with its best selector. */
|
|
2153
|
+
type MacAnalysisWindow = {
|
|
2154
|
+
title?: string;
|
|
2155
|
+
identifier?: string;
|
|
2156
|
+
/** Best selector candidate for the window. */
|
|
2157
|
+
selector: string;
|
|
2158
|
+
};
|
|
2159
|
+
type MacAnalysisResult = {
|
|
2160
|
+
/** Bundle id (or the app reference) of the analyzed app. */
|
|
2161
|
+
app: string;
|
|
2162
|
+
elements: MacAnalysisElement[];
|
|
2163
|
+
windows: MacAnalysisWindow[];
|
|
2164
|
+
menuItems: MacAnalysisElement[];
|
|
2165
|
+
};
|
|
2166
|
+
/**
|
|
2167
|
+
* Ranked selector candidates for an element, best first. Mirrors the driver's
|
|
2168
|
+
* selector dialect (`id=` > `label=` > `role=…[name="…"]` > `text=`). Falls back
|
|
2169
|
+
* to a bare `role=<role>` so every element with a role is at least addressable.
|
|
2170
|
+
*/
|
|
2171
|
+
declare function rankMacSelectors(node: MacAxNode): string[];
|
|
2172
|
+
type AnalyzeMacOptions = {
|
|
2173
|
+
/** Bundle id / app label to report in the result. */
|
|
2174
|
+
app: string;
|
|
2175
|
+
/** AX-tree depth to request from the helper. */
|
|
2176
|
+
treeDepth?: number;
|
|
2177
|
+
/** Timeout (seconds) for opening the status-item menu. */
|
|
2178
|
+
menuTimeoutSeconds?: number;
|
|
2179
|
+
};
|
|
2180
|
+
/**
|
|
2181
|
+
* Analyze an already-launched macOS app through `client`, returning its
|
|
2182
|
+
* interactive elements, windows, and status-menu items with ranked selectors.
|
|
2183
|
+
*
|
|
2184
|
+
* The caller owns the session lifecycle (launch + guardrails + teardown); this
|
|
2185
|
+
* function is read-only apart from opening and immediately closing the
|
|
2186
|
+
* status-item menu to read its contents.
|
|
2187
|
+
*/
|
|
2188
|
+
declare function analyzeMacApp(client: MacHelperClient, options: AnalyzeMacOptions): Promise<MacAnalysisResult>;
|
|
2189
|
+
|
|
2190
|
+
/**
|
|
2191
|
+
* PROWL-061 — a tiny, dependency-free XML parser for on-device UI hierarchies.
|
|
2192
|
+
*
|
|
2193
|
+
* Both native mobile analyzers read an XML page source: Android via the
|
|
2194
|
+
* uiautomator2 agent's `GET /source` (a `<hierarchy>` of `<node>` elements) and
|
|
2195
|
+
* iOS via WebDriverAgent's `GET /source` (a tree of `<XCUIElementType…>`
|
|
2196
|
+
* elements). Both dialects share the same shape — a tree of elements whose data
|
|
2197
|
+
* lives entirely in double-quoted attributes, with no meaningful text between
|
|
2198
|
+
* tags — so one small scanner serves both, keeping with the repo's "no heavy
|
|
2199
|
+
* SDK" ethos (there is no XML parser in our own dependency set, only transitive
|
|
2200
|
+
* ones we must not rely on).
|
|
2201
|
+
*
|
|
2202
|
+
* The scanner is deliberately narrow: it understands element start/end/self-close
|
|
2203
|
+
* tags, quoted attributes (single or double), XML declarations, comments, and the
|
|
2204
|
+
* five predefined entities plus numeric character references. It ignores text
|
|
2205
|
+
* nodes and CDATA (the UI dumps carry none). It never throws on malformed input —
|
|
2206
|
+
* it returns the best-effort root element, or null when there is no element at
|
|
2207
|
+
* all — so a surprising payload degrades to an empty analysis rather than a crash.
|
|
2208
|
+
*/
|
|
2209
|
+
/** A parsed XML element: its tag name, attributes, and child elements. */
|
|
2210
|
+
type XmlElement = {
|
|
2211
|
+
tag: string;
|
|
2212
|
+
attrs: Record<string, string>;
|
|
2213
|
+
children: XmlElement[];
|
|
2214
|
+
};
|
|
2215
|
+
/** Decode the five predefined XML entities and numeric character references. */
|
|
2216
|
+
declare function decodeXmlEntities(value: string): string;
|
|
2217
|
+
/**
|
|
2218
|
+
* Parse an XML document into its root {@link XmlElement} (best-effort). Returns
|
|
2219
|
+
* null when the input contains no element. Malformed markup is tolerated: unknown
|
|
2220
|
+
* constructs are skipped and mismatched end tags simply pop the stack.
|
|
2221
|
+
*/
|
|
2222
|
+
declare function parseXml(input: string): XmlElement | null;
|
|
2223
|
+
|
|
2224
|
+
/**
|
|
2225
|
+
* PROWL-061 — Android analyzer.
|
|
2226
|
+
*
|
|
2227
|
+
* The Android analog of {@link analyzeMacApp}: dumps a running Android app's
|
|
2228
|
+
* interactive elements with ranked selector candidates so hunt authors don't have
|
|
2229
|
+
* to guess resource-ids. It reads the on-device UI hierarchy through the same
|
|
2230
|
+
* uiautomator2 agent the runner uses (`GET /source`, the standard `uiautomator
|
|
2231
|
+
* dump` XML) and shapes the result to mirror the macOS analyzer's feel.
|
|
2232
|
+
*
|
|
2233
|
+
* Selector ranking (best → last resort), matching the Android driver's selector
|
|
2234
|
+
* dialect so the emitted selectors are directly usable in hunts:
|
|
2235
|
+
* id=<resource-id> (best — the native `data-testid`; already
|
|
2236
|
+
* package-qualified in the dump, e.g.
|
|
2237
|
+
* `com.android.settings:id/title`)
|
|
2238
|
+
* label="<content-desc>" (exact content-description)
|
|
2239
|
+
* role=<class>[name="<text>"] (widget class + visible text, substring)
|
|
2240
|
+
* text="<text>" (last resort — visible-text substring)
|
|
2241
|
+
*
|
|
2242
|
+
* Read-only: this never taps, types, or otherwise mutates the app — it only reads
|
|
2243
|
+
* the page source.
|
|
2244
|
+
*/
|
|
2245
|
+
|
|
2246
|
+
/**
|
|
2247
|
+
* Widget classes treated as interactive on their own (in addition to any node
|
|
2248
|
+
* flagged clickable / long-clickable / checkable / scrollable). Tuned to the
|
|
2249
|
+
* common `android.widget` / AndroidX input and control classes; text/containers
|
|
2250
|
+
* without an interactive flag are surfaced only when clickable.
|
|
2251
|
+
*/
|
|
2252
|
+
declare const ANDROID_INTERACTIVE_CLASSES: ReadonlySet<string>;
|
|
2253
|
+
/** A single node in the uiautomator hierarchy (`<node>` element attributes). */
|
|
2254
|
+
type AndroidUiNode = {
|
|
2255
|
+
className?: string;
|
|
2256
|
+
resourceId?: string;
|
|
2257
|
+
contentDesc?: string;
|
|
2258
|
+
text?: string;
|
|
2259
|
+
package?: string;
|
|
2260
|
+
clickable?: boolean;
|
|
2261
|
+
longClickable?: boolean;
|
|
2262
|
+
checkable?: boolean;
|
|
2263
|
+
checked?: boolean;
|
|
2264
|
+
scrollable?: boolean;
|
|
2265
|
+
focusable?: boolean;
|
|
2266
|
+
/** Whether the node currently holds input focus (`:focus`; from the dump's `focused` attr). */
|
|
2267
|
+
focused?: boolean;
|
|
2268
|
+
enabled?: boolean;
|
|
2269
|
+
children: AndroidUiNode[];
|
|
2270
|
+
};
|
|
2271
|
+
/** An interactive element with ranked selector candidates (best first). */
|
|
2272
|
+
type AndroidAnalysisElement = {
|
|
2273
|
+
className: string;
|
|
2274
|
+
resourceId?: string;
|
|
2275
|
+
contentDesc?: string;
|
|
2276
|
+
text?: string;
|
|
2277
|
+
clickable?: boolean;
|
|
2278
|
+
checkable?: boolean;
|
|
2279
|
+
scrollable?: boolean;
|
|
2280
|
+
enabled?: boolean;
|
|
2281
|
+
/** Ranked selector candidates, best first. Always at least one entry. */
|
|
2282
|
+
selectors: string[];
|
|
2283
|
+
};
|
|
2284
|
+
type AndroidAnalysisResult = {
|
|
2285
|
+
/** Package name of the analyzed app. */
|
|
2286
|
+
app: string;
|
|
2287
|
+
elements: AndroidAnalysisElement[];
|
|
2288
|
+
};
|
|
2289
|
+
/** Parse a uiautomator2 `/source` XML dump into a tree of {@link AndroidUiNode}. */
|
|
2290
|
+
declare function parseAndroidHierarchy(xml: string): AndroidUiNode | null;
|
|
2291
|
+
/** Whether a node is worth surfacing as an interactive element. */
|
|
2292
|
+
declare function isAndroidInteractive(node: AndroidUiNode): boolean;
|
|
2293
|
+
/**
|
|
2294
|
+
* Ranked selector candidates for a node, best first, via the shared native
|
|
2295
|
+
* selector engine (PROWL-060). The Android attribute mapping is applied here —
|
|
2296
|
+
* `id=`←resource-id (already package-qualified in the dump, emitted verbatim),
|
|
2297
|
+
* `label=`←content-desc, `role=`←class, name←visible text — then
|
|
2298
|
+
* {@link rankNativeSelectors} imposes the shared `id=` > `label=` >
|
|
2299
|
+
* `role=…[name]` > `text=` order (with a bare `role=` fallback).
|
|
2300
|
+
*/
|
|
2301
|
+
declare function rankAndroidSelectors(node: AndroidUiNode): string[];
|
|
2302
|
+
/**
|
|
2303
|
+
* Project an {@link AndroidUiNode} into the neutral {@link NativeNode} the shared
|
|
2304
|
+
* matcher compares against: `id`←resource-id, `label`←content-desc, `role`←class,
|
|
2305
|
+
* `text=` substring source ← visible text.
|
|
2306
|
+
*/
|
|
2307
|
+
declare function androidNodeToNative(node: AndroidUiNode): NativeNode;
|
|
2308
|
+
/**
|
|
2309
|
+
* Host-side "snapshot-then-match": parse a uiautomator2 `/source` dump and return
|
|
2310
|
+
* every node the Prowl `selector` resolves to, in document order, using Android's
|
|
2311
|
+
* shared dialect semantics. Read-only and device-free. Exposed for tooling and a
|
|
2312
|
+
* future runner/macdriver migration; the runner still matches on-device today.
|
|
2313
|
+
*/
|
|
2314
|
+
declare function matchAndroidSelector(xml: string, selector: string, options?: NativeMatchOptions): AndroidUiNode[];
|
|
2315
|
+
/** The minimal transport the Android analyzer needs: read the UI hierarchy XML. */
|
|
2316
|
+
interface AndroidUiSource {
|
|
2317
|
+
/** Return the current UI hierarchy as uiautomator2 `/source` XML. */
|
|
2318
|
+
source(): Promise<string>;
|
|
2319
|
+
}
|
|
2320
|
+
type AnalyzeAndroidOptions = {
|
|
2321
|
+
/** Package name to report in the result. */
|
|
2322
|
+
app: string;
|
|
2323
|
+
};
|
|
2324
|
+
/**
|
|
2325
|
+
* Analyze an already-launched Android app through `client`, returning its
|
|
2326
|
+
* interactive elements with ranked selectors.
|
|
2327
|
+
*
|
|
2328
|
+
* The caller owns the session lifecycle (launch + guardrails + teardown); this
|
|
2329
|
+
* function is strictly read-only — it only reads the page source.
|
|
2330
|
+
*/
|
|
2331
|
+
declare function analyzeAndroidApp(client: AndroidUiSource, options: AnalyzeAndroidOptions): Promise<AndroidAnalysisResult>;
|
|
2332
|
+
|
|
2333
|
+
/**
|
|
2334
|
+
* PROWL-061 — iOS analyzer.
|
|
2335
|
+
*
|
|
2336
|
+
* The iOS analog of {@link analyzeMacApp}: dumps a running iOS app's interactive
|
|
2337
|
+
* elements (and its windows) with ranked selector candidates so hunt authors
|
|
2338
|
+
* don't have to guess accessibility identifiers. It reads the on-simulator UI
|
|
2339
|
+
* hierarchy through the same WebDriverAgent the runner uses (`GET /source`, WDA's
|
|
2340
|
+
* XML page source of `<XCUIElementType…>` elements) and shapes the result to
|
|
2341
|
+
* mirror the macOS analyzer's feel.
|
|
2342
|
+
*
|
|
2343
|
+
* Selector ranking (best → last resort), matching the iOS driver's selector
|
|
2344
|
+
* dialect so the emitted selectors are directly usable in hunts:
|
|
2345
|
+
* id=<accessibility id> (best — the native `data-testid`)
|
|
2346
|
+
* label="<label>" (exact accessibility label)
|
|
2347
|
+
* role=<Type>[name="<text>"] (element type + visible text, substring)
|
|
2348
|
+
* text="<text>" (last resort — label/value substring)
|
|
2349
|
+
*
|
|
2350
|
+
* Identifier caveat: WDA's page source exposes a single `name` attribute that is
|
|
2351
|
+
* the element's `accessibilityIdentifier` when one is set, otherwise its label.
|
|
2352
|
+
* We therefore rank `id=` only when `name` differs from `label`, so the analyzer
|
|
2353
|
+
* does not recommend label-shaped ids. Host-side matching still follows WDA's
|
|
2354
|
+
* `accessibility id` strategy and resolves `id=` against `name`.
|
|
2355
|
+
*
|
|
2356
|
+
* Read-only: this never taps, types, or otherwise mutates the app — it only reads
|
|
2357
|
+
* the page source.
|
|
2358
|
+
*/
|
|
2359
|
+
|
|
2360
|
+
/**
|
|
2361
|
+
* Element types treated as interactive. Tuned to the common `XCUIElementType…`
|
|
2362
|
+
* controls; static text and layout containers are intentionally excluded.
|
|
2363
|
+
*/
|
|
2364
|
+
declare const IOS_INTERACTIVE_TYPES: ReadonlySet<string>;
|
|
2365
|
+
/** The element type that represents a navigable window/screen surface. */
|
|
2366
|
+
declare const IOS_WINDOW_TYPE = "XCUIElementTypeWindow";
|
|
2367
|
+
/** A single node in the WDA hierarchy (`<XCUIElementType…>` element attributes). */
|
|
2368
|
+
type IosUiNode = {
|
|
2369
|
+
type?: string;
|
|
2370
|
+
name?: string;
|
|
2371
|
+
label?: string;
|
|
2372
|
+
value?: string;
|
|
2373
|
+
enabled?: boolean;
|
|
2374
|
+
visible?: boolean;
|
|
2375
|
+
children: IosUiNode[];
|
|
2376
|
+
};
|
|
2377
|
+
/** An interactive element with ranked selector candidates (best first). */
|
|
2378
|
+
type IosAnalysisElement = {
|
|
2379
|
+
type: string;
|
|
2380
|
+
name?: string;
|
|
2381
|
+
label?: string;
|
|
2382
|
+
value?: string;
|
|
2383
|
+
enabled?: boolean;
|
|
2384
|
+
visible?: boolean;
|
|
2385
|
+
/** Ranked selector candidates, best first. Always at least one entry. */
|
|
2386
|
+
selectors: string[];
|
|
2387
|
+
};
|
|
2388
|
+
/** A top-level window, exposed as a navigable surface with its best selector. */
|
|
2389
|
+
type IosAnalysisWindow = {
|
|
2390
|
+
name?: string;
|
|
2391
|
+
label?: string;
|
|
2392
|
+
/** Best selector candidate for the window. */
|
|
2393
|
+
selector: string;
|
|
2394
|
+
};
|
|
2395
|
+
type IosAnalysisResult = {
|
|
2396
|
+
/** Bundle id of the analyzed app. */
|
|
2397
|
+
app: string;
|
|
2398
|
+
elements: IosAnalysisElement[];
|
|
2399
|
+
windows: IosAnalysisWindow[];
|
|
2400
|
+
};
|
|
2401
|
+
/** Parse a WDA `/source` XML dump into a tree of {@link IosUiNode}. */
|
|
2402
|
+
declare function parseIosHierarchy(xml: string): IosUiNode | null;
|
|
2403
|
+
/**
|
|
2404
|
+
* Ranked selector candidates for a node, best first, via the shared native
|
|
2405
|
+
* selector engine (PROWL-060). The iOS attribute mapping is applied here — `id=`←
|
|
2406
|
+
* accessibility id (the `name` attribute, but only when it differs from the label;
|
|
2407
|
+
* see the caveat above), `label=`←label, `role=`←the short element type, name←
|
|
2408
|
+
* `label ?? value` — then {@link rankNativeSelectors} imposes the shared `id=` >
|
|
2409
|
+
* `label=` > `role=…[name]` > `text=` order (with a bare `role=` fallback).
|
|
2410
|
+
*/
|
|
2411
|
+
declare function rankIosSelectors(node: IosUiNode): string[];
|
|
2412
|
+
/**
|
|
2413
|
+
* Project an {@link IosUiNode} into the neutral {@link NativeNode} the shared
|
|
2414
|
+
* matcher compares against: `id`←name (matching WDA's `accessibility id`
|
|
2415
|
+
* strategy even when it equals the label), `label`←label, `role`←the full
|
|
2416
|
+
* element type (the dialect normalizes a `Button` shorthand against it), and both
|
|
2417
|
+
* label and value as `text=` substring sources.
|
|
2418
|
+
*/
|
|
2419
|
+
declare function iosNodeToNative(node: IosUiNode): NativeNode;
|
|
2420
|
+
/**
|
|
2421
|
+
* Host-side "snapshot-then-match": parse a WebDriverAgent `/source` dump and
|
|
2422
|
+
* return every node the Prowl `selector` resolves to, in document order, using
|
|
2423
|
+
* iOS's shared dialect semantics. Read-only and device-free. Exposed for tooling
|
|
2424
|
+
* and a future runner/macdriver migration; the runner still matches on-device.
|
|
2425
|
+
*/
|
|
2426
|
+
declare function matchIosSelector(xml: string, selector: string): IosUiNode[];
|
|
2427
|
+
/** Whether a node is worth surfacing as an interactive element. */
|
|
2428
|
+
declare function isIosInteractive(node: IosUiNode): boolean;
|
|
2429
|
+
/** The minimal transport the iOS analyzer needs: read the UI hierarchy XML. */
|
|
2430
|
+
interface IosUiSource {
|
|
2431
|
+
/** Return the current UI hierarchy as WebDriverAgent `/source` XML. */
|
|
2432
|
+
source(): Promise<string>;
|
|
2433
|
+
}
|
|
2434
|
+
type AnalyzeIosOptions = {
|
|
2435
|
+
/** Bundle id to report in the result. */
|
|
2436
|
+
app: string;
|
|
2437
|
+
};
|
|
2438
|
+
/**
|
|
2439
|
+
* Analyze an already-launched iOS app through `client`, returning its interactive
|
|
2440
|
+
* elements and windows with ranked selectors.
|
|
2441
|
+
*
|
|
2442
|
+
* The caller owns the session lifecycle (launch + guardrails + teardown); this
|
|
2443
|
+
* function is strictly read-only — it only reads the page source.
|
|
2444
|
+
*/
|
|
2445
|
+
declare function analyzeIosApp(client: IosUiSource, options: AnalyzeIosOptions): Promise<IosAnalysisResult>;
|
|
1221
2446
|
|
|
1222
2447
|
type GenerateOptions = {
|
|
1223
2448
|
url?: string;
|
|
@@ -1229,4 +2454,4 @@ type GenerateOptions = {
|
|
|
1229
2454
|
};
|
|
1230
2455
|
declare function generateHunt(options: GenerateOptions): Promise<string>;
|
|
1231
2456
|
|
|
1232
|
-
export { type AnalysisResult, type AssertScreenshotStep, type Assertion, type AssertionResult, type BrowserChannel, type BrowserEngine, type BugFailure, type BugLogSummary, type CiFailureCluster, type CiFlakyHunt, type CiHuntResult, type CiResult, type CiStatus, type Config, DEFAULT_FLAKY_THRESHOLD, DEFAULT_REQUEST_TIMEOUT_MS, type EvalScriptStep, type FailureCluster, type FlakyScore, type GenerateOptions, type HealResult, type HistoryEntry, type HistoryFile, type Hunt, type IfStep, type LaunchMacOptions, type MacDriverOptions, type MacHelperClient, type MacQuery, type MacSession, type MacosTarget, type MockRouteStep, type PageElement, type PageForm, type PageLink, type RankFlakyOptions, type ReliabilityConfig, type RepeatStep, type RunArtifacts, type RunOptions, type RunResult, type RunScriptStep, type RunSuiteHooks, type RunSuiteOptions, type RunSuiteResult, SpawnMacHelperClient, type SpawnMacHelperOptions, type Step, type StepResult, type Target, type UnmockRouteStep, type UpdateBacklogOptions, type Viewport, WEB_ONLY_STEP_TYPES, type WebTarget, analyzePage, assertStepsSupportedByTarget, assertTargetAppAllowed, buildHealCandidates, closeMacSession, clusterFailures, computeFlakeScore, configSchema, createMacDriver, extractFailures, extractSelectorIntent, generateHunt, healSelector, huntSchema, interpolateHunt, launchMacSession, listHunts, loadConfig, loadHunt, loadHuntMeta, loadHuntTags, macdriverBuildInstructions, parseMacSelector, rankFlaky, readHistory, readHuntHistory, resolveHelperBinary, runHunt, runSuite, stepSchema, updateBacklogFromSuite, webOnlyReason };
|
|
2457
|
+
export { ANDROID_INTERACTIVE_CLASSES, ANDROID_KEYCODES, ANDROID_MATCH_DIALECT, type AdbDevice, type AdbResult, type AdbRunner, type AdbSpawner, type AgentApks, type AgentConnector, type AnalysisResult, type AnalyzeAndroidOptions, type AnalyzeIosOptions, type AnalyzeMacOptions, type AndroidAgentClient, type AndroidAnalysisElement, type AndroidAnalysisResult, type AndroidDriverOptions, type AndroidLocator, type AndroidQuery, type AndroidSession, type AndroidTarget, type AndroidUiNode, type AndroidUiSource, type AssertScreenshotStep, type Assertion, type AssertionResult, type BrowserChannel, type BrowserEngine, type BugFailure, type BugLogSummary, type CiFailureCluster, type CiFlakyHunt, type CiHuntResult, type CiResult, type CiStatus, type Config, DEFAULT_AGENT_REQUEST_TIMEOUT_MS, DEFAULT_ANALYZE_TREE_DEPTH, DEFAULT_FLAKY_THRESHOLD, DEFAULT_REQUEST_TIMEOUT_MS, DEFAULT_SIMULATOR_LOCK_ROOT, DEFAULT_WDA_REQUEST_TIMEOUT_MS, type EvalScriptStep, type FailureCluster, type FetchLike$1 as FetchLike, type FlakyScore, type GenerateOptions, type HealResult, type HistoryEntry, type HistoryFile, type Hunt, INTERACTIVE_ROLES, IOS_INTERACTIVE_TYPES, IOS_MATCH_DIALECT, IOS_PRESS_KEYS, IOS_WINDOW_TYPE, type IfStep, type IosAgentClient, type IosAgentConnector, type IosAnalysisElement, type IosAnalysisResult, type IosAnalysisWindow, type IosDriverOptions, type IosLocator, type IosQuery, type IosSession, type IosTarget, type IosUiNode, type IosUiSource, type LaunchAndroidOptions, type LaunchIosOptions, type LaunchMacOptions, MACOS_MATCH_DIALECT, type MacAnalysisElement, type MacAnalysisResult, type MacAnalysisWindow, type MacAxNode, type MacDriverOptions, type MacHelperClient, type MacQuery, type MacSession, type MacosTarget, type MockRouteStep, NATIVE_ATTRIBUTE_MAP, type NativeMatchDialect, type NativeMatchOptions, type NativeNode, type NativePlatform, type NativeRankFields, type NativeSelector, type NativeSelectorKind, type PageElement, type PageForm, type PageLink, type RankFlakyOptions, type ReliabilityConfig, type RepeatStep, type ReserveSimulatorOptions, type ResolveWdaTestRunOptions, type RunArtifacts, type RunOptions, type RunResult, type RunScriptStep, type RunSuiteHooks, type RunSuiteOptions, type RunSuiteResult, type SelectorKindMapping, type SimDevice, type SimctlResult, type SimctlRunner, type SimulatorReservation, SpawnMacHelperClient, type SpawnMacHelperOptions, type Step, type StepResult, type Target, UIA2_REMOTE_PORT, Uia2HttpError, Uia2Transport, type Uia2TransportOptions, type UnmockRouteStep, type UpdateBacklogOptions, type Viewport, WDA_RUNNER_BUNDLE_ID, WDA_USE_PORT_ENV, WEB_ONLY_STEP_TYPES, WdaHttpError, type WdaTestRunPreparer, WdaTransport, type WdaTransportOptions, type WebTarget, type XmlElement, analyzeAndroidApp, analyzeIosApp, analyzeMacApp, analyzePage, androidAppAllowedIdentities, androidNodeToNative, androidQueryToLocator, assertAndroidAppAllowed, assertIosAppAllowed, assertStepsSupportedByTarget, assertTargetAppAllowed, bootedDevices, bootedSimulators, buildHealCandidates, closeAndroidSession, closeIosSession, closeMacSession, clusterFailures, computeFlakeScore, configSchema, createAndroidDriver, createIosDriver, createMacDriver, createUia2AgentClient, createUia2Session, createWdaAgentClient, createWdaSession, decodeXmlEntities, defaultAgentConnector, defaultIosAgentConnector, defaultWdaTestRunPreparer, escapePredicateArg, escapeUiSelectorArg, extractElementId, extractFailures, extractSelectorIntent, findFreePort, generateHunt, healSelector, huntSchema, injectUsePortIntoXctestrun, interpolateHunt, iosAppAllowedIdentities, iosNodeToNative, iosQueryToLocator, isAndroidInteractive, isIosInteractive, launchAndroidSession, launchIosSession, launchMacSession, listDevices, listHunts, listSimulators, loadConfig, loadHunt, loadHuntMeta, loadHuntTags, macdriverBuildInstructions, matchAndroidSelector, matchIosSelector, matchNativeTree, nodeMatchesSelector, normalizeXcuiClassName, parseAaptPackage, parseAdbDevices, parseAndroidHierarchy, parseAndroidSelector, parseForwardPort, parseIosHierarchy, parseIosSelector, parseMacSelector, parseNativeSelector, parseSimctlDevices, parseSnapshot, parseXcodeVersion, parseXml, qualifyResourceId, quoteSelectorValue, rankAndroidSelectors, rankFlaky, rankIosSelectors, rankMacSelectors, rankNativeSelectors, readHistory, readHuntHistory, readIosBundleIdentifier, reserveSimulatorUdid, resolveAgentApks, resolveHelperBinary, resolveWdaProject, resolveWdaTestRun, runHunt, runSuite, selectDeviceSerial, selectSimulatorUdid, shortIosType, stepSchema, unquoteSelectorValue, unwrapAndroidTextSelector, unwrapIosTextSelector, unwrapNativeTextSelector, updateBacklogFromSuite, waitForAgentReady, waitForWdaReady, wdaCacheDir, wdaTestRunArgs, webOnlyReason };
|