zig-mobile-runner 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/CHANGELOG.md +484 -0
- package/CONTRIBUTING.md +42 -0
- package/FEATURES.md +112 -0
- package/LICENSE +21 -0
- package/README.md +255 -0
- package/SECURITY.md +34 -0
- package/build.zig +38 -0
- package/build.zig.zon +7 -0
- package/clients/README.md +144 -0
- package/clients/go/README.md +24 -0
- package/clients/go/examples/fake-session/main.go +93 -0
- package/clients/go/go.mod +3 -0
- package/clients/go/zmr/client.go +432 -0
- package/clients/kotlin/README.md +35 -0
- package/clients/kotlin/build.gradle.kts +35 -0
- package/clients/kotlin/settings.gradle.kts +15 -0
- package/clients/kotlin/src/main/kotlin/dev/zmr/FakeSession.kt +86 -0
- package/clients/kotlin/src/main/kotlin/dev/zmr/ZmrClient.kt +67 -0
- package/clients/python/README.md +29 -0
- package/clients/python/examples/fake_session.py +48 -0
- package/clients/python/pyproject.toml +13 -0
- package/clients/python/zmr_client.py +202 -0
- package/clients/rust/Cargo.lock +107 -0
- package/clients/rust/Cargo.toml +10 -0
- package/clients/rust/README.md +19 -0
- package/clients/rust/examples/fake_session.rs +70 -0
- package/clients/rust/src/lib.rs +461 -0
- package/clients/swift/Package.swift +16 -0
- package/clients/swift/README.md +36 -0
- package/clients/swift/Sources/ZMRClient/ZMRClient.swift +114 -0
- package/clients/swift/Sources/ZMRFakeSession/main.swift +86 -0
- package/clients/typescript/README.md +34 -0
- package/clients/typescript/examples/fake-session.mjs +36 -0
- package/clients/typescript/index.d.ts +144 -0
- package/clients/typescript/index.mjs +192 -0
- package/clients/typescript/package.json +8 -0
- package/docs/adr/0001-agent-native-runner-boundary.md +31 -0
- package/docs/adr/0002-app-local-zmr-contract.md +39 -0
- package/docs/adr/0003-ios-simulator-xctest-shim.md +41 -0
- package/docs/adr/0004-benchmark-claims-and-baseline-collection.md +37 -0
- package/docs/adr/README.md +12 -0
- package/docs/ai-agents.md +156 -0
- package/docs/app-integration.md +316 -0
- package/docs/benchmarking.md +275 -0
- package/docs/client-installation.md +141 -0
- package/docs/clients.md +98 -0
- package/docs/config.md +175 -0
- package/docs/demo.md +259 -0
- package/docs/dsl.md +57 -0
- package/docs/install.md +233 -0
- package/docs/market-positioning.md +70 -0
- package/docs/npm.md +359 -0
- package/docs/protocol-fixtures/README.md +8 -0
- package/docs/protocol-fixtures/core-session.requests.jsonl +8 -0
- package/docs/protocol-fixtures/core-session.responses.jsonl +8 -0
- package/docs/protocol-versioning.md +65 -0
- package/docs/protocol.md +560 -0
- package/docs/publication.md +77 -0
- package/docs/release-audit.md +99 -0
- package/docs/release-candidate.md +111 -0
- package/docs/release-evidence.md +188 -0
- package/docs/release-notes-template.md +58 -0
- package/docs/roadmap.md +334 -0
- package/docs/scenario-authoring.md +88 -0
- package/docs/shipping.md +170 -0
- package/docs/trace-privacy.md +88 -0
- package/docs/troubleshooting.md +256 -0
- package/examples/android-app-auth-probe.json +89 -0
- package/examples/android-app-error-state.json +13 -0
- package/examples/android-app-login-smoke.json +192 -0
- package/examples/android-app-onboarding.json +12 -0
- package/examples/android-app-referral-deep-link.json +12 -0
- package/examples/android-shim-smoke.json +19 -0
- package/examples/demo-failure.json +12 -0
- package/examples/demo-fake.json +14 -0
- package/examples/ios-dev-client-open-link.json +26 -0
- package/examples/ios-dev-client-route-snapshot.json +24 -0
- package/examples/ios-shim-smoke.json +23 -0
- package/examples/ios-smoke.json +9 -0
- package/go.work +3 -0
- package/npm/agents.mjs +183 -0
- package/npm/app-config.mjs +95 -0
- package/npm/build-zmr.mjs +21 -0
- package/npm/commands.mjs +104 -0
- package/npm/generated-files.mjs +50 -0
- package/npm/index.mjs +75 -0
- package/npm/init-app.mjs +80 -0
- package/npm/package-scripts.mjs +72 -0
- package/npm/postinstall.mjs +21 -0
- package/npm/scaffold.mjs +179 -0
- package/npm/scenarios.mjs +93 -0
- package/npm/setup.mjs +69 -0
- package/npm/wizard.mjs +117 -0
- package/npm/zmr.mjs +23 -0
- package/package.json +114 -0
- package/prebuilds/darwin-arm64/zmr +0 -0
- package/prebuilds/darwin-x64/zmr +0 -0
- package/prebuilds/linux-arm64/zmr +0 -0
- package/prebuilds/linux-x64/zmr +0 -0
- package/schemas/README.md +26 -0
- package/schemas/action-result.schema.json +27 -0
- package/schemas/capabilities-output.schema.json +98 -0
- package/schemas/devices-output.schema.json +25 -0
- package/schemas/doctor-output.schema.json +51 -0
- package/schemas/explain-output.schema.json +51 -0
- package/schemas/import-output.schema.json +23 -0
- package/schemas/init-output.schema.json +71 -0
- package/schemas/json-rpc.schema.json +55 -0
- package/schemas/release-manifest.schema.json +43 -0
- package/schemas/release-readiness-output.schema.json +127 -0
- package/schemas/run-output.schema.json +43 -0
- package/schemas/scenario.schema.json +128 -0
- package/schemas/schemas-output.schema.json +26 -0
- package/schemas/semantic-snapshot.schema.json +116 -0
- package/schemas/snapshot.schema.json +60 -0
- package/schemas/trace-event.schema.json +14 -0
- package/schemas/trace-manifest.schema.json +59 -0
- package/schemas/validate-output.schema.json +42 -0
- package/schemas/version-output.schema.json +23 -0
- package/schemas/zmr-config.schema.json +75 -0
- package/scripts/android-emulator.sh +126 -0
- package/scripts/assert-ios-physical-ready.sh +213 -0
- package/scripts/benchmark-command.sh +307 -0
- package/scripts/benchmark.sh +359 -0
- package/scripts/benchmark_gate.py +117 -0
- package/scripts/benchmark_result_row.py +88 -0
- package/scripts/compare-benchmarks.py +288 -0
- package/scripts/create-android-demo-app.sh +342 -0
- package/scripts/create-ios-demo-app.sh +261 -0
- package/scripts/demo-android-real.sh +232 -0
- package/scripts/demo-ios-real.sh +270 -0
- package/scripts/demo.sh +464 -0
- package/scripts/device-matrix.sh +338 -0
- package/scripts/ensure-ios-shim-target.rb +237 -0
- package/scripts/install-android-shim.sh +281 -0
- package/scripts/install-ios-shim.sh +589 -0
- package/scripts/pilot-gate.sh +560 -0
- package/scripts/release-readiness.py +838 -0
- package/scripts/release-readiness.sh +91 -0
- package/scripts/run-android-pilot.sh +561 -0
- package/scripts/run-ios-pilot.sh +509 -0
- package/shims/android/README.md +21 -0
- package/shims/android/ZMRShimInstrumentedTest.java +152 -0
- package/shims/android/protocol.md +18 -0
- package/shims/ios/README.md +50 -0
- package/shims/ios/ZMRShim.swift +110 -0
- package/shims/ios/ZMRShimUITestCase.swift +475 -0
- package/shims/ios/protocol.md +74 -0
- package/skills/zmr-mobile-testing/SKILL.md +127 -0
- package/src/android.zig +344 -0
- package/src/android_device_info.zig +99 -0
- package/src/android_emulator.zig +154 -0
- package/src/android_screen_recording.zig +112 -0
- package/src/android_shell.zig +112 -0
- package/src/bundle.zig +124 -0
- package/src/bundle_redaction.zig +272 -0
- package/src/bundle_tar.zig +123 -0
- package/src/cli_devices.zig +97 -0
- package/src/cli_doctor.zig +114 -0
- package/src/cli_import.zig +70 -0
- package/src/cli_info.zig +39 -0
- package/src/cli_init.zig +72 -0
- package/src/cli_output.zig +467 -0
- package/src/cli_run.zig +259 -0
- package/src/cli_serve.zig +287 -0
- package/src/cli_trace.zig +111 -0
- package/src/cli_validate.zig +41 -0
- package/src/command.zig +211 -0
- package/src/config.zig +305 -0
- package/src/config_diagnostics.zig +212 -0
- package/src/config_paths.zig +49 -0
- package/src/device_registry.zig +37 -0
- package/src/doctor.zig +412 -0
- package/src/doctor_hints.zig +52 -0
- package/src/errors.zig +55 -0
- package/src/fake_device.zig +163 -0
- package/src/health.zig +28 -0
- package/src/importer.zig +343 -0
- package/src/importer_json.zig +100 -0
- package/src/importer_model.zig +103 -0
- package/src/ios.zig +399 -0
- package/src/ios_devices.zig +219 -0
- package/src/ios_lifecycle.zig +72 -0
- package/src/ios_shim.zig +242 -0
- package/src/ios_snapshot.zig +20 -0
- package/src/json_fields.zig +80 -0
- package/src/json_rpc.zig +150 -0
- package/src/json_rpc_methods.zig +318 -0
- package/src/json_rpc_observation.zig +31 -0
- package/src/json_rpc_params.zig +52 -0
- package/src/json_rpc_protocol.zig +110 -0
- package/src/json_rpc_trace.zig +73 -0
- package/src/main.zig +135 -0
- package/src/mcp.zig +234 -0
- package/src/mcp_protocol.zig +64 -0
- package/src/mcp_trace.zig +83 -0
- package/src/report.zig +346 -0
- package/src/report_html.zig +63 -0
- package/src/report_values.zig +27 -0
- package/src/run_options.zig +152 -0
- package/src/runner.zig +280 -0
- package/src/runner_actions.zig +109 -0
- package/src/runner_config.zig +6 -0
- package/src/runner_diagnostics.zig +268 -0
- package/src/runner_events.zig +170 -0
- package/src/runner_native.zig +88 -0
- package/src/runner_waits.zig +300 -0
- package/src/scaffold.zig +472 -0
- package/src/scenario.zig +346 -0
- package/src/scenario_fields.zig +50 -0
- package/src/schema_registry.zig +53 -0
- package/src/selector.zig +84 -0
- package/src/semantic.zig +171 -0
- package/src/trace.zig +315 -0
- package/src/trace_json.zig +340 -0
- package/src/trace_summary.zig +218 -0
- package/src/trace_summary_diagnostic.zig +202 -0
- package/src/types.zig +120 -0
- package/src/uiautomator.zig +164 -0
- package/src/validation.zig +187 -0
- package/src/version.zig +22 -0
- package/viewer/app.js +373 -0
- package/viewer/index.html +126 -0
- package/viewer/parser.js +233 -0
- package/viewer/styles.css +585 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# ZMR Python Reference Client
|
|
2
|
+
|
|
3
|
+
Standard-library Python client for ZMR's newline-delimited JSON-RPC protocol.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from zmr_client import ZmrClient
|
|
7
|
+
|
|
8
|
+
with ZmrClient(
|
|
9
|
+
"zmr",
|
|
10
|
+
[
|
|
11
|
+
"serve",
|
|
12
|
+
"--transport", "stdio",
|
|
13
|
+
"--device", "emulator-5554",
|
|
14
|
+
"--app-id", "com.example.mobiletest",
|
|
15
|
+
"--trace-dir", "traces/agent-session",
|
|
16
|
+
],
|
|
17
|
+
) as zmr:
|
|
18
|
+
zmr.create_session()
|
|
19
|
+
zmr.open_link("exampleapp://e2e-auth?probe=1")
|
|
20
|
+
zmr.wait_until({"text": "E2E auth probe"}, timeout_ms=30000)
|
|
21
|
+
snapshot = zmr.snapshot()
|
|
22
|
+
events = zmr.trace_events(0, limit=100)
|
|
23
|
+
print(snapshot["nodes"])
|
|
24
|
+
print(len(events["events"]))
|
|
25
|
+
zmr.export_trace("traces/agent-session-redacted.zmrtrace", redact=True, omit_screenshots=True)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The client intentionally avoids runtime dependencies so agents can vendor or
|
|
29
|
+
copy it into automation harnesses easily.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
|
8
|
+
|
|
9
|
+
from zmr_client import ZmrClient # noqa: E402
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
with ZmrClient(
|
|
16
|
+
os.path.join(ROOT, "zig-out", "bin", "zmr"),
|
|
17
|
+
[
|
|
18
|
+
"serve",
|
|
19
|
+
"--transport",
|
|
20
|
+
"stdio",
|
|
21
|
+
"--device",
|
|
22
|
+
"fake-android-1",
|
|
23
|
+
"--app-id",
|
|
24
|
+
"com.example.mobiletest",
|
|
25
|
+
"--adb",
|
|
26
|
+
os.path.join(ROOT, "tests", "fake-adb.sh"),
|
|
27
|
+
"--trace-dir",
|
|
28
|
+
"traces/demo-python-client",
|
|
29
|
+
],
|
|
30
|
+
) as zmr:
|
|
31
|
+
capabilities = zmr.capabilities()
|
|
32
|
+
zmr.create_session()
|
|
33
|
+
zmr.open_link("exampleapp://e2e-auth?probe=1")
|
|
34
|
+
zmr.assert_healthy(timeout_ms=100)
|
|
35
|
+
snapshot = zmr.snapshot()
|
|
36
|
+
events = zmr.trace_events(0, limit=20)
|
|
37
|
+
zmr.export_trace("traces/demo-python-client-redacted.zmrtrace", redact=True, omit_screenshots=True)
|
|
38
|
+
print(
|
|
39
|
+
json.dumps(
|
|
40
|
+
{
|
|
41
|
+
"protocolVersion": capabilities["protocolVersion"],
|
|
42
|
+
"activePackage": snapshot["activePackage"],
|
|
43
|
+
"nodes": len(snapshot["nodes"]),
|
|
44
|
+
"events": len(events["events"]),
|
|
45
|
+
},
|
|
46
|
+
separators=(",", ":"),
|
|
47
|
+
)
|
|
48
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "zmr-client"
|
|
7
|
+
version = "0.1.0.dev1"
|
|
8
|
+
description = "Python JSON-RPC client for Zig Mobile Runner."
|
|
9
|
+
requires-python = ">=3.9"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
|
|
12
|
+
[tool.setuptools]
|
|
13
|
+
py-modules = ["zmr_client"]
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import subprocess
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ZmrRpcError(RuntimeError):
|
|
7
|
+
def __init__(self, error):
|
|
8
|
+
super().__init__(error.get("message", "ZMR JSON-RPC error"))
|
|
9
|
+
self.code = error.get("code")
|
|
10
|
+
self.public_code = error.get("publicCode")
|
|
11
|
+
self.data = error.get("data")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ZmrClient:
|
|
15
|
+
def __init__(self, command, args=None, cwd=None, env=None, stderr=None):
|
|
16
|
+
self._process = subprocess.Popen(
|
|
17
|
+
[command, *(args or [])],
|
|
18
|
+
cwd=cwd,
|
|
19
|
+
env=env,
|
|
20
|
+
stdin=subprocess.PIPE,
|
|
21
|
+
stdout=subprocess.PIPE,
|
|
22
|
+
stderr=stderr,
|
|
23
|
+
text=True,
|
|
24
|
+
encoding="utf-8",
|
|
25
|
+
bufsize=1,
|
|
26
|
+
)
|
|
27
|
+
self._next_id = 1
|
|
28
|
+
self._lock = threading.Lock()
|
|
29
|
+
self._closed = False
|
|
30
|
+
|
|
31
|
+
def request(self, method, params=None):
|
|
32
|
+
with self._lock:
|
|
33
|
+
if self._closed:
|
|
34
|
+
raise RuntimeError("zmr client is closed")
|
|
35
|
+
request_id = self._next_id
|
|
36
|
+
self._next_id += 1
|
|
37
|
+
line = json.dumps(
|
|
38
|
+
{
|
|
39
|
+
"jsonrpc": "2.0",
|
|
40
|
+
"id": request_id,
|
|
41
|
+
"method": method,
|
|
42
|
+
"params": params or {},
|
|
43
|
+
},
|
|
44
|
+
separators=(",", ":"),
|
|
45
|
+
)
|
|
46
|
+
self._process.stdin.write(line + "\n")
|
|
47
|
+
self._process.stdin.flush()
|
|
48
|
+
|
|
49
|
+
response_line = self._process.stdout.readline()
|
|
50
|
+
if response_line == "":
|
|
51
|
+
code = self._process.poll()
|
|
52
|
+
raise RuntimeError(f"zmr process exited with {code}")
|
|
53
|
+
response = json.loads(response_line)
|
|
54
|
+
if response.get("id") != request_id:
|
|
55
|
+
raise RuntimeError(f"unexpected JSON-RPC response id {response.get('id')!r}")
|
|
56
|
+
if "error" in response:
|
|
57
|
+
raise ZmrRpcError(response["error"])
|
|
58
|
+
return response.get("result")
|
|
59
|
+
|
|
60
|
+
def capabilities(self):
|
|
61
|
+
return self.request("runner.capabilities")
|
|
62
|
+
|
|
63
|
+
def create_session(self):
|
|
64
|
+
return self.request("session.create")
|
|
65
|
+
|
|
66
|
+
def close_session(self):
|
|
67
|
+
return self.request("session.close")
|
|
68
|
+
|
|
69
|
+
def devices(self):
|
|
70
|
+
return self.request("device.list")
|
|
71
|
+
|
|
72
|
+
def launch(self):
|
|
73
|
+
return self.request("app.launch")
|
|
74
|
+
|
|
75
|
+
def stop(self):
|
|
76
|
+
return self.request("app.stop")
|
|
77
|
+
|
|
78
|
+
def clear_state(self):
|
|
79
|
+
return self.request("app.clearState")
|
|
80
|
+
|
|
81
|
+
def open_link(self, url):
|
|
82
|
+
return self.request("app.openLink", {"url": url})
|
|
83
|
+
|
|
84
|
+
def snapshot(self):
|
|
85
|
+
return self.request("observe.snapshot")
|
|
86
|
+
|
|
87
|
+
def semantic_snapshot(self):
|
|
88
|
+
return self.request("observe.semanticSnapshot")
|
|
89
|
+
|
|
90
|
+
def tap(self, selector):
|
|
91
|
+
return self.request("ui.tap", {"selector": selector})
|
|
92
|
+
|
|
93
|
+
def type_text(self, text, selector=None):
|
|
94
|
+
params = {"text": text}
|
|
95
|
+
if selector is not None:
|
|
96
|
+
params["selector"] = selector
|
|
97
|
+
return self.request("ui.type", params)
|
|
98
|
+
|
|
99
|
+
def erase_text(self, selector=None, max_chars=None):
|
|
100
|
+
params = {}
|
|
101
|
+
if selector is not None:
|
|
102
|
+
params["selector"] = selector
|
|
103
|
+
if max_chars is not None:
|
|
104
|
+
params["maxChars"] = max_chars
|
|
105
|
+
return self.request("ui.eraseText", params)
|
|
106
|
+
|
|
107
|
+
def hide_keyboard(self):
|
|
108
|
+
return self.request("ui.hideKeyboard")
|
|
109
|
+
|
|
110
|
+
def swipe(self, x1, y1, x2, y2, duration_ms=None):
|
|
111
|
+
params = {"x1": x1, "y1": y1, "x2": x2, "y2": y2}
|
|
112
|
+
if duration_ms is not None:
|
|
113
|
+
params["durationMs"] = duration_ms
|
|
114
|
+
return self.request("ui.swipe", params)
|
|
115
|
+
|
|
116
|
+
def press_back(self):
|
|
117
|
+
return self.request("ui.pressBack")
|
|
118
|
+
|
|
119
|
+
def scroll_until_visible(self, selector, direction=None, timeout_ms=None):
|
|
120
|
+
params = {"selector": selector}
|
|
121
|
+
if direction is not None:
|
|
122
|
+
params["direction"] = direction
|
|
123
|
+
if timeout_ms is not None:
|
|
124
|
+
params["timeoutMs"] = timeout_ms
|
|
125
|
+
return self.request("ui.scrollUntilVisible", params)
|
|
126
|
+
|
|
127
|
+
def wait_until(self, selector, timeout_ms=None):
|
|
128
|
+
params = {"visible": selector}
|
|
129
|
+
if timeout_ms is not None:
|
|
130
|
+
params["timeoutMs"] = timeout_ms
|
|
131
|
+
return self.request("wait.until", params)
|
|
132
|
+
|
|
133
|
+
def wait_any(self, selectors, timeout_ms=None):
|
|
134
|
+
params = {"selectors": selectors}
|
|
135
|
+
if timeout_ms is not None:
|
|
136
|
+
params["timeoutMs"] = timeout_ms
|
|
137
|
+
return self.request("wait.any", params)
|
|
138
|
+
|
|
139
|
+
def wait_gone(self, selector, timeout_ms=None):
|
|
140
|
+
params = {"selector": selector}
|
|
141
|
+
if timeout_ms is not None:
|
|
142
|
+
params["timeoutMs"] = timeout_ms
|
|
143
|
+
return self.request("wait.gone", params)
|
|
144
|
+
|
|
145
|
+
def assert_visible(self, selector, timeout_ms=None):
|
|
146
|
+
params = {"selector": selector}
|
|
147
|
+
if timeout_ms is not None:
|
|
148
|
+
params["timeoutMs"] = timeout_ms
|
|
149
|
+
return self.request("assert.visible", params)
|
|
150
|
+
|
|
151
|
+
def assert_not_visible(self, selector, timeout_ms=None):
|
|
152
|
+
params = {"selector": selector}
|
|
153
|
+
if timeout_ms is not None:
|
|
154
|
+
params["timeoutMs"] = timeout_ms
|
|
155
|
+
return self.request("assert.notVisible", params)
|
|
156
|
+
|
|
157
|
+
def assert_healthy(self, timeout_ms=None):
|
|
158
|
+
params = {}
|
|
159
|
+
if timeout_ms is not None:
|
|
160
|
+
params["timeoutMs"] = timeout_ms
|
|
161
|
+
return self.request("assert.healthy", params)
|
|
162
|
+
|
|
163
|
+
def export_trace(self, out, redact=False, omit_screenshots=False):
|
|
164
|
+
return self.request(
|
|
165
|
+
"trace.export",
|
|
166
|
+
{
|
|
167
|
+
"out": out,
|
|
168
|
+
"redact": redact,
|
|
169
|
+
"omitScreenshots": omit_screenshots,
|
|
170
|
+
},
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def trace_events(self, after_seq=0, limit=None):
|
|
174
|
+
params = {"afterSeq": after_seq}
|
|
175
|
+
if limit is not None:
|
|
176
|
+
params["limit"] = limit
|
|
177
|
+
return self.request("trace.events", params)
|
|
178
|
+
|
|
179
|
+
def close(self):
|
|
180
|
+
if self._closed:
|
|
181
|
+
return
|
|
182
|
+
self._closed = True
|
|
183
|
+
if self._process.stdin:
|
|
184
|
+
self._process.stdin.close()
|
|
185
|
+
if self._process.poll() is None:
|
|
186
|
+
self._process.terminate()
|
|
187
|
+
try:
|
|
188
|
+
self._process.wait(timeout=2)
|
|
189
|
+
except subprocess.TimeoutExpired:
|
|
190
|
+
self._process.kill()
|
|
191
|
+
self._process.wait(timeout=2)
|
|
192
|
+
if self._process.stdout:
|
|
193
|
+
self._process.stdout.close()
|
|
194
|
+
if self._process.stderr:
|
|
195
|
+
self._process.stderr.close()
|
|
196
|
+
|
|
197
|
+
def __enter__(self):
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
def __exit__(self, exc_type, exc, traceback):
|
|
201
|
+
self.close()
|
|
202
|
+
return False
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "itoa"
|
|
7
|
+
version = "1.0.18"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
10
|
+
|
|
11
|
+
[[package]]
|
|
12
|
+
name = "memchr"
|
|
13
|
+
version = "2.8.0"
|
|
14
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
15
|
+
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
|
16
|
+
|
|
17
|
+
[[package]]
|
|
18
|
+
name = "proc-macro2"
|
|
19
|
+
version = "1.0.106"
|
|
20
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
21
|
+
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
|
22
|
+
dependencies = [
|
|
23
|
+
"unicode-ident",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[[package]]
|
|
27
|
+
name = "quote"
|
|
28
|
+
version = "1.0.45"
|
|
29
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
30
|
+
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
|
31
|
+
dependencies = [
|
|
32
|
+
"proc-macro2",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[[package]]
|
|
36
|
+
name = "serde"
|
|
37
|
+
version = "1.0.228"
|
|
38
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
39
|
+
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
|
40
|
+
dependencies = [
|
|
41
|
+
"serde_core",
|
|
42
|
+
"serde_derive",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[[package]]
|
|
46
|
+
name = "serde_core"
|
|
47
|
+
version = "1.0.228"
|
|
48
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
49
|
+
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
|
50
|
+
dependencies = [
|
|
51
|
+
"serde_derive",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[[package]]
|
|
55
|
+
name = "serde_derive"
|
|
56
|
+
version = "1.0.228"
|
|
57
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
58
|
+
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
59
|
+
dependencies = [
|
|
60
|
+
"proc-macro2",
|
|
61
|
+
"quote",
|
|
62
|
+
"syn",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
[[package]]
|
|
66
|
+
name = "serde_json"
|
|
67
|
+
version = "1.0.149"
|
|
68
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
69
|
+
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
|
70
|
+
dependencies = [
|
|
71
|
+
"itoa",
|
|
72
|
+
"memchr",
|
|
73
|
+
"serde",
|
|
74
|
+
"serde_core",
|
|
75
|
+
"zmij",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[[package]]
|
|
79
|
+
name = "syn"
|
|
80
|
+
version = "2.0.117"
|
|
81
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
82
|
+
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
|
83
|
+
dependencies = [
|
|
84
|
+
"proc-macro2",
|
|
85
|
+
"quote",
|
|
86
|
+
"unicode-ident",
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
[[package]]
|
|
90
|
+
name = "unicode-ident"
|
|
91
|
+
version = "1.0.24"
|
|
92
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
93
|
+
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
94
|
+
|
|
95
|
+
[[package]]
|
|
96
|
+
name = "zmij"
|
|
97
|
+
version = "1.0.21"
|
|
98
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
99
|
+
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
|
100
|
+
|
|
101
|
+
[[package]]
|
|
102
|
+
name = "zmr-client"
|
|
103
|
+
version = "0.1.0"
|
|
104
|
+
dependencies = [
|
|
105
|
+
"serde",
|
|
106
|
+
"serde_json",
|
|
107
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# ZMR Rust Client
|
|
2
|
+
|
|
3
|
+
Small synchronous JSON-RPC client for driving `zmr serve --transport stdio`
|
|
4
|
+
from Rust agents and test harnesses.
|
|
5
|
+
|
|
6
|
+
```rust
|
|
7
|
+
let mut client = zmr_client::Client::start("zmr", ["serve", "--transport", "stdio"])?;
|
|
8
|
+
let snapshot = client.snapshot()?;
|
|
9
|
+
let healthy = client.assert_healthy(Some(1000))?;
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Run the fake-session example from the repository root:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
cargo run --manifest-path clients/rust/Cargo.toml --example fake_session -- \
|
|
16
|
+
--zmr ./zig-out/bin/zmr \
|
|
17
|
+
--adb ./tests/fake-adb.sh \
|
|
18
|
+
--trace-dir traces/demo-rust-client
|
|
19
|
+
```
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
use serde_json::json;
|
|
2
|
+
use std::env;
|
|
3
|
+
use zmr_client::Client;
|
|
4
|
+
|
|
5
|
+
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
6
|
+
let mut zmr = String::from("zig-out/bin/zmr");
|
|
7
|
+
let mut adb = String::from("tests/fake-adb.sh");
|
|
8
|
+
let mut device = String::from("fake-android-1");
|
|
9
|
+
let mut app_id = String::from("com.example.mobiletest");
|
|
10
|
+
let mut trace_dir = String::from("traces/demo-rust-client");
|
|
11
|
+
let mut trace_out = String::from("traces/demo-rust-client-redacted.zmrtrace");
|
|
12
|
+
|
|
13
|
+
let mut args = env::args().skip(1);
|
|
14
|
+
while let Some(arg) = args.next() {
|
|
15
|
+
match arg.as_str() {
|
|
16
|
+
"--zmr" => zmr = args.next().ok_or("--zmr requires a value")?,
|
|
17
|
+
"--adb" => adb = args.next().ok_or("--adb requires a value")?,
|
|
18
|
+
"--device" => device = args.next().ok_or("--device requires a value")?,
|
|
19
|
+
"--app-id" => app_id = args.next().ok_or("--app-id requires a value")?,
|
|
20
|
+
"--trace-dir" => trace_dir = args.next().ok_or("--trace-dir requires a value")?,
|
|
21
|
+
"--trace-out" => trace_out = args.next().ok_or("--trace-out requires a value")?,
|
|
22
|
+
_ => return Err(format!("unknown argument: {arg}").into()),
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let mut client = Client::start(
|
|
27
|
+
&zmr,
|
|
28
|
+
[
|
|
29
|
+
"serve",
|
|
30
|
+
"--transport",
|
|
31
|
+
"stdio",
|
|
32
|
+
"--device",
|
|
33
|
+
&device,
|
|
34
|
+
"--app-id",
|
|
35
|
+
&app_id,
|
|
36
|
+
"--adb",
|
|
37
|
+
&adb,
|
|
38
|
+
"--trace-dir",
|
|
39
|
+
&trace_dir,
|
|
40
|
+
],
|
|
41
|
+
)?;
|
|
42
|
+
let capabilities = client.capabilities()?;
|
|
43
|
+
client.create_session()?;
|
|
44
|
+
client.open_link("exampleapp://rust-client")?;
|
|
45
|
+
client.wait_until(json!({ "text": "Dashboard" }), Some(1000))?;
|
|
46
|
+
client.tap(json!({ "text": "Sign in" }))?;
|
|
47
|
+
client.type_text(
|
|
48
|
+
"agent@example.com",
|
|
49
|
+
Some(json!({ "resourceId": "email-login-email-input" })),
|
|
50
|
+
)?;
|
|
51
|
+
client.assert_not_visible(json!({ "text": "Application has crashed" }), Some(100))?;
|
|
52
|
+
client.assert_healthy(Some(100))?;
|
|
53
|
+
let snapshot = client.snapshot()?;
|
|
54
|
+
let exported = client.export_trace(&trace_out, true, true)?;
|
|
55
|
+
let events = client.trace_events(0, Some(10))?;
|
|
56
|
+
|
|
57
|
+
println!(
|
|
58
|
+
"{}",
|
|
59
|
+
json!({
|
|
60
|
+
"protocolVersion": capabilities.protocol_version,
|
|
61
|
+
"activePackage": snapshot.active_package,
|
|
62
|
+
"nodes": snapshot.nodes.len(),
|
|
63
|
+
"events": events.next_seq,
|
|
64
|
+
"traceDir": trace_dir,
|
|
65
|
+
"traceOut": exported.out,
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
Ok(())
|
|
70
|
+
}
|