zeno-mobile-runner 0.1.2
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 +497 -0
- package/CONTRIBUTING.md +42 -0
- package/FEATURES.md +111 -0
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/SECURITY.md +34 -0
- package/build.zig +38 -0
- package/build.zig.zon +7 -0
- package/clients/README.md +149 -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 +154 -0
- package/docs/app-integration.md +330 -0
- package/docs/benchmarking.md +273 -0
- package/docs/client-installation.md +133 -0
- package/docs/clients.md +98 -0
- package/docs/config.md +175 -0
- package/docs/demo.md +259 -0
- package/docs/frameworks.md +72 -0
- package/docs/install.md +95 -0
- package/docs/npm.md +356 -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/scenario-authoring.md +88 -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 +118 -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 +518 -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 +131 -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,342 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
OUT=""
|
|
6
|
+
APP_ID="com.example.mobiletest"
|
|
7
|
+
API="35"
|
|
8
|
+
BUILD_TOOLS="35.0.1"
|
|
9
|
+
ANDROID_SDK="${ANDROID_HOME:-$HOME/Library/Android/sdk}"
|
|
10
|
+
DRY_RUN=0
|
|
11
|
+
|
|
12
|
+
usage() {
|
|
13
|
+
cat <<'USAGE'
|
|
14
|
+
Usage:
|
|
15
|
+
scripts/create-android-demo-app.sh --out <dir> [options]
|
|
16
|
+
|
|
17
|
+
Creates a small public native Android demo app and a matching .zmr smoke
|
|
18
|
+
scenario. The generated app is intentionally generic and contains no private
|
|
19
|
+
app references. It uses Android SDK command-line tools directly, so it does not
|
|
20
|
+
need Gradle or network access.
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
--out <dir> Output app repository directory. Required.
|
|
24
|
+
--app-id <id> Android application id. Default: com.example.mobiletest.
|
|
25
|
+
--api <level> Android platform API level. Default: 35.
|
|
26
|
+
--build-tools <ver> Android build-tools version. Default: 35.0.1.
|
|
27
|
+
--android-sdk <path> Android SDK root. Default: ANDROID_HOME or ~/Library/Android/sdk.
|
|
28
|
+
--dry-run Print commands without executing them.
|
|
29
|
+
-h, --help Show this help.
|
|
30
|
+
|
|
31
|
+
After generation:
|
|
32
|
+
adb install -r <dir>/build/app-debug.apk
|
|
33
|
+
zmr run <dir>/.zmr/android-smoke.json --device emulator-5554 --app-id com.example.mobiletest --trace-dir <dir>/traces/android-demo
|
|
34
|
+
USAGE
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
die() {
|
|
38
|
+
echo "error: $*" >&2
|
|
39
|
+
exit 2
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
require_value() {
|
|
43
|
+
local flag="$1"
|
|
44
|
+
local value="${2-}"
|
|
45
|
+
if [[ -z "$value" || "$value" == --* ]]; then
|
|
46
|
+
die "$flag requires a value"
|
|
47
|
+
fi
|
|
48
|
+
printf '%s\n' "$value"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
quote_cmd() {
|
|
52
|
+
local quoted=()
|
|
53
|
+
local arg
|
|
54
|
+
for arg in "$@"; do
|
|
55
|
+
quoted+=("$(printf '%q' "$arg")")
|
|
56
|
+
done
|
|
57
|
+
printf '%s\n' "${quoted[*]}"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
run() {
|
|
61
|
+
echo "+ $(quote_cmd "$@")"
|
|
62
|
+
if [[ "$DRY_RUN" -eq 0 ]]; then
|
|
63
|
+
"$@"
|
|
64
|
+
fi
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
write_file() {
|
|
68
|
+
local path="$1"
|
|
69
|
+
local content="$2"
|
|
70
|
+
echo "+ write $path"
|
|
71
|
+
if [[ "$DRY_RUN" -eq 0 ]]; then
|
|
72
|
+
mkdir -p "$(dirname "$path")"
|
|
73
|
+
printf '%s' "$content" > "$path"
|
|
74
|
+
fi
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
while [[ $# -gt 0 ]]; do
|
|
78
|
+
case "$1" in
|
|
79
|
+
--out)
|
|
80
|
+
OUT="$(require_value "$1" "${2-}")"
|
|
81
|
+
shift 2
|
|
82
|
+
;;
|
|
83
|
+
--app-id)
|
|
84
|
+
APP_ID="$(require_value "$1" "${2-}")"
|
|
85
|
+
shift 2
|
|
86
|
+
;;
|
|
87
|
+
--api)
|
|
88
|
+
API="$(require_value "$1" "${2-}")"
|
|
89
|
+
shift 2
|
|
90
|
+
;;
|
|
91
|
+
--build-tools)
|
|
92
|
+
BUILD_TOOLS="$(require_value "$1" "${2-}")"
|
|
93
|
+
shift 2
|
|
94
|
+
;;
|
|
95
|
+
--android-sdk)
|
|
96
|
+
ANDROID_SDK="$(require_value "$1" "${2-}")"
|
|
97
|
+
shift 2
|
|
98
|
+
;;
|
|
99
|
+
--dry-run)
|
|
100
|
+
DRY_RUN=1
|
|
101
|
+
shift
|
|
102
|
+
;;
|
|
103
|
+
-h|--help)
|
|
104
|
+
usage
|
|
105
|
+
exit 0
|
|
106
|
+
;;
|
|
107
|
+
*)
|
|
108
|
+
die "unknown argument: $1"
|
|
109
|
+
;;
|
|
110
|
+
esac
|
|
111
|
+
done
|
|
112
|
+
|
|
113
|
+
[[ -n "$OUT" ]] || die "--out is required"
|
|
114
|
+
[[ "$APP_ID" =~ ^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$ ]] || die "--app-id must be a Java-style package id"
|
|
115
|
+
[[ "$API" =~ ^[0-9]+$ ]] || die "--api must be an integer"
|
|
116
|
+
[[ -n "$BUILD_TOOLS" ]] || die "--build-tools must be non-empty"
|
|
117
|
+
|
|
118
|
+
if [[ "$OUT" != /* ]]; then
|
|
119
|
+
OUT="$(pwd -P)/$OUT"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
ANDROID_DIR="$OUT/android"
|
|
123
|
+
SRC_DIR="$ANDROID_DIR/src/dev/zmr/demo"
|
|
124
|
+
RES_DIR="$ANDROID_DIR/res"
|
|
125
|
+
BUILD_DIR="$OUT/build"
|
|
126
|
+
GEN_DIR="$BUILD_DIR/generated"
|
|
127
|
+
CLASSES_DIR="$BUILD_DIR/classes"
|
|
128
|
+
DEX_DIR="$BUILD_DIR/dex"
|
|
129
|
+
COMPILED_RES="$BUILD_DIR/compiled-res.zip"
|
|
130
|
+
UNSIGNED_APK="$BUILD_DIR/app-unsigned.apk"
|
|
131
|
+
SIGNED_APK="$BUILD_DIR/app-debug.apk"
|
|
132
|
+
KEYSTORE="$BUILD_DIR/debug.keystore"
|
|
133
|
+
ANDROID_JAR="$ANDROID_SDK/platforms/android-$API/android.jar"
|
|
134
|
+
BUILD_TOOLS_DIR="$ANDROID_SDK/build-tools/$BUILD_TOOLS"
|
|
135
|
+
AAPT2="$BUILD_TOOLS_DIR/aapt2"
|
|
136
|
+
D8="$BUILD_TOOLS_DIR/d8"
|
|
137
|
+
APKSIGNER="$BUILD_TOOLS_DIR/apksigner"
|
|
138
|
+
ZMR_BIN="${ZMR_BIN:-}"
|
|
139
|
+
|
|
140
|
+
echo "Android demo app: $OUT"
|
|
141
|
+
echo "Android demo APK: $SIGNED_APK"
|
|
142
|
+
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
143
|
+
echo "DRY RUN: commands will be printed but not executed"
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
if [[ "$DRY_RUN" -eq 0 ]]; then
|
|
147
|
+
[[ -f "$ANDROID_JAR" ]] || die "android.jar not found: $ANDROID_JAR"
|
|
148
|
+
[[ -x "$AAPT2" ]] || die "aapt2 not found: $AAPT2"
|
|
149
|
+
[[ -x "$D8" ]] || die "d8 not found: $D8"
|
|
150
|
+
[[ -x "$APKSIGNER" ]] || die "apksigner not found: $APKSIGNER"
|
|
151
|
+
command -v javac >/dev/null 2>&1 || die "javac is required"
|
|
152
|
+
command -v keytool >/dev/null 2>&1 || die "keytool is required"
|
|
153
|
+
command -v zip >/dev/null 2>&1 || die "zip is required"
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
if [[ "$DRY_RUN" -eq 0 ]]; then
|
|
157
|
+
rm -rf "$BUILD_DIR"
|
|
158
|
+
fi
|
|
159
|
+
run mkdir -p "$SRC_DIR" "$RES_DIR/values" "$BUILD_DIR" "$GEN_DIR" "$CLASSES_DIR" "$DEX_DIR" "$OUT/.zmr"
|
|
160
|
+
|
|
161
|
+
write_file "$ANDROID_DIR/AndroidManifest.xml" "$(cat <<EOF
|
|
162
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
163
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="$APP_ID">
|
|
164
|
+
<uses-sdk android:minSdkVersion="23" android:targetSdkVersion="$API" />
|
|
165
|
+
<application android:theme="@style/AppTheme" android:label="ZMR Android Demo" android:allowBackup="false" android:supportsRtl="true">
|
|
166
|
+
<activity android:name="dev.zmr.demo.MainActivity" android:exported="true">
|
|
167
|
+
<intent-filter>
|
|
168
|
+
<action android:name="android.intent.action.MAIN" />
|
|
169
|
+
<category android:name="android.intent.category.LAUNCHER" />
|
|
170
|
+
</intent-filter>
|
|
171
|
+
<intent-filter>
|
|
172
|
+
<action android:name="android.intent.action.VIEW" />
|
|
173
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
174
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
175
|
+
<data android:scheme="exampleapp" />
|
|
176
|
+
</intent-filter>
|
|
177
|
+
</activity>
|
|
178
|
+
</application>
|
|
179
|
+
</manifest>
|
|
180
|
+
EOF
|
|
181
|
+
)"
|
|
182
|
+
|
|
183
|
+
write_file "$RES_DIR/values/styles.xml" "$(cat <<'EOF'
|
|
184
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
185
|
+
<resources>
|
|
186
|
+
<style name="AppTheme" parent="android:style/Theme.Material.Light.NoActionBar">
|
|
187
|
+
<item name="android:fontFamily">sans</item>
|
|
188
|
+
<item name="android:windowLightStatusBar">true</item>
|
|
189
|
+
<item name="android:colorAccent">#2563EB</item>
|
|
190
|
+
</style>
|
|
191
|
+
</resources>
|
|
192
|
+
EOF
|
|
193
|
+
)"
|
|
194
|
+
|
|
195
|
+
write_file "$RES_DIR/values/ids.xml" "$(cat <<'EOF'
|
|
196
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
197
|
+
<resources>
|
|
198
|
+
<item name="demo_title" type="id" />
|
|
199
|
+
<item name="continue_button" type="id" />
|
|
200
|
+
<item name="demo_input" type="id" />
|
|
201
|
+
<item name="demo_status" type="id" />
|
|
202
|
+
</resources>
|
|
203
|
+
EOF
|
|
204
|
+
)"
|
|
205
|
+
|
|
206
|
+
write_file "$SRC_DIR/MainActivity.java" "$(cat <<EOF
|
|
207
|
+
package dev.zmr.demo;
|
|
208
|
+
|
|
209
|
+
import android.app.Activity;
|
|
210
|
+
import android.graphics.Color;
|
|
211
|
+
import android.net.Uri;
|
|
212
|
+
import android.os.Bundle;
|
|
213
|
+
import android.view.View;
|
|
214
|
+
import android.view.Gravity;
|
|
215
|
+
import android.view.inputmethod.InputMethodManager;
|
|
216
|
+
import android.content.Context;
|
|
217
|
+
import android.widget.Button;
|
|
218
|
+
import android.widget.EditText;
|
|
219
|
+
import android.widget.LinearLayout;
|
|
220
|
+
import android.widget.TextView;
|
|
221
|
+
|
|
222
|
+
public class MainActivity extends Activity {
|
|
223
|
+
private TextView status;
|
|
224
|
+
private EditText input;
|
|
225
|
+
|
|
226
|
+
@Override
|
|
227
|
+
protected void onCreate(Bundle savedInstanceState) {
|
|
228
|
+
super.onCreate(savedInstanceState);
|
|
229
|
+
|
|
230
|
+
LinearLayout layout = new LinearLayout(this);
|
|
231
|
+
layout.setOrientation(LinearLayout.VERTICAL);
|
|
232
|
+
layout.setGravity(Gravity.CENTER_HORIZONTAL);
|
|
233
|
+
int padding = dp(24);
|
|
234
|
+
layout.setPadding(padding, padding, padding, padding);
|
|
235
|
+
|
|
236
|
+
TextView title = new TextView(this);
|
|
237
|
+
title.setId(R.id.demo_title);
|
|
238
|
+
title.setText("ZMR Android Demo");
|
|
239
|
+
title.setTextSize(24);
|
|
240
|
+
title.setTextColor(Color.rgb(17, 24, 39));
|
|
241
|
+
title.setGravity(Gravity.CENTER);
|
|
242
|
+
layout.addView(title, new LinearLayout.LayoutParams(-1, -2));
|
|
243
|
+
|
|
244
|
+
Button button = new Button(this);
|
|
245
|
+
button.setId(R.id.continue_button);
|
|
246
|
+
button.setText("Continue");
|
|
247
|
+
layout.addView(button, new LinearLayout.LayoutParams(-1, dp(56)));
|
|
248
|
+
|
|
249
|
+
input = new EditText(this);
|
|
250
|
+
input.setId(R.id.demo_input);
|
|
251
|
+
input.setHint("Type here");
|
|
252
|
+
input.setSingleLine(true);
|
|
253
|
+
layout.addView(input, new LinearLayout.LayoutParams(-1, dp(56)));
|
|
254
|
+
|
|
255
|
+
status = new TextView(this);
|
|
256
|
+
status.setId(R.id.demo_status);
|
|
257
|
+
status.setText("Ready");
|
|
258
|
+
status.setTextSize(18);
|
|
259
|
+
status.setGravity(Gravity.CENTER);
|
|
260
|
+
layout.addView(status, new LinearLayout.LayoutParams(-1, -2));
|
|
261
|
+
|
|
262
|
+
button.setOnClickListener(new View.OnClickListener() {
|
|
263
|
+
@Override
|
|
264
|
+
public void onClick(View view) {
|
|
265
|
+
status.setText("Continue tapped");
|
|
266
|
+
input.requestFocus();
|
|
267
|
+
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
268
|
+
if (imm != null) {
|
|
269
|
+
imm.showSoftInput(input, InputMethodManager.SHOW_IMPLICIT);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
Uri data = getIntent().getData();
|
|
275
|
+
if (data != null) {
|
|
276
|
+
status.setText("Deep link opened");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
setContentView(layout);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private int dp(int value) {
|
|
283
|
+
return (int) (value * getResources().getDisplayMetrics().density + 0.5f);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
EOF
|
|
287
|
+
)"
|
|
288
|
+
|
|
289
|
+
write_file "$OUT/.zmr/android-smoke.json" "$(cat <<EOF
|
|
290
|
+
{
|
|
291
|
+
"name": "ZMR Android demo smoke",
|
|
292
|
+
"appId": "$APP_ID",
|
|
293
|
+
"steps": [
|
|
294
|
+
{ "action": "launch" },
|
|
295
|
+
{ "action": "waitVisible", "selector": { "text": "ZMR Android Demo" }, "timeoutMs": 30000 },
|
|
296
|
+
{ "action": "tap", "selector": { "resourceId": "$APP_ID:id/continue_button" } },
|
|
297
|
+
{ "action": "waitVisible", "selector": { "text": "Continue tapped" }, "timeoutMs": 10000 },
|
|
298
|
+
{ "action": "typeText", "selector": { "resourceId": "$APP_ID:id/demo_input" }, "text": "hello from zmr" },
|
|
299
|
+
{ "action": "snapshot" }
|
|
300
|
+
]
|
|
301
|
+
}
|
|
302
|
+
EOF
|
|
303
|
+
)"
|
|
304
|
+
|
|
305
|
+
run "$AAPT2" compile --dir "$RES_DIR" -o "$COMPILED_RES"
|
|
306
|
+
run "$AAPT2" link -o "$UNSIGNED_APK" -I "$ANDROID_JAR" --manifest "$ANDROID_DIR/AndroidManifest.xml" -R "$COMPILED_RES" --java "$GEN_DIR" --custom-package dev.zmr.demo --auto-add-overlay
|
|
307
|
+
run javac -source 1.8 -target 1.8 -bootclasspath "$ANDROID_JAR" -d "$CLASSES_DIR" "$GEN_DIR/dev/zmr/demo/R.java" "$SRC_DIR/MainActivity.java"
|
|
308
|
+
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
309
|
+
CLASS_FILES=(
|
|
310
|
+
"$CLASSES_DIR/dev/zmr/demo/R.class"
|
|
311
|
+
"$CLASSES_DIR/dev/zmr/demo/MainActivity.class"
|
|
312
|
+
"$CLASSES_DIR/dev/zmr/demo/MainActivity\$1.class"
|
|
313
|
+
)
|
|
314
|
+
else
|
|
315
|
+
CLASS_FILES=()
|
|
316
|
+
while IFS= read -r class_file; do
|
|
317
|
+
CLASS_FILES+=("$class_file")
|
|
318
|
+
done < <(find "$CLASSES_DIR" -name '*.class' -print | sort)
|
|
319
|
+
[[ "${#CLASS_FILES[@]}" -gt 0 ]] || die "no compiled Java classes found in $CLASSES_DIR"
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
run "$D8" --lib "$ANDROID_JAR" --min-api 23 --output "$DEX_DIR" "${CLASS_FILES[@]}"
|
|
323
|
+
run zip -j "$UNSIGNED_APK" "$DEX_DIR/classes.dex"
|
|
324
|
+
run keytool -genkeypair -keystore "$KEYSTORE" -storepass android -keypass android -alias zmrdebug -keyalg RSA -keysize 2048 -validity 10000 -dname "CN=ZMR Android Demo,O=ZMR,C=US"
|
|
325
|
+
run "$APKSIGNER" sign --ks "$KEYSTORE" --ks-key-alias zmrdebug --ks-pass pass:android --key-pass pass:android --out "$SIGNED_APK" "$UNSIGNED_APK"
|
|
326
|
+
if [[ -z "$ZMR_BIN" ]]; then
|
|
327
|
+
if [[ -x "$ROOT/zig-out/bin/zmr" ]]; then
|
|
328
|
+
ZMR_BIN="$ROOT/zig-out/bin/zmr"
|
|
329
|
+
elif command -v zmr >/dev/null 2>&1; then
|
|
330
|
+
ZMR_BIN="$(command -v zmr)"
|
|
331
|
+
fi
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
if [[ -n "$ZMR_BIN" ]]; then
|
|
335
|
+
run "$ZMR_BIN" validate "$OUT/.zmr/android-smoke.json"
|
|
336
|
+
else
|
|
337
|
+
echo "warning: skipped scenario validation because zmr was not found; run 'zmr validate $OUT/.zmr/android-smoke.json' after installation" >&2
|
|
338
|
+
fi
|
|
339
|
+
|
|
340
|
+
echo "created Android demo app at $OUT"
|
|
341
|
+
echo "apk: $SIGNED_APK"
|
|
342
|
+
echo "scenario: $OUT/.zmr/android-smoke.json"
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SOURCE="${BASH_SOURCE[0]}"
|
|
5
|
+
while [[ -h "$SOURCE" ]]; do
|
|
6
|
+
SOURCE_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
|
7
|
+
SOURCE="$(readlink "$SOURCE")"
|
|
8
|
+
if [[ "$SOURCE" != /* ]]; then
|
|
9
|
+
SOURCE="$SOURCE_DIR/$SOURCE"
|
|
10
|
+
fi
|
|
11
|
+
done
|
|
12
|
+
|
|
13
|
+
ROOT="$(cd -P "$(dirname "$SOURCE")/.." && pwd)"
|
|
14
|
+
OUT=""
|
|
15
|
+
APP_NAME="ZMRDemo"
|
|
16
|
+
BUNDLE_ID="com.example.mobiletest"
|
|
17
|
+
DEPLOYMENT_TARGET="16.0"
|
|
18
|
+
|
|
19
|
+
usage() {
|
|
20
|
+
cat <<'USAGE'
|
|
21
|
+
Usage:
|
|
22
|
+
scripts/create-ios-demo-app.sh --out <dir> [options]
|
|
23
|
+
|
|
24
|
+
Creates a small public SwiftUI iOS simulator demo app and installs the ZMR
|
|
25
|
+
XCTest shim into it. The generated app is intentionally generic and contains no
|
|
26
|
+
private app references.
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
--out <dir> Output app repository directory. Required.
|
|
30
|
+
--name <name> App target name. Default: ZMRDemo.
|
|
31
|
+
--bundle-id <id> App bundle id. Default: com.example.mobiletest.
|
|
32
|
+
--deployment-target <ver> iOS deployment target. Default: 16.0.
|
|
33
|
+
-h, --help Show this help.
|
|
34
|
+
|
|
35
|
+
After generation:
|
|
36
|
+
cd <dir>
|
|
37
|
+
xcodebuild -project ios/ZMRDemo.xcodeproj -scheme ZMRDemo -destination 'generic/platform=iOS Simulator' -derivedDataPath DerivedData build
|
|
38
|
+
xcrun simctl install booted DerivedData/Build/Products/Debug-iphonesimulator/ZMRDemo.app
|
|
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
|
+
USAGE
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
die() {
|
|
44
|
+
echo "error: $*" >&2
|
|
45
|
+
exit 2
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
require_value() {
|
|
49
|
+
local flag="$1"
|
|
50
|
+
local value="${2-}"
|
|
51
|
+
if [[ -z "$value" || "$value" == --* ]]; then
|
|
52
|
+
die "$flag requires a value"
|
|
53
|
+
fi
|
|
54
|
+
printf '%s\n' "$value"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
while [[ $# -gt 0 ]]; do
|
|
58
|
+
case "$1" in
|
|
59
|
+
--out)
|
|
60
|
+
OUT="$(require_value "$1" "${2-}")"
|
|
61
|
+
shift 2
|
|
62
|
+
;;
|
|
63
|
+
--name)
|
|
64
|
+
APP_NAME="$(require_value "$1" "${2-}")"
|
|
65
|
+
shift 2
|
|
66
|
+
;;
|
|
67
|
+
--bundle-id)
|
|
68
|
+
BUNDLE_ID="$(require_value "$1" "${2-}")"
|
|
69
|
+
shift 2
|
|
70
|
+
;;
|
|
71
|
+
--deployment-target)
|
|
72
|
+
DEPLOYMENT_TARGET="$(require_value "$1" "${2-}")"
|
|
73
|
+
shift 2
|
|
74
|
+
;;
|
|
75
|
+
-h|--help)
|
|
76
|
+
usage
|
|
77
|
+
exit 0
|
|
78
|
+
;;
|
|
79
|
+
*)
|
|
80
|
+
die "unknown argument: $1"
|
|
81
|
+
;;
|
|
82
|
+
esac
|
|
83
|
+
done
|
|
84
|
+
|
|
85
|
+
[[ -n "$OUT" ]] || die "--out is required"
|
|
86
|
+
[[ "$APP_NAME" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || die "--name must be a valid Swift identifier"
|
|
87
|
+
if ! ruby -e 'require "xcodeproj"' >/dev/null 2>&1; then
|
|
88
|
+
die "Ruby gem xcodeproj is required. Install it with: gem install xcodeproj"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
mkdir -p "$OUT/ios/$APP_NAME"
|
|
92
|
+
OUT="$(cd "$OUT" && pwd)"
|
|
93
|
+
PROJECT_PATH="$OUT/ios/$APP_NAME.xcodeproj"
|
|
94
|
+
SOURCE_DIR="$OUT/ios/$APP_NAME"
|
|
95
|
+
TEST_SCHEME="${APP_NAME}ZMRUITests"
|
|
96
|
+
|
|
97
|
+
cat > "$SOURCE_DIR/${APP_NAME}App.swift" <<EOF
|
|
98
|
+
import SwiftUI
|
|
99
|
+
|
|
100
|
+
@main
|
|
101
|
+
struct ${APP_NAME}App: App {
|
|
102
|
+
var body: some Scene {
|
|
103
|
+
WindowGroup {
|
|
104
|
+
ContentView()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
EOF
|
|
109
|
+
|
|
110
|
+
cat > "$SOURCE_DIR/ContentView.swift" <<'EOF'
|
|
111
|
+
import SwiftUI
|
|
112
|
+
|
|
113
|
+
struct ContentView: View {
|
|
114
|
+
@State private var input = ""
|
|
115
|
+
@State private var status = "Ready"
|
|
116
|
+
@FocusState private var inputFocused: Bool
|
|
117
|
+
|
|
118
|
+
var body: some View {
|
|
119
|
+
VStack(spacing: 20) {
|
|
120
|
+
Text("ZMR iOS Demo")
|
|
121
|
+
.font(.title)
|
|
122
|
+
.accessibilityIdentifier("demo_title")
|
|
123
|
+
|
|
124
|
+
Button("Continue") {
|
|
125
|
+
status = "Continue tapped"
|
|
126
|
+
inputFocused = true
|
|
127
|
+
}
|
|
128
|
+
.buttonStyle(.borderedProminent)
|
|
129
|
+
.accessibilityIdentifier("continue_button")
|
|
130
|
+
|
|
131
|
+
TextField("Type here", text: $input)
|
|
132
|
+
.textFieldStyle(.roundedBorder)
|
|
133
|
+
.focused($inputFocused)
|
|
134
|
+
.accessibilityIdentifier("demo_input")
|
|
135
|
+
.padding(.horizontal, 32)
|
|
136
|
+
|
|
137
|
+
Text(status)
|
|
138
|
+
.accessibilityIdentifier("demo_status")
|
|
139
|
+
}
|
|
140
|
+
.padding()
|
|
141
|
+
.onOpenURL { _ in
|
|
142
|
+
status = "Deep link opened"
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
EOF
|
|
147
|
+
|
|
148
|
+
cat > "$SOURCE_DIR/Info.plist" <<EOF
|
|
149
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
150
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
151
|
+
<plist version="1.0">
|
|
152
|
+
<dict>
|
|
153
|
+
<key>CFBundleDevelopmentRegion</key>
|
|
154
|
+
<string>\$(DEVELOPMENT_LANGUAGE)</string>
|
|
155
|
+
<key>CFBundleExecutable</key>
|
|
156
|
+
<string>\$(EXECUTABLE_NAME)</string>
|
|
157
|
+
<key>CFBundleIdentifier</key>
|
|
158
|
+
<string>\$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
159
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
|
160
|
+
<string>6.0</string>
|
|
161
|
+
<key>CFBundleName</key>
|
|
162
|
+
<string>\$(PRODUCT_NAME)</string>
|
|
163
|
+
<key>CFBundlePackageType</key>
|
|
164
|
+
<string>APPL</string>
|
|
165
|
+
<key>CFBundleShortVersionString</key>
|
|
166
|
+
<string>1.0</string>
|
|
167
|
+
<key>CFBundleURLTypes</key>
|
|
168
|
+
<array>
|
|
169
|
+
<dict>
|
|
170
|
+
<key>CFBundleURLName</key>
|
|
171
|
+
<string>${BUNDLE_ID}</string>
|
|
172
|
+
<key>CFBundleURLSchemes</key>
|
|
173
|
+
<array>
|
|
174
|
+
<string>exampleapp</string>
|
|
175
|
+
</array>
|
|
176
|
+
</dict>
|
|
177
|
+
</array>
|
|
178
|
+
<key>CFBundleVersion</key>
|
|
179
|
+
<string>1</string>
|
|
180
|
+
<key>UILaunchScreen</key>
|
|
181
|
+
<dict/>
|
|
182
|
+
</dict>
|
|
183
|
+
</plist>
|
|
184
|
+
EOF
|
|
185
|
+
|
|
186
|
+
ruby - "$PROJECT_PATH" "$APP_NAME" "$BUNDLE_ID" "$DEPLOYMENT_TARGET" <<'RUBY'
|
|
187
|
+
require "fileutils"
|
|
188
|
+
require "xcodeproj"
|
|
189
|
+
|
|
190
|
+
project_path, app_name, bundle_id, deployment_target = ARGV
|
|
191
|
+
project = Xcodeproj::Project.new(project_path)
|
|
192
|
+
target = project.new_target(:application, app_name, :ios, deployment_target)
|
|
193
|
+
group = project.main_group.new_group(app_name, app_name)
|
|
194
|
+
|
|
195
|
+
["#{app_name}App.swift", "ContentView.swift"].each do |source|
|
|
196
|
+
file_ref = group.new_file(source)
|
|
197
|
+
target.add_file_references([file_ref])
|
|
198
|
+
end
|
|
199
|
+
group.new_file("Info.plist")
|
|
200
|
+
|
|
201
|
+
target.build_configurations.each do |configuration|
|
|
202
|
+
settings = configuration.build_settings
|
|
203
|
+
settings["PRODUCT_BUNDLE_IDENTIFIER"] = bundle_id
|
|
204
|
+
settings["PRODUCT_NAME"] = "$(TARGET_NAME)"
|
|
205
|
+
settings["SWIFT_VERSION"] = "5.0"
|
|
206
|
+
settings["GENERATE_INFOPLIST_FILE"] = "NO"
|
|
207
|
+
settings["INFOPLIST_FILE"] = "#{app_name}/Info.plist"
|
|
208
|
+
settings["TARGETED_DEVICE_FAMILY"] = "1,2"
|
|
209
|
+
settings["CODE_SIGNING_ALLOWED"] = "NO"
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
project.save
|
|
213
|
+
scheme_dir = File.join(project.path, "xcshareddata/xcschemes")
|
|
214
|
+
FileUtils.mkdir_p(scheme_dir)
|
|
215
|
+
scheme_path = File.join(scheme_dir, "#{app_name}.xcscheme")
|
|
216
|
+
File.write(scheme_path, <<~XML)
|
|
217
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
218
|
+
<Scheme LastUpgradeVersion = "1600" version = "1.7">
|
|
219
|
+
<BuildAction parallelizeBuildables = "YES" buildImplicitDependencies = "YES">
|
|
220
|
+
<BuildActionEntries>
|
|
221
|
+
<BuildActionEntry buildForTesting = "YES" buildForRunning = "YES" buildForProfiling = "YES" buildForArchiving = "YES" buildForAnalyzing = "YES">
|
|
222
|
+
<BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "#{target.uuid}" BuildableName = "#{app_name}.app" BlueprintName = "#{app_name}" ReferencedContainer = "container:#{File.basename(project.path)}">
|
|
223
|
+
</BuildableReference>
|
|
224
|
+
</BuildActionEntry>
|
|
225
|
+
</BuildActionEntries>
|
|
226
|
+
</BuildAction>
|
|
227
|
+
<LaunchAction buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES">
|
|
228
|
+
<BuildableProductRunnable runnableDebuggingMode = "0">
|
|
229
|
+
<BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "#{target.uuid}" BuildableName = "#{app_name}.app" BlueprintName = "#{app_name}" ReferencedContainer = "container:#{File.basename(project.path)}">
|
|
230
|
+
</BuildableReference>
|
|
231
|
+
</BuildableProductRunnable>
|
|
232
|
+
</LaunchAction>
|
|
233
|
+
<ProfileAction buildConfiguration = "Release" shouldUseLaunchSchemeArgsEnv = "YES" savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES">
|
|
234
|
+
<BuildableProductRunnable runnableDebuggingMode = "0">
|
|
235
|
+
<BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "#{target.uuid}" BuildableName = "#{app_name}.app" BlueprintName = "#{app_name}" ReferencedContainer = "container:#{File.basename(project.path)}">
|
|
236
|
+
</BuildableReference>
|
|
237
|
+
</BuildableProductRunnable>
|
|
238
|
+
</ProfileAction>
|
|
239
|
+
</Scheme>
|
|
240
|
+
XML
|
|
241
|
+
RUBY
|
|
242
|
+
|
|
243
|
+
"$ROOT/scripts/install-ios-shim.sh" \
|
|
244
|
+
--app-root "$OUT" \
|
|
245
|
+
--scheme "$TEST_SCHEME" \
|
|
246
|
+
--test-target "$TEST_SCHEME" \
|
|
247
|
+
--project "ios/$APP_NAME.xcodeproj" \
|
|
248
|
+
--app-target "$APP_NAME" \
|
|
249
|
+
--bundle-id "$BUNDLE_ID" \
|
|
250
|
+
--test-bundle-id "$BUNDLE_ID.zmr-uitests" \
|
|
251
|
+
--deployment-target "$DEPLOYMENT_TARGET" \
|
|
252
|
+
--patch-xcodeproj
|
|
253
|
+
|
|
254
|
+
cp "$ROOT/examples/ios-smoke.json" "$OUT/.zmr/ios-smoke.json"
|
|
255
|
+
cp "$ROOT/examples/ios-shim-smoke.json" "$OUT/.zmr/ios-shim-smoke.json"
|
|
256
|
+
|
|
257
|
+
echo "created iOS demo app at $OUT"
|
|
258
|
+
echo "project: $PROJECT_PATH"
|
|
259
|
+
echo "app scheme: $APP_NAME"
|
|
260
|
+
echo "shim scheme: $TEST_SCHEME"
|
|
261
|
+
echo "bundle id: $BUNDLE_ID"
|