@goliapkg/smix 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +193 -0
- package/dist/A11yNode.d.ts +36 -0
- package/dist/A11yNode.d.ts.map +1 -0
- package/dist/A11yNode.js +30 -0
- package/dist/A11yNode.js.map +1 -0
- package/dist/App.d.ts +57 -0
- package/dist/App.d.ts.map +1 -0
- package/dist/App.js +123 -0
- package/dist/App.js.map +1 -0
- package/dist/ExpectationFailure.d.ts +30 -0
- package/dist/ExpectationFailure.d.ts.map +1 -0
- package/dist/ExpectationFailure.js +47 -0
- package/dist/ExpectationFailure.js.map +1 -0
- package/dist/HttpRunner.d.ts +61 -0
- package/dist/HttpRunner.d.ts.map +1 -0
- package/dist/HttpRunner.js +148 -0
- package/dist/HttpRunner.js.map +1 -0
- package/dist/Locator.d.ts +60 -0
- package/dist/Locator.d.ts.map +1 -0
- package/dist/Locator.js +177 -0
- package/dist/Locator.js.map +1 -0
- package/dist/Pattern.d.ts +21 -0
- package/dist/Pattern.d.ts.map +1 -0
- package/dist/Pattern.js +44 -0
- package/dist/Pattern.js.map +1 -0
- package/dist/Selector.d.ts +96 -0
- package/dist/Selector.d.ts.map +1 -0
- package/dist/Selector.js +276 -0
- package/dist/Selector.js.map +1 -0
- package/dist/SelectorResolver.d.ts +41 -0
- package/dist/SelectorResolver.d.ts.map +1 -0
- package/dist/SelectorResolver.js +55 -0
- package/dist/SelectorResolver.js.map +1 -0
- package/dist/SimRuntime.d.ts +81 -0
- package/dist/SimRuntime.d.ts.map +1 -0
- package/dist/SimRuntime.js +91 -0
- package/dist/SimRuntime.js.map +1 -0
- package/dist/Smix.d.ts +22 -0
- package/dist/Smix.d.ts.map +1 -0
- package/dist/Smix.js +35 -0
- package/dist/Smix.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
- package/src/A11yNode.ts +62 -0
- package/src/App.ts +153 -0
- package/src/ExpectationFailure.ts +67 -0
- package/src/HttpRunner.ts +185 -0
- package/src/Locator.ts +190 -0
- package/src/Pattern.ts +51 -0
- package/src/Selector.ts +296 -0
- package/src/SelectorResolver.ts +76 -0
- package/src/SimRuntime.ts +145 -0
- package/src/Smix.ts +49 -0
- package/src/__tests__/AppFullApi.test.ts +168 -0
- package/src/__tests__/AppTapMock.test.ts +122 -0
- package/src/__tests__/HttpRunner.test.ts +121 -0
- package/src/__tests__/Locator.test.ts +128 -0
- package/src/__tests__/LocatorToHave.test.ts +118 -0
- package/src/__tests__/MvpApiShape.test.ts +117 -0
- package/src/__tests__/SelectorFullSchema.test.ts +161 -0
- package/src/index.ts +76 -0
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# @smix/rn
|
|
2
|
+
|
|
3
|
+
> **Status (2026-06-30)**: v7 initiative ✅ closed — TS SDK API surface complete,
|
|
4
|
+
> 61 vitest unit tests + 4 demo flows running end-to-end PASS + 24-fixture
|
|
5
|
+
> cross-binary harness × 3 backend byte-identical (Rust + Swift + TS).
|
|
6
|
+
>
|
|
7
|
+
> Currently `0.1.0-pre.1` (not yet published to npm). **Public ship gated by
|
|
8
|
+
> user actions** per [docs/ai-guide/v7-release-gpg-infra.md](../../docs/ai-guide/v7-release-gpg-infra.md):
|
|
9
|
+
> `@smix` npm scope ownership + smix-runner-server Swift handler injection
|
|
10
|
+
> (v8 backlog).
|
|
11
|
+
>
|
|
12
|
+
> See also [v7 full initiative wrap](../../docs/plan-history/v7-full-initiative-wrap.md)
|
|
13
|
+
> for the 10-cycle outcome summary.
|
|
14
|
+
|
|
15
|
+
Playwright-style iOS Simulator + Android emulator UI automation SDK for
|
|
16
|
+
TypeScript / React Native / Expo / Detox-compatible test runners. v7
|
|
17
|
+
initiative brings smix's Rust-core selector resolver to TS via HTTP
|
|
18
|
+
client(production) or in-process mock(unit tests),packaged as ESM Node
|
|
19
|
+
20+ module。
|
|
20
|
+
|
|
21
|
+
## Installation(post v0.1.0 ship)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Once v0.1.0 published:
|
|
25
|
+
bun add -D @smix/rn
|
|
26
|
+
# OR
|
|
27
|
+
npm install -D @smix/rn
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
For test target / Detox-equiv consumer. **Iron rule (§9 #13)**: this
|
|
31
|
+
package ships only to `devDependencies` — never bundled into a
|
|
32
|
+
production RN/Expo app via metro/expo tree-shaking。
|
|
33
|
+
|
|
34
|
+
## Quick start(MockSimRuntime — unit tests)
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { Smix, Selector, literal, MockSimRuntime, MockSelectorResolver, bundleId } from '@smix/rn'
|
|
38
|
+
|
|
39
|
+
const runtime = new MockSimRuntime({ snapshotResult: /* A11yNode tree */ })
|
|
40
|
+
const resolver = new MockSelectorResolver()
|
|
41
|
+
resolver.registerHit('{"id":"btn-login"}', 'btn-login')
|
|
42
|
+
|
|
43
|
+
const app = await Smix.launchApp(bundleId('com.example.MyApp'), runtime, resolver.resolve)
|
|
44
|
+
await app.tap(Selector.id('btn-login'))
|
|
45
|
+
await app.fill(Selector.id('input-username'), 'alice')
|
|
46
|
+
|
|
47
|
+
const welcome = app.find(Selector.text(literal('Welcome back')))
|
|
48
|
+
await welcome.toBeVisible({ timeoutMs: 5_000 })
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Production: HTTP runtime
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { Smix, Selector, bundleId, HttpSimRuntime } from '@smix/rn'
|
|
55
|
+
|
|
56
|
+
// Connects to smix-runner-server running on host(see swift-bridge/SmixRunnerCore)
|
|
57
|
+
const runtime = new HttpSimRuntime('http://localhost:14101')
|
|
58
|
+
|
|
59
|
+
const app = await Smix.launchApp(bundleId('com.example.MyApp'), runtime, runtime.resolver, runtime.labelsResolver)
|
|
60
|
+
await app.tap(Selector.id('btn-login').below(Selector.text(literal('Sign In'))).nth(0))
|
|
61
|
+
await app.find(Selector.role('button')).toHaveCount(3, { timeoutMs: 5_000 })
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
HTTP endpoints documented in [`src/HttpRunner.ts`](src/HttpRunner.ts) +
|
|
65
|
+
[Swift server route handler](../../swift-bridge/Sources/SmixRunnerCore/SmixRunnerServer.swift).
|
|
66
|
+
|
|
67
|
+
## API surface(v7 complete)
|
|
68
|
+
|
|
69
|
+
### Selector
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// Base discriminators(untagged JSON wire shape — matches Rust smix-selector)
|
|
73
|
+
Selector.id('btn-login')
|
|
74
|
+
Selector.text(literal('Sign In'))
|
|
75
|
+
Selector.text(regex('^Sub', 'i'))
|
|
76
|
+
Selector.label('Settings')
|
|
77
|
+
Selector.role('button')
|
|
78
|
+
Selector.role('button', literal('Submit'))
|
|
79
|
+
Selector.focused()
|
|
80
|
+
Selector.anchor({ below: Selector.text(literal('Total')) }, { nth: 0 })
|
|
81
|
+
Selector.localizedText({ en: 'Submit', ja: '送信' })
|
|
82
|
+
|
|
83
|
+
// Fluent modifier chaining(returns new Selector with Modifiers updated)
|
|
84
|
+
Selector.id('btn').below(Selector.text(literal('Address')))
|
|
85
|
+
Selector.label('Edit').nth(0)
|
|
86
|
+
Selector.text(literal('Item')).above(Selector.text(literal('Footer'))).first()
|
|
87
|
+
Selector.role('button').near(Selector.text(literal('Confirm'))).ancestor(Selector.role('dialog'))
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Wire JSON(untagged + flatten — byte-identical to Rust smix-selector):
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{"id": "btn-login", "below": {"text": "Address"}, "nth": 0}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### App(act)
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
await app.tap(selector: Selector, opts?: { timeoutMs?: number })
|
|
100
|
+
await app.fill(selector: Selector, text: string)
|
|
101
|
+
await app.pressKey(key: KeyName) // 'return' | 'delete' | 'space' | 'tab' | 'escape' | 'enter'
|
|
102
|
+
await app.swipe(direction: SwipeDirection) // 'up' | 'down' | 'left' | 'right'
|
|
103
|
+
await app.tapAtCoord(nx: number, ny: number) // 0..1, throws if out-of-range
|
|
104
|
+
await app.terminate()
|
|
105
|
+
await app.relaunch()
|
|
106
|
+
await app.launchFresh({ clearState?: boolean, clearKeychain?: boolean, appPath?: string })
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### App(sense)
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const png: Uint8Array = await app.screenshot()
|
|
113
|
+
const tree: A11yNode = await app.tree()
|
|
114
|
+
const popups: A11yNode[] = await app.systemPopups()
|
|
115
|
+
await app.openUrl('myapp://deep/link')
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Locator(assertions)
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
const loc = app.find(Selector.text(literal('Welcome')))
|
|
122
|
+
await loc.toBeVisible({ timeoutMs: 5_000 }) // polls 250ms
|
|
123
|
+
await loc.toContainText('alice', { timeoutMs: 5_000 }) // substring
|
|
124
|
+
await loc.toHaveLabel('Sign In', { timeoutMs: 5_000 }) // strict equal
|
|
125
|
+
await loc.toHaveCount(3, { timeoutMs: 5_000 }) // exact match count
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### ExpectationFailure(AI-readable JSON)
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
try {
|
|
132
|
+
await app.tap(Selector.id('btn-missing'))
|
|
133
|
+
} catch (e) {
|
|
134
|
+
if (e instanceof ExpectationFailure) {
|
|
135
|
+
e.code // 'notFound' | 'ambiguous' | 'notInteractable' | 'timeout' | 'wrongState' | 'unknown'
|
|
136
|
+
e.message // human-readable
|
|
137
|
+
e.selectorJson // original Selector encoded as JSON
|
|
138
|
+
e.visibleElements // A11yNode[] — first 20 nodes from current tree
|
|
139
|
+
e.suggestions // string[] — ['check accessibilityIdentifier...', ...]
|
|
140
|
+
e.toJson() // single-line sorted-keys JSON for AI agent consumption
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Demos
|
|
146
|
+
|
|
147
|
+
Run any of the 4 realistic e2e flows in [`examples/demo-app/`](examples/demo-app):
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
cd examples/demo-app
|
|
151
|
+
bun login-flow.ts # login + welcome assertion
|
|
152
|
+
bun form-validation-flow.ts # 3-cycle form validation + inline errors
|
|
153
|
+
bun list-scroll-flow.ts # scrollable list + toHaveCount semantics
|
|
154
|
+
bun multi-screen-nav-flow.ts # nav + tapAtCoord + openUrl + relaunch
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
See [`examples/demo-app/COVERAGE.md`](examples/demo-app/COVERAGE.md) for the
|
|
158
|
+
SDK surface coverage matrix across demos.
|
|
159
|
+
|
|
160
|
+
## Conformance
|
|
161
|
+
|
|
162
|
+
Cross-binary harness verifies SDK output byte-identical to Rust:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
bash ../../scripts/sdk/run-cross-binary-harness.sh
|
|
166
|
+
# Summary: 24 / 24 fixtures byte-identical (Rust + Swift + TS)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Iron rule §9 #13
|
|
170
|
+
|
|
171
|
+
`@smix/rn` is **never** bundled into a production RN/Expo app. It lives
|
|
172
|
+
in `devDependencies` only — metro/expo's tree-shaking guarantees the
|
|
173
|
+
production bundle excludes it. The `HttpSimRuntime` connects to a
|
|
174
|
+
smix-runner-server running on the host(via 127.0.0.1:port loopback),
|
|
175
|
+
not in-process。
|
|
176
|
+
|
|
177
|
+
## Architecture
|
|
178
|
+
|
|
179
|
+
Per [docs/plan-cold/v7-sdk-design.md](../../docs/plan-cold/v7-sdk-design.md):
|
|
180
|
+
|
|
181
|
+
- **Rust stone core**(crates/smix-{selector,selector-resolver,screen,error}):
|
|
182
|
+
selector resolution + AI-readable failure types,shared across all
|
|
183
|
+
4 language SDKs。
|
|
184
|
+
- **HTTP runtime**(`src/HttpRunner.ts`): wraps fetch to smix-runner-server
|
|
185
|
+
on the host。Production-default。
|
|
186
|
+
- **Mock runtime**(`src/SimRuntime.ts MockSimRuntime`): in-memory tree +
|
|
187
|
+
event log。Used by vitest unit tests + 4 demo flows。
|
|
188
|
+
- **SDK facade**(`src/{Smix,App,Selector,Modifiers,Locator,ExpectationFailure}.ts`):
|
|
189
|
+
Playwright-style ergonomic surface,class-based fluent chaining。
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
Apache-2.0 OR MIT(dual,at your option)。
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface Rect {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
w: number;
|
|
5
|
+
h: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function rectCenter(r: Rect): {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
};
|
|
11
|
+
export type A11yRole = 'button' | 'link' | 'textField' | 'secureTextField' | 'searchField' | 'switch' | 'toggle' | 'checkBox' | 'radio' | 'image' | 'staticText' | 'tab' | 'tabBar' | 'navigationBar' | 'cell' | 'alert' | 'dialog' | 'slider' | 'progressBar' | 'picker' | 'menu' | 'menuItem' | 'scrollView' | 'segmentedControl' | 'table' | 'collectionView' | 'webView' | 'keyboard';
|
|
12
|
+
export interface A11yNode {
|
|
13
|
+
rawType: string;
|
|
14
|
+
role?: A11yRole | undefined;
|
|
15
|
+
identifier?: string | undefined;
|
|
16
|
+
label?: string | undefined;
|
|
17
|
+
title?: string | undefined;
|
|
18
|
+
placeholderValue?: string | undefined;
|
|
19
|
+
value?: string | undefined;
|
|
20
|
+
text?: string | undefined;
|
|
21
|
+
bounds: Rect;
|
|
22
|
+
enabled?: boolean | undefined;
|
|
23
|
+
selected?: boolean | undefined;
|
|
24
|
+
hasFocus?: boolean | undefined;
|
|
25
|
+
visible?: boolean | undefined;
|
|
26
|
+
children?: A11yNode[] | undefined;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Recursively find the first node whose [identifier] matches [id].
|
|
30
|
+
*/
|
|
31
|
+
export declare function findById(node: A11yNode, id: string): A11yNode | null;
|
|
32
|
+
/**
|
|
33
|
+
* DFS pre-order flatten — self + all descendants in a single list.
|
|
34
|
+
*/
|
|
35
|
+
export declare function flatten(node: A11yNode): A11yNode[];
|
|
36
|
+
//# sourceMappingURL=A11yNode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"A11yNode.d.ts","sourceRoot":"","sources":["../src/A11yNode.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,IAAI;IACnB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAE5D;AAED,MAAM,MAAM,QAAQ,GAChB,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,iBAAiB,GAAG,aAAa,GACnE,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,YAAY,GACnE,KAAK,GAAG,QAAQ,GAAG,eAAe,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAChE,QAAQ,GAAG,aAAa,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,GACzD,YAAY,GAAG,kBAAkB,GAAG,OAAO,GAAG,gBAAgB,GAC9D,SAAS,GAAG,UAAU,CAAA;AAE1B,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,MAAM,EAAE,IAAI,CAAA;IACZ,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC9B,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC9B,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,QAAQ,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAA;CAClC;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAOpE;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,EAAE,CAMlD"}
|
package/dist/A11yNode.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// v7.5 c1 — A11yNode + Rect mirror Rust smix-screen A11yNode +
|
|
2
|
+
// Swift v7.2 + Kotlin v7.4 A11yNode. CamelCase wire keys match
|
|
3
|
+
// Rust `#[serde(rename_all = "camelCase")]`.
|
|
4
|
+
export function rectCenter(r) {
|
|
5
|
+
return { x: r.x + r.w / 2, y: r.y + r.h / 2 };
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Recursively find the first node whose [identifier] matches [id].
|
|
9
|
+
*/
|
|
10
|
+
export function findById(node, id) {
|
|
11
|
+
if (node.identifier === id)
|
|
12
|
+
return node;
|
|
13
|
+
for (const child of node.children ?? []) {
|
|
14
|
+
const hit = findById(child, id);
|
|
15
|
+
if (hit !== null)
|
|
16
|
+
return hit;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* DFS pre-order flatten — self + all descendants in a single list.
|
|
22
|
+
*/
|
|
23
|
+
export function flatten(node) {
|
|
24
|
+
const out = [node];
|
|
25
|
+
for (const child of node.children ?? []) {
|
|
26
|
+
out.push(...flatten(child));
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=A11yNode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"A11yNode.js","sourceRoot":"","sources":["../src/A11yNode.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAC/D,6CAA6C;AAS7C,MAAM,UAAU,UAAU,CAAC,CAAO;IAChC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAA;AAC/C,CAAC;AA2BD;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAc,EAAE,EAAU;IACjD,IAAI,IAAI,CAAC,UAAU,KAAK,EAAE;QAAE,OAAO,IAAI,CAAA;IACvC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;QAC/B,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO,GAAG,CAAA;IAC9B,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,IAAc;IACpC,MAAM,GAAG,GAAe,CAAC,IAAI,CAAC,CAAA;IAC9B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;QACxC,GAAG,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;IAC7B,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
|
package/dist/App.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type A11yNode } from './A11yNode.js';
|
|
2
|
+
import { Locator, SmixNotImplementedError } from './Locator.js';
|
|
3
|
+
import { type Selector } from './Selector.js';
|
|
4
|
+
import { type KeyName, type SmixSimRuntime, type SwipeDirection } from './SimRuntime.js';
|
|
5
|
+
import type { LabelsResolver, SelectorResolver } from './SelectorResolver.js';
|
|
6
|
+
/**
|
|
7
|
+
* A handle to a running app on the simulator / emulator.
|
|
8
|
+
*/
|
|
9
|
+
export declare class App {
|
|
10
|
+
readonly bundleId: string;
|
|
11
|
+
readonly runtime: SmixSimRuntime;
|
|
12
|
+
readonly resolver: SelectorResolver;
|
|
13
|
+
/**
|
|
14
|
+
* v7.6 c1 — wraps Rust `resolve_selector_labels` (each match's
|
|
15
|
+
* `.label`). Used by Locator.toHaveCount + toHaveLabel. Defaults
|
|
16
|
+
* to a stub that throws — tests + production should pass a real
|
|
17
|
+
* impl (HttpSimRuntime.labelsResolver or MockLabelsResolver.resolve).
|
|
18
|
+
*/
|
|
19
|
+
readonly labelsResolver: LabelsResolver;
|
|
20
|
+
constructor(bundleId: string, runtime: SmixSimRuntime, resolver: SelectorResolver,
|
|
21
|
+
/**
|
|
22
|
+
* v7.6 c1 — wraps Rust `resolve_selector_labels` (each match's
|
|
23
|
+
* `.label`). Used by Locator.toHaveCount + toHaveLabel. Defaults
|
|
24
|
+
* to a stub that throws — tests + production should pass a real
|
|
25
|
+
* impl (HttpSimRuntime.labelsResolver or MockLabelsResolver.resolve).
|
|
26
|
+
*/
|
|
27
|
+
labelsResolver?: LabelsResolver);
|
|
28
|
+
tap(selector: Selector, _opts?: {
|
|
29
|
+
timeoutMs?: number;
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve [selector], tap to focus, then type [text]. Mirror
|
|
33
|
+
* Kotlin App.fill from v7.4 c3.
|
|
34
|
+
*/
|
|
35
|
+
fill(selector: Selector, text: string): Promise<void>;
|
|
36
|
+
pressKey(key: KeyName): Promise<void>;
|
|
37
|
+
swipe(direction: SwipeDirection): Promise<void>;
|
|
38
|
+
screenshot(): Promise<Uint8Array>;
|
|
39
|
+
/**
|
|
40
|
+
* Tap at a normalized coordinate (0..1) — escape hatch per §9 #3.
|
|
41
|
+
* Out-of-range values throw ExpectationFailure code=wrongState.
|
|
42
|
+
*/
|
|
43
|
+
tapAtCoord(nx: number, ny: number): Promise<void>;
|
|
44
|
+
terminate(): Promise<void>;
|
|
45
|
+
relaunch(): Promise<void>;
|
|
46
|
+
launchFresh(opts?: {
|
|
47
|
+
clearState?: boolean;
|
|
48
|
+
clearKeychain?: boolean;
|
|
49
|
+
appPath?: string;
|
|
50
|
+
}): Promise<void>;
|
|
51
|
+
find(selector: Selector): Locator;
|
|
52
|
+
tree(): Promise<A11yNode>;
|
|
53
|
+
systemPopups(): Promise<A11yNode[]>;
|
|
54
|
+
openUrl(url: string): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
export { SmixNotImplementedError };
|
|
57
|
+
//# sourceMappingURL=App.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../src/App.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,QAAQ,EAAqB,MAAM,eAAe,CAAA;AAEhE,OAAO,EAAE,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AAC/D,OAAO,EAEL,KAAK,QAAQ,EACd,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,cAAc,EACnB,KAAK,cAAc,EACpB,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAE7E;;GAEG;AACH,qBAAa,GAAG;aAEI,QAAQ,EAAE,MAAM;aAChB,OAAO,EAAE,cAAc;aACvB,QAAQ,EAAE,gBAAgB;IAC1C;;;;;OAKG;aACa,cAAc,EAAE,cAAc;gBAT9B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,cAAc,EACvB,QAAQ,EAAE,gBAAgB;IAC1C;;;;;OAKG;IACa,cAAc,GAAE,cAE/B;IAKG,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAsChF;;;OAGG;IACG,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrD,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrC,KAAK,CAAC,SAAS,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC;IAIvC;;;OAGG;IACG,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWjD,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAI1B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAKzB,WAAW,CAAC,IAAI,GAAE;QACtB,UAAU,CAAC,EAAE,OAAO,CAAA;QACpB,aAAa,CAAC,EAAE,OAAO,CAAA;QACvB,OAAO,CAAC,EAAE,MAAM,CAAA;KACZ,GAAG,OAAO,CAAC,IAAI,CAAC;IAWtB,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO;IAI3B,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC;IAIzB,YAAY,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAInC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG1C;AAED,OAAO,EAAE,uBAAuB,EAAE,CAAA"}
|
package/dist/App.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// v7.5 c3 — App class full wire mirrors Kotlin v7.4 c5 / Swift v7.2.
|
|
2
|
+
import { findById, flatten } from './A11yNode.js';
|
|
3
|
+
import { ExpectationFailure } from './ExpectationFailure.js';
|
|
4
|
+
import { Locator, SmixNotImplementedError } from './Locator.js';
|
|
5
|
+
import { encodeSelectorJson, } from './Selector.js';
|
|
6
|
+
/**
|
|
7
|
+
* A handle to a running app on the simulator / emulator.
|
|
8
|
+
*/
|
|
9
|
+
export class App {
|
|
10
|
+
bundleId;
|
|
11
|
+
runtime;
|
|
12
|
+
resolver;
|
|
13
|
+
labelsResolver;
|
|
14
|
+
constructor(bundleId, runtime, resolver,
|
|
15
|
+
/**
|
|
16
|
+
* v7.6 c1 — wraps Rust `resolve_selector_labels` (each match's
|
|
17
|
+
* `.label`). Used by Locator.toHaveCount + toHaveLabel. Defaults
|
|
18
|
+
* to a stub that throws — tests + production should pass a real
|
|
19
|
+
* impl (HttpSimRuntime.labelsResolver or MockLabelsResolver.resolve).
|
|
20
|
+
*/
|
|
21
|
+
labelsResolver = async () => {
|
|
22
|
+
throw new Error('labelsResolver not configured; pass one to Smix.launchApp or App constructor');
|
|
23
|
+
}) {
|
|
24
|
+
this.bundleId = bundleId;
|
|
25
|
+
this.runtime = runtime;
|
|
26
|
+
this.resolver = resolver;
|
|
27
|
+
this.labelsResolver = labelsResolver;
|
|
28
|
+
}
|
|
29
|
+
// ---- act methods --------------------------------------------------
|
|
30
|
+
async tap(selector, _opts = {}) {
|
|
31
|
+
const tree = await this.runtime.snapshotTree();
|
|
32
|
+
const selectorJson = encodeSelectorJson(selector);
|
|
33
|
+
const treeJson = JSON.stringify(tree);
|
|
34
|
+
let matches;
|
|
35
|
+
try {
|
|
36
|
+
matches = await this.resolver(treeJson, selectorJson);
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
40
|
+
throw new ExpectationFailure({
|
|
41
|
+
code: 'unknown',
|
|
42
|
+
message: `FFI resolveSelector raised: ${msg}`,
|
|
43
|
+
selectorJson,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const firstId = matches[0];
|
|
47
|
+
const node = firstId !== undefined ? findById(tree, firstId) : null;
|
|
48
|
+
if (node === null) {
|
|
49
|
+
throw new ExpectationFailure({
|
|
50
|
+
code: 'notFound',
|
|
51
|
+
message: 'no candidates after spatial + index filters',
|
|
52
|
+
selectorJson,
|
|
53
|
+
visibleElements: flatten(tree).slice(0, 20),
|
|
54
|
+
suggestions: [
|
|
55
|
+
'check `accessibilityIdentifier` is set on the target view',
|
|
56
|
+
'consider relaxing to text(...) if the label is more stable than the id',
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
await this.runtime.synthesizeTap(node.bounds.x + node.bounds.w / 2, node.bounds.y + node.bounds.h / 2);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Resolve [selector], tap to focus, then type [text]. Mirror
|
|
64
|
+
* Kotlin App.fill from v7.4 c3.
|
|
65
|
+
*/
|
|
66
|
+
async fill(selector, text) {
|
|
67
|
+
await this.tap(selector);
|
|
68
|
+
await this.runtime.sendString(text);
|
|
69
|
+
}
|
|
70
|
+
async pressKey(key) {
|
|
71
|
+
await this.runtime.pressKey(key);
|
|
72
|
+
}
|
|
73
|
+
async swipe(direction) {
|
|
74
|
+
await this.runtime.swipe(direction);
|
|
75
|
+
}
|
|
76
|
+
async screenshot() {
|
|
77
|
+
return this.runtime.screenshot();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Tap at a normalized coordinate (0..1) — escape hatch per §9 #3.
|
|
81
|
+
* Out-of-range values throw ExpectationFailure code=wrongState.
|
|
82
|
+
*/
|
|
83
|
+
async tapAtCoord(nx, ny) {
|
|
84
|
+
if (nx < 0 || nx > 1 || ny < 0 || ny > 1) {
|
|
85
|
+
throw new ExpectationFailure({
|
|
86
|
+
code: 'wrongState',
|
|
87
|
+
message: `tapAtCoord arguments out of [0,1] range: nx=${nx}, ny=${ny}`,
|
|
88
|
+
suggestions: ['use normalized 0..1 — for raw points, divide by viewport size'],
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
await this.runtime.synthesizeTapAtNormalized(nx, ny);
|
|
92
|
+
}
|
|
93
|
+
async terminate() {
|
|
94
|
+
await this.runtime.terminate(this.bundleId);
|
|
95
|
+
}
|
|
96
|
+
async relaunch() {
|
|
97
|
+
await this.runtime.terminate(this.bundleId);
|
|
98
|
+
await this.runtime.launch(this.bundleId);
|
|
99
|
+
}
|
|
100
|
+
async launchFresh(opts = {}) {
|
|
101
|
+
await this.runtime.launchFresh({
|
|
102
|
+
bundleId: this.bundleId,
|
|
103
|
+
clearState: opts.clearState ?? false,
|
|
104
|
+
clearKeychain: opts.clearKeychain ?? false,
|
|
105
|
+
appPath: opts.appPath,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// ---- sense methods ------------------------------------------------
|
|
109
|
+
find(selector) {
|
|
110
|
+
return new Locator(this, selector);
|
|
111
|
+
}
|
|
112
|
+
async tree() {
|
|
113
|
+
return this.runtime.snapshotTree();
|
|
114
|
+
}
|
|
115
|
+
async systemPopups() {
|
|
116
|
+
return this.runtime.systemPopups();
|
|
117
|
+
}
|
|
118
|
+
async openUrl(url) {
|
|
119
|
+
await this.runtime.openUrl(url);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export { SmixNotImplementedError };
|
|
123
|
+
//# sourceMappingURL=App.js.map
|
package/dist/App.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"App.js","sourceRoot":"","sources":["../src/App.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { A11yNode } from './A11yNode.js';
|
|
2
|
+
export type FailureCode = 'notFound' | 'ambiguous' | 'notInteractable' | 'timeout' | 'wrongState' | 'unknown';
|
|
3
|
+
export declare const FAILURE_CODES: readonly FailureCode[];
|
|
4
|
+
/**
|
|
5
|
+
* Structured failure thrown by SDK resolver paths (App.tap, Locator
|
|
6
|
+
* assertions) when matching fails. Wire format matches Swift +
|
|
7
|
+
* Kotlin + Rust 1:1.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ExpectationFailure extends Error {
|
|
10
|
+
readonly code: FailureCode;
|
|
11
|
+
readonly selectorJson: string | null;
|
|
12
|
+
readonly visibleElements: readonly A11yNode[];
|
|
13
|
+
readonly suggestions: readonly string[];
|
|
14
|
+
readonly timestamp: number;
|
|
15
|
+
constructor(opts: {
|
|
16
|
+
code: FailureCode;
|
|
17
|
+
message: string;
|
|
18
|
+
selectorJson?: string | undefined;
|
|
19
|
+
visibleElements?: readonly A11yNode[] | undefined;
|
|
20
|
+
suggestions?: readonly string[] | undefined;
|
|
21
|
+
timestamp?: number | undefined;
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* AI-readable JSON dump — what Claude Code sees when the test fails.
|
|
25
|
+
* Sorted keys, single line. Mirror Swift errorDescription / Kotlin
|
|
26
|
+
* errorJson() byte-shape.
|
|
27
|
+
*/
|
|
28
|
+
toJson(): string;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=ExpectationFailure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpectationFailure.d.ts","sourceRoot":"","sources":["../src/ExpectationFailure.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAE7C,MAAM,MAAM,WAAW,GACnB,UAAU,GACV,WAAW,GACX,iBAAiB,GACjB,SAAS,GACT,YAAY,GACZ,SAAS,CAAA;AAEb,eAAO,MAAM,aAAa,EAAE,SAAS,WAAW,EAGtC,CAAA;AAEV;;;;GAIG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,QAAQ,CAAC,eAAe,EAAE,SAAS,QAAQ,EAAE,CAAA;IAC7C,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;gBAEd,IAAI,EAAE;QAChB,IAAI,EAAE,WAAW,CAAA;QACjB,OAAO,EAAE,MAAM,CAAA;QACf,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;QACjC,eAAe,CAAC,EAAE,SAAS,QAAQ,EAAE,GAAG,SAAS,CAAA;QACjD,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;QAC3C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAC/B;IAUD;;;;OAIG;IACH,MAAM,IAAI,MAAM;CAUjB"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// v7.5 c1 — ExpectationFailure + FailureCode mirror Swift v7.2 c4 +
|
|
2
|
+
// Kotlin v7.4 c1 + Rust smix-error.
|
|
3
|
+
//
|
|
4
|
+
// AI-readable JSON contract: toJson() emits sorted-keys, ISO-8601
|
|
5
|
+
// timestamp single-line JSON matching Swift errorDescription contract
|
|
6
|
+
// (per design.md §9 #11).
|
|
7
|
+
export const FAILURE_CODES = [
|
|
8
|
+
'notFound', 'ambiguous', 'notInteractable',
|
|
9
|
+
'timeout', 'wrongState', 'unknown',
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* Structured failure thrown by SDK resolver paths (App.tap, Locator
|
|
13
|
+
* assertions) when matching fails. Wire format matches Swift +
|
|
14
|
+
* Kotlin + Rust 1:1.
|
|
15
|
+
*/
|
|
16
|
+
export class ExpectationFailure extends Error {
|
|
17
|
+
code;
|
|
18
|
+
selectorJson;
|
|
19
|
+
visibleElements;
|
|
20
|
+
suggestions;
|
|
21
|
+
timestamp;
|
|
22
|
+
constructor(opts) {
|
|
23
|
+
super(opts.message);
|
|
24
|
+
this.name = 'ExpectationFailure';
|
|
25
|
+
this.code = opts.code;
|
|
26
|
+
this.selectorJson = opts.selectorJson ?? null;
|
|
27
|
+
this.visibleElements = opts.visibleElements ?? [];
|
|
28
|
+
this.suggestions = opts.suggestions ?? [];
|
|
29
|
+
this.timestamp = opts.timestamp ?? Date.now();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* AI-readable JSON dump — what Claude Code sees when the test fails.
|
|
33
|
+
* Sorted keys, single line. Mirror Swift errorDescription / Kotlin
|
|
34
|
+
* errorJson() byte-shape.
|
|
35
|
+
*/
|
|
36
|
+
toJson() {
|
|
37
|
+
return JSON.stringify({
|
|
38
|
+
code: this.code,
|
|
39
|
+
message: this.message,
|
|
40
|
+
selector: this.selectorJson,
|
|
41
|
+
suggestions: this.suggestions,
|
|
42
|
+
timestamp: this.timestamp,
|
|
43
|
+
visibleElements: this.visibleElements,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=ExpectationFailure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpectationFailure.js","sourceRoot":"","sources":["../src/ExpectationFailure.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,oCAAoC;AACpC,EAAE;AACF,kEAAkE;AAClE,sEAAsE;AACtE,0BAA0B;AAY1B,MAAM,CAAC,MAAM,aAAa,GAA2B;IACnD,UAAU,EAAE,WAAW,EAAE,iBAAiB;IAC1C,SAAS,EAAE,YAAY,EAAE,SAAS;CAC1B,CAAA;AAEV;;;;GAIG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,IAAI,CAAa;IACjB,YAAY,CAAe;IAC3B,eAAe,CAAqB;IACpC,WAAW,CAAmB;IAC9B,SAAS,CAAQ;IAE1B,YAAY,IAOX;QACC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACnB,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAA;QAChC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAA;QACrB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,IAAI,CAAA;QAC7C,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,eAAe,IAAI,EAAE,CAAA;QACjD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAA;QACzC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,CAAA;IAC/C,CAAC;IAED;;;;OAIG;IACH,MAAM;QACJ,OAAO,IAAI,CAAC,SAAS,CAAC;YACpB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE,IAAI,CAAC,YAAY;YAC3B,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,eAAe,EAAE,IAAI,CAAC,eAAe;SACtC,CAAC,CAAA;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type A11yNode } from './A11yNode.js';
|
|
2
|
+
import type { KeyName, SmixSimRuntime, SwipeDirection } from './SimRuntime.js';
|
|
3
|
+
import type { LabelsResolver, SelectorResolver } from './SelectorResolver.js';
|
|
4
|
+
/**
|
|
5
|
+
* Minimal HTTP client surface — overridable for testing. fetch-compatible
|
|
6
|
+
* signature so consumers can pass globalThis.fetch directly.
|
|
7
|
+
*/
|
|
8
|
+
export type HttpFetch = (input: string, init: {
|
|
9
|
+
method: 'POST';
|
|
10
|
+
headers: Record<string, string>;
|
|
11
|
+
body: string;
|
|
12
|
+
}) => Promise<{
|
|
13
|
+
ok: boolean;
|
|
14
|
+
status: number;
|
|
15
|
+
json(): Promise<unknown>;
|
|
16
|
+
text(): Promise<string>;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* SmixSimRuntime + SelectorResolver wrapper around the smix-runner HTTP
|
|
20
|
+
* server. Used in production RN/Expo test targets via fetch.
|
|
21
|
+
*/
|
|
22
|
+
export declare class HttpSimRuntime implements SmixSimRuntime {
|
|
23
|
+
readonly baseUrl: string;
|
|
24
|
+
readonly fetchImpl: HttpFetch;
|
|
25
|
+
readonly resolver: SelectorResolver;
|
|
26
|
+
/**
|
|
27
|
+
* v7.7 c1 — labels resolver wrapping Rust `resolve_selector_labels`
|
|
28
|
+
* via smix-runner-server `/select/resolve-labels` endpoint. Used by
|
|
29
|
+
* `Locator.toHaveCount` (count = labels.length) + Locator.toHaveLabel.
|
|
30
|
+
* Pass directly to App constructor or Smix.launchApp.
|
|
31
|
+
*/
|
|
32
|
+
readonly labelsResolver: LabelsResolver;
|
|
33
|
+
constructor(baseUrl: string, fetchImpl?: HttpFetch);
|
|
34
|
+
/**
|
|
35
|
+
* v7.7 c1 — direct match-count via dedicated FFI fn (avoid allocating
|
|
36
|
+
* the full ids list when callers only need count). Mirrors Rust
|
|
37
|
+
* `resolve_selector_count`. Locator.toHaveCount can prefer this over
|
|
38
|
+
* `labelsResolver` if it needs only count, not the labels themselves.
|
|
39
|
+
*/
|
|
40
|
+
resolveCount(treeJson: string, selectorJson: string): Promise<number>;
|
|
41
|
+
launch(bundleId: string): Promise<void>;
|
|
42
|
+
terminate(bundleId: string): Promise<void>;
|
|
43
|
+
snapshotTree(): Promise<A11yNode>;
|
|
44
|
+
synthesizeTap(x: number, y: number): Promise<void>;
|
|
45
|
+
sendString(text: string): Promise<void>;
|
|
46
|
+
pressKey(key: KeyName): Promise<void>;
|
|
47
|
+
swipe(direction: SwipeDirection): Promise<void>;
|
|
48
|
+
screenshot(): Promise<Uint8Array>;
|
|
49
|
+
systemPopups(): Promise<A11yNode[]>;
|
|
50
|
+
openUrl(url: string): Promise<void>;
|
|
51
|
+
launchFresh(opts: {
|
|
52
|
+
bundleId: string;
|
|
53
|
+
clearState: boolean;
|
|
54
|
+
clearKeychain: boolean;
|
|
55
|
+
appPath?: string | undefined;
|
|
56
|
+
}): Promise<void>;
|
|
57
|
+
launchFromPath(appPath: string): Promise<void>;
|
|
58
|
+
synthesizeTapAtNormalized(nx: number, ny: number): Promise<void>;
|
|
59
|
+
private post;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=HttpRunner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HttpRunner.d.ts","sourceRoot":"","sources":["../src/HttpRunner.ts"],"names":[],"mappings":"AAgCA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,KAAK,EACV,OAAO,EAEP,cAAc,EACd,cAAc,EACf,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAE7E;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,CACtB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,KACpE,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC,CAAA;AAEhG;;;GAGG;AACH,qBAAa,cAAe,YAAW,cAAc;aAWjC,OAAO,EAAE,MAAM;aACf,SAAS,EAAE,SAAS;IAXtC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAA;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAA;gBAGrB,OAAO,EAAE,MAAM,EACf,SAAS,GAAE,SAAoD;IAYjF;;;;;OAKG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAKrE,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1C,YAAY,IAAI,OAAO,CAAC,QAAQ,CAAC;IAKjC,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrC,KAAK,CAAC,SAAS,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC;IAKjC,YAAY,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAKnC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInC,WAAW,CAAC,IAAI,EAAE;QACtB,QAAQ,EAAE,MAAM,CAAA;QAChB,UAAU,EAAE,OAAO,CAAA;QACnB,aAAa,EAAE,OAAO,CAAA;QACtB,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAC7B,GAAG,OAAO,CAAC,IAAI,CAAC;IAUX,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAIxD,IAAI;CAkBnB"}
|