zeno-mobile-runner 0.1.8 → 0.2.1
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 +72 -0
- package/FEATURES.md +1 -1
- package/README.md +175 -238
- package/clients/kotlin/README.md +1 -1
- package/clients/kotlin/build.gradle.kts +1 -1
- package/clients/python/pyproject.toml +1 -1
- package/clients/rust/Cargo.lock +1 -1
- package/clients/rust/Cargo.toml +1 -1
- package/clients/typescript/package.json +1 -1
- package/docs/agent-discovery.md +10 -0
- package/docs/ai-agents.md +18 -0
- package/docs/benchmarking.md +39 -0
- package/docs/benchmarks/2026-06-09-android-workflow.md +73 -0
- package/docs/benchmarks/2026-06-09-android-workflow.results.jsonl +20 -0
- package/docs/benchmarks/2026-06-09-framework-baseline-status.md +32 -0
- package/docs/benchmarks/2026-06-09-ios-appium-comparison.md +115 -0
- package/docs/benchmarks/2026-06-09-ios-appium-comparison.results.jsonl +40 -0
- package/docs/benchmarks/2026-06-09-ios-demo.md +90 -0
- package/docs/benchmarks/2026-06-09-ios-demo.results.jsonl +20 -0
- package/docs/benchmarks/2026-06-09-ios-maestro-comparison.md +128 -0
- package/docs/benchmarks/2026-06-09-ios-maestro-comparison.results.jsonl +40 -0
- package/docs/benchmarks/2026-06-09-ios-workflow-comparison.md +143 -0
- package/docs/benchmarks/2026-06-09-ios-workflow-comparison.results.jsonl +40 -0
- package/docs/benchmarks/2026-06-09-ios-xctest-floor.md +106 -0
- package/docs/benchmarks/2026-06-09-ios-xctest-floor.results.jsonl +40 -0
- package/docs/benchmarks/README.md +36 -0
- package/docs/benchmarks/benchmark-lab-v1.json +155 -0
- package/docs/benchmarks/benchmark-lab-v1.md +95 -0
- package/docs/clients.md +16 -0
- package/docs/demo.md +36 -1
- package/docs/frameworks.md +10 -0
- package/docs/npm.md +44 -2
- package/docs/protocol-fixtures/core-session.responses.jsonl +1 -1
- package/docs/protocol.md +10 -10
- package/docs/scenario-authoring.md +15 -0
- package/docs/trace-privacy.md +9 -0
- package/docs/troubleshooting.md +6 -0
- package/examples/android-workflow.json +79 -0
- package/examples/ios-dev-client-open-link.json +24 -13
- package/examples/ios-dev-client-route-snapshot.json +33 -8
- package/examples/ios-shim-workflow.json +79 -0
- package/examples/react-native-expo-workflow.json +75 -0
- package/npm/scenarios.mjs +15 -8
- package/npm/wizard.mjs +1 -1
- package/package.json +6 -1
- 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/scripts/benchmark-lab.py +253 -0
- package/scripts/create-android-demo-app.sh +324 -29
- package/scripts/create-ios-demo-app.sh +174 -7
- package/scripts/create-react-native-expo-demo-app.sh +727 -0
- package/scripts/demo.sh +3 -0
- package/scripts/install-ios-shim.sh +2 -2
- package/shims/ios/ZMRShim.swift +10 -0
- package/shims/ios/ZMRShimUITestCase.swift +49 -1
- package/shims/ios/protocol.md +1 -0
- package/src/cli_import.zig +31 -15
- package/src/cli_trace.zig +38 -16
- package/src/cli_validate.zig +12 -6
- package/src/ios.zig +44 -11
- package/src/ios_shim.zig +36 -2
- package/src/main.zig +6 -0
- package/src/version.zig +1 -1
- package/viewer/app.js +23 -3
|
@@ -37,6 +37,7 @@ After generation:
|
|
|
37
37
|
xcodebuild -project ios/ZMRDemo.xcodeproj -scheme ZMRDemo -destination 'generic/platform=iOS Simulator' -derivedDataPath DerivedData build
|
|
38
38
|
xcrun simctl install booted DerivedData/Build/Products/Debug-iphonesimulator/ZMRDemo.app
|
|
39
39
|
zmr run .zmr/ios-shim-smoke.json --platform ios --device booted --app-id com.example.mobiletest --ios-shim ./.zmr/ios-shim --trace-dir traces/zmr-ios-demo
|
|
40
|
+
zmr run .zmr/ios-shim-workflow.json --platform ios --device booted --app-id com.example.mobiletest --ios-shim ./.zmr/ios-shim --trace-dir traces/zmr-ios-workflow
|
|
40
41
|
USAGE
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -110,12 +111,75 @@ EOF
|
|
|
110
111
|
cat > "$SOURCE_DIR/ContentView.swift" <<'EOF'
|
|
111
112
|
import SwiftUI
|
|
112
113
|
|
|
114
|
+
private enum DemoScreen {
|
|
115
|
+
case welcome
|
|
116
|
+
case profile
|
|
117
|
+
case catalog
|
|
118
|
+
case detail
|
|
119
|
+
case review
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private struct CatalogItem: Identifiable {
|
|
123
|
+
let id: String
|
|
124
|
+
let title: String
|
|
125
|
+
let subtitle: String
|
|
126
|
+
}
|
|
127
|
+
|
|
113
128
|
struct ContentView: View {
|
|
114
129
|
@State private var input = ""
|
|
115
130
|
@State private var status = "Ready"
|
|
131
|
+
@State private var screen: DemoScreen = .welcome
|
|
132
|
+
@State private var profileName = ""
|
|
133
|
+
@State private var profileEmail = ""
|
|
134
|
+
@State private var selectedItem = CatalogItem(id: "north_ridge_pack", title: "North Ridge Pack", subtitle: "Weatherproof day pack")
|
|
116
135
|
@FocusState private var inputFocused: Bool
|
|
117
136
|
|
|
137
|
+
private let catalogItems = [
|
|
138
|
+
CatalogItem(id: "trail_lamp", title: "Trail Lamp", subtitle: "Compact campsite light"),
|
|
139
|
+
CatalogItem(id: "river_bottle", title: "River Bottle", subtitle: "Insulated hydration bottle"),
|
|
140
|
+
CatalogItem(id: "north_ridge_pack", title: "North Ridge Pack", subtitle: "Weatherproof day pack"),
|
|
141
|
+
CatalogItem(id: "summit_shell", title: "Summit Shell", subtitle: "Lightweight rain layer"),
|
|
142
|
+
CatalogItem(id: "basecamp_roll", title: "Basecamp Roll", subtitle: "Modular storage roll"),
|
|
143
|
+
CatalogItem(id: "maple_organizer", title: "Maple Organizer", subtitle: "Cable and tool pouch"),
|
|
144
|
+
CatalogItem(id: "canyon_sling", title: "Canyon Sling", subtitle: "Cross-body field bag"),
|
|
145
|
+
CatalogItem(id: "harbor_tote", title: "Harbor Tote", subtitle: "Daily carry tote"),
|
|
146
|
+
CatalogItem(id: "studio_stand", title: "Studio Stand", subtitle: "Fold-flat work stand")
|
|
147
|
+
]
|
|
148
|
+
|
|
118
149
|
var body: some View {
|
|
150
|
+
VStack(spacing: 16) {
|
|
151
|
+
switch screen {
|
|
152
|
+
case .welcome:
|
|
153
|
+
welcomeView
|
|
154
|
+
case .profile:
|
|
155
|
+
profileView
|
|
156
|
+
case .catalog:
|
|
157
|
+
catalogView
|
|
158
|
+
case .detail:
|
|
159
|
+
detailView
|
|
160
|
+
case .review:
|
|
161
|
+
reviewView
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
VStack(spacing: 4) {
|
|
165
|
+
Text(status)
|
|
166
|
+
.font(.footnote)
|
|
167
|
+
.foregroundStyle(.secondary)
|
|
168
|
+
.accessibilityIdentifier("demo_status")
|
|
169
|
+
|
|
170
|
+
Text(status)
|
|
171
|
+
.font(.footnote)
|
|
172
|
+
.foregroundStyle(.secondary)
|
|
173
|
+
.accessibilityIdentifier("workflow_status")
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
.padding()
|
|
177
|
+
.onOpenURL { _ in
|
|
178
|
+
status = "Deep link opened"
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private var welcomeView: some View {
|
|
119
183
|
VStack(spacing: 20) {
|
|
120
184
|
Text("ZMR iOS Demo")
|
|
121
185
|
.font(.title)
|
|
@@ -123,24 +187,126 @@ struct ContentView: View {
|
|
|
123
187
|
|
|
124
188
|
Button("Continue") {
|
|
125
189
|
status = "Continue tapped"
|
|
126
|
-
|
|
190
|
+
screen = .profile
|
|
127
191
|
}
|
|
128
192
|
.buttonStyle(.borderedProminent)
|
|
129
193
|
.accessibilityIdentifier("continue_button")
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private var profileView: some View {
|
|
198
|
+
VStack(spacing: 14) {
|
|
199
|
+
Text("Profile")
|
|
200
|
+
.font(.title2)
|
|
201
|
+
.accessibilityIdentifier("profile_title")
|
|
130
202
|
|
|
131
203
|
TextField("Type here", text: $input)
|
|
132
204
|
.textFieldStyle(.roundedBorder)
|
|
133
205
|
.focused($inputFocused)
|
|
134
206
|
.accessibilityIdentifier("demo_input")
|
|
135
|
-
.padding(.horizontal, 32)
|
|
136
207
|
|
|
137
|
-
|
|
138
|
-
.
|
|
208
|
+
TextField("Name", text: $profileName)
|
|
209
|
+
.textFieldStyle(.roundedBorder)
|
|
210
|
+
.textInputAutocapitalization(.never)
|
|
211
|
+
.autocorrectionDisabled()
|
|
212
|
+
.accessibilityIdentifier("profile_name_input")
|
|
213
|
+
|
|
214
|
+
TextField("Email", text: $profileEmail)
|
|
215
|
+
.textFieldStyle(.roundedBorder)
|
|
216
|
+
.keyboardType(.emailAddress)
|
|
217
|
+
.textInputAutocapitalization(.never)
|
|
218
|
+
.autocorrectionDisabled()
|
|
219
|
+
.accessibilityIdentifier("profile_email_input")
|
|
220
|
+
|
|
221
|
+
Button("Save profile") {
|
|
222
|
+
status = "Profile saved"
|
|
223
|
+
screen = .catalog
|
|
224
|
+
}
|
|
225
|
+
.buttonStyle(.borderedProminent)
|
|
226
|
+
.accessibilityIdentifier("save_profile_button")
|
|
139
227
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private var catalogView: some View {
|
|
231
|
+
VStack(spacing: 14) {
|
|
232
|
+
Text("Catalog")
|
|
233
|
+
.font(.title2)
|
|
234
|
+
.accessibilityIdentifier("catalog_title")
|
|
235
|
+
|
|
236
|
+
ScrollView {
|
|
237
|
+
LazyVStack(spacing: 10) {
|
|
238
|
+
ForEach(catalogItems) { item in
|
|
239
|
+
Button {
|
|
240
|
+
selectedItem = item
|
|
241
|
+
status = "Selected \(item.title)"
|
|
242
|
+
screen = .detail
|
|
243
|
+
} label: {
|
|
244
|
+
VStack(alignment: .leading, spacing: 3) {
|
|
245
|
+
Text(item.title)
|
|
246
|
+
.font(.headline)
|
|
247
|
+
Text(item.subtitle)
|
|
248
|
+
.font(.subheadline)
|
|
249
|
+
.foregroundStyle(.secondary)
|
|
250
|
+
}
|
|
251
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
252
|
+
.padding()
|
|
253
|
+
}
|
|
254
|
+
.buttonStyle(.bordered)
|
|
255
|
+
.accessibilityIdentifier(catalogAccessibilityIdentifier(for: item))
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
.frame(maxHeight: 340)
|
|
260
|
+
.accessibilityIdentifier("catalog_list")
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private var detailView: some View {
|
|
265
|
+
VStack(spacing: 16) {
|
|
266
|
+
Text(selectedItem.title)
|
|
267
|
+
.font(.title2)
|
|
268
|
+
.accessibilityIdentifier("detail_title")
|
|
269
|
+
|
|
270
|
+
Text(selectedItem.subtitle)
|
|
271
|
+
.foregroundStyle(.secondary)
|
|
272
|
+
.accessibilityIdentifier("detail_subtitle")
|
|
273
|
+
|
|
274
|
+
Button("Save item") {
|
|
275
|
+
status = "Saved \(selectedItem.title)"
|
|
276
|
+
}
|
|
277
|
+
.buttonStyle(.borderedProminent)
|
|
278
|
+
.accessibilityIdentifier("detail_save_button")
|
|
279
|
+
|
|
280
|
+
Button("Review order") {
|
|
281
|
+
status = "Workflow complete"
|
|
282
|
+
screen = .review
|
|
283
|
+
}
|
|
284
|
+
.buttonStyle(.bordered)
|
|
285
|
+
.accessibilityIdentifier("review_button")
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private var reviewView: some View {
|
|
290
|
+
VStack(spacing: 16) {
|
|
291
|
+
Text("Review")
|
|
292
|
+
.font(.title2)
|
|
293
|
+
.accessibilityIdentifier("review_title")
|
|
294
|
+
|
|
295
|
+
Text("Workflow complete")
|
|
296
|
+
.font(.headline)
|
|
297
|
+
.accessibilityIdentifier("review_complete")
|
|
298
|
+
|
|
299
|
+
Text(selectedItem.title)
|
|
300
|
+
.foregroundStyle(.secondary)
|
|
301
|
+
.accessibilityIdentifier("review_item")
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private func catalogAccessibilityIdentifier(for item: CatalogItem) -> String {
|
|
306
|
+
if item.id == "north_ridge_pack" {
|
|
307
|
+
return "catalog_item_north_ridge_pack"
|
|
143
308
|
}
|
|
309
|
+
return "catalog_item_\(item.id)"
|
|
144
310
|
}
|
|
145
311
|
}
|
|
146
312
|
EOF
|
|
@@ -253,6 +419,7 @@ RUBY
|
|
|
253
419
|
|
|
254
420
|
cp "$ROOT/examples/ios-smoke.json" "$OUT/.zmr/ios-smoke.json"
|
|
255
421
|
cp "$ROOT/examples/ios-shim-smoke.json" "$OUT/.zmr/ios-shim-smoke.json"
|
|
422
|
+
cp "$ROOT/examples/ios-shim-workflow.json" "$OUT/.zmr/ios-shim-workflow.json"
|
|
256
423
|
|
|
257
424
|
echo "created iOS demo app at $OUT"
|
|
258
425
|
echo "project: $PROJECT_PATH"
|