@aarwitz/tapp 0.15.0 → 0.15.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/Harness/OCQAHarness/AppDelegate.swift +1 -1
- package/Harness/OCQAHarness.xcodeproj/project.pbxproj +2 -2
- package/Harness/OCQAHarnessUITests/ExplorerTests.swift +2 -2
- package/Harness/generate-harness-xcodeproj.rb +3 -3
- package/README.md +6 -7
- package/bin/tapp.js +16 -16
- package/docs/BROWSER-PRODUCT.md +1 -1
- package/docs/application-model.md +1 -6
- package/mcp-server/src/browser-product.js +1 -1
- package/mcp-server/src/enrich.js +1 -1
- package/mcp-server/src/index.js +35 -35
- package/mcp-server/src/product-execution.js +1 -1
- package/mcp-server/src/product-operations.js +1 -1
- package/mcp-server/src/report.js +3 -3
- package/package.json +15 -3
- package/scripts/ci-gate.sh +3 -3
- package/scripts/flow_ai_judge.py +1 -1
- package/scripts/quick-capture.sh +15 -14
- package/scripts/run-flow.sh +6 -6
- package/scripts/android-corpus-e2e.sh +0 -30
- package/scripts/cleanup-xcode.sh +0 -157
- package/scripts/corpus-apps.txt +0 -9
- package/scripts/corpus-sweep.sh +0 -121
- package/scripts/coverage-eval.sh +0 -92
- package/scripts/coverage_eval_parse.py +0 -95
- package/scripts/deploy-and-build.sh +0 -99
- package/scripts/mutation-recall-desktop.sh +0 -186
- package/scripts/mutation-recall.sh +0 -121
- package/scripts/mutation_lib.py +0 -128
- package/scripts/mutation_operators.py +0 -144
- package/scripts/validation-matrix.sh +0 -146
- package/scripts/vision-fp-eval.sh +0 -206
- package/scripts/vision_escalation_responder.py +0 -147
- package/scripts/vision_fp_probe.py +0 -221
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aarwitz/tapp",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"mcpName": "io.github.aarwitz/tapp",
|
|
5
5
|
"description": "Release contracts, autonomous QA, and evidence-backed CI gates for iOS, Android, and web.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,7 +24,19 @@
|
|
|
24
24
|
"docs/application-model.md",
|
|
25
25
|
"docs/scenarios.md",
|
|
26
26
|
"mcp-server/src/",
|
|
27
|
-
"scripts/",
|
|
27
|
+
"scripts/ci-gate.sh",
|
|
28
|
+
"scripts/compile-contract.js",
|
|
29
|
+
"scripts/compile-flow.js",
|
|
30
|
+
"scripts/flow-platform.js",
|
|
31
|
+
"scripts/flow_ai_judge.py",
|
|
32
|
+
"scripts/flow_lib.py",
|
|
33
|
+
"scripts/platform-gate.js",
|
|
34
|
+
"scripts/pr-plan.js",
|
|
35
|
+
"scripts/quick-capture.sh",
|
|
36
|
+
"scripts/run-android-flow.js",
|
|
37
|
+
"scripts/run-flow.sh",
|
|
38
|
+
"scripts/run-web-flow.js",
|
|
39
|
+
"scripts/run-web-scenario.js",
|
|
28
40
|
"Harness/OCQAHarness/",
|
|
29
41
|
"Harness/OCQAHarnessUITests/",
|
|
30
42
|
"Harness/OCQAHarness.xcodeproj/",
|
|
@@ -75,7 +87,7 @@
|
|
|
75
87
|
"mobile"
|
|
76
88
|
],
|
|
77
89
|
"scripts": {
|
|
78
|
-
"test": "node --test tests/report.test.js tests/regression.test.js tests/engine.test.js tests/project-config.test.js tests/application-model.test.js tests/ui-map.test.js tests/task-runtime.test.js tests/release-contract.test.js tests/pr-selection.test.js tests/flow-runtime.test.js tests/web-explorer.test.js tests/web-session.test.js tests/web-flow.test.js tests/scenario-runtime.test.js tests/android-driver.test.js tests/android-explorer.test.js tests/android-flow.test.js tests/managed-web.test.js tests/product-operations.test.js tests/browser-product.test.js tests/browser-onboarding.test.js tests/managed-operation.test.js tests/cloud-runner.test.js tests/ci-setup.test.js tests/ci-install.test.js tests/cli.test.js tests/action.test.js tests/ci-gate.test.js tests/ci-report.test.js tests/desktop-protocol.test.js tests/ios-flow-protocol.test.js",
|
|
90
|
+
"test": "node --test tests/report.test.js tests/regression.test.js tests/engine.test.js tests/project-config.test.js tests/application-model.test.js tests/ui-map.test.js tests/task-runtime.test.js tests/release-contract.test.js tests/pr-selection.test.js tests/flow-runtime.test.js tests/web-explorer.test.js tests/web-session.test.js tests/web-flow.test.js tests/scenario-runtime.test.js tests/android-driver.test.js tests/android-explorer.test.js tests/android-flow.test.js tests/managed-web.test.js tests/product-operations.test.js tests/browser-product.test.js tests/browser-onboarding.test.js tests/managed-operation.test.js tests/cloud-runner.test.js tests/ci-setup.test.js tests/ci-install.test.js tests/cli.test.js tests/action.test.js tests/package-surface.test.js tests/ci-gate.test.js tests/ci-report.test.js tests/desktop-protocol.test.js tests/ios-flow-protocol.test.js",
|
|
79
91
|
"test:browser-journey": "node --test tests/browser-journey.test.js",
|
|
80
92
|
"test:browser-native": "TAPP_RUN_NATIVE_BROWSER=1 node --test tests/browser-native-journey.test.js"
|
|
81
93
|
}
|
package/scripts/ci-gate.sh
CHANGED
|
@@ -254,7 +254,7 @@ print(named[0] if named else (iphones[0] if iphones else ''))
|
|
|
254
254
|
if [[ -z "$UDID" ]]; then
|
|
255
255
|
RUNTIME="$(xcrun simctl list runtimes -j | python3 -c 'import sys,json; rts=[r for r in json.load(sys.stdin)["runtimes"] if r.get("isAvailable") and r["platform"]=="iOS"]; print(rts[-1]["identifier"] if rts else "")')"
|
|
256
256
|
[[ -n "$RUNTIME" ]] || { echo "❌ No iOS simulator runtime available" >&2; exit 1; }
|
|
257
|
-
UDID="$(xcrun simctl create "
|
|
257
|
+
UDID="$(xcrun simctl create "Tapp CI" "$DEVICE" "$RUNTIME")" || { echo "❌ Could not create simulator" >&2; exit 1; }
|
|
258
258
|
fi
|
|
259
259
|
echo "Booting $UDID …"
|
|
260
260
|
xcrun simctl boot "$UDID" || true
|
|
@@ -269,7 +269,7 @@ xcrun simctl install "$UDID" "$APP_PATH" || { echo "❌ simctl install failed
|
|
|
269
269
|
# ── Autonomous exploration (quick-capture builds the harness itself if needed).
|
|
270
270
|
step "Explore ($ACTIONS actions, ${TIMEOUT}s watchdog)"
|
|
271
271
|
set +e
|
|
272
|
-
CAPTURE_ROOT="${AUTOTAP_HOME:-$ROOT}/captures"
|
|
272
|
+
CAPTURE_ROOT="${TAPP_HOME:-${AUTOTAP_HOME:-$ROOT}}/captures"
|
|
273
273
|
mkdir -p "$CAPTURE_ROOT"
|
|
274
274
|
CAPTURE_DIR="$(mktemp -d "$CAPTURE_ROOT/ci.XXXXXX")"
|
|
275
275
|
TAPP_CAPTURE_DIR="$CAPTURE_DIR" OCQA_PR_TARGET_JSON="$IOS_PR_TARGET_JSON" "$ROOT/scripts/quick-capture.sh" explore "$BUNDLE_ID" --actions "$ACTIONS" --timeout "$TIMEOUT"
|
|
@@ -282,7 +282,7 @@ echo "Markers: $MARKERS"
|
|
|
282
282
|
FLOW_LOG_ARGS=()
|
|
283
283
|
if [[ "${#FLOW_FILES[@]}" -gt 0 ]]; then
|
|
284
284
|
step "Flows"
|
|
285
|
-
FLOW_LOG_DIR="$(mktemp -d /tmp/
|
|
285
|
+
FLOW_LOG_DIR="$(mktemp -d /tmp/tapp-ci-flows.XXXXXX)"
|
|
286
286
|
for flow in "${FLOW_FILES[@]}"; do
|
|
287
287
|
name="$(basename "$flow" .yml)"
|
|
288
288
|
log="$FLOW_LOG_DIR/$name.log"
|
package/scripts/flow_ai_judge.py
CHANGED
|
@@ -26,7 +26,7 @@ def _ctx():
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
SSL_CTX = _ctx()
|
|
29
|
-
MODEL = os.environ.get("AUTOTAP_VISION_MODEL", "claude-haiku-4-5-20251001")
|
|
29
|
+
MODEL = os.environ.get("TAPP_VISION_MODEL") or os.environ.get("AUTOTAP_VISION_MODEL", "claude-haiku-4-5-20251001")
|
|
30
30
|
SYSTEM = (
|
|
31
31
|
"You are a QA test oracle. You are shown one iOS app screenshot and a CLAIM the test asserts about "
|
|
32
32
|
"it. Decide if the claim is TRUE of what is actually visible. Be strict and literal — only pass if "
|
package/scripts/quick-capture.sh
CHANGED
|
@@ -12,27 +12,28 @@ set -euo pipefail
|
|
|
12
12
|
# [--timeout <secs>] # Hard timeout for explore command
|
|
13
13
|
# quick-capture.sh tree <bundleId> # Dump accessibility tree
|
|
14
14
|
#
|
|
15
|
-
# All output goes to
|
|
15
|
+
# All output goes to the configured Tapp home or the repository capture directory.
|
|
16
16
|
|
|
17
17
|
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
18
18
|
# TAPP_CAPTURE_DIR selects one exact run directory (used by CI so a failed run can never
|
|
19
|
-
# accidentally reuse stale markers).
|
|
20
|
-
# set by the `
|
|
19
|
+
# accidentally reuse stale markers). TAPP_HOME redirects all writable output otherwise —
|
|
20
|
+
# set by the `tapp` CLI when running as an installed npm package, where the package dir
|
|
21
21
|
# must stay read-only. Unset (repo dev flow), everything lands in the repo as before.
|
|
22
|
+
TAPP_RUNTIME_HOME="${TAPP_HOME:-${AUTOTAP_HOME:-}}"
|
|
22
23
|
if [[ -n "${TAPP_CAPTURE_DIR:-}" ]]; then
|
|
23
24
|
CAPTURE_DIR="$TAPP_CAPTURE_DIR"
|
|
24
|
-
elif [[ -n "$
|
|
25
|
-
CAPTURE_DIR="$
|
|
25
|
+
elif [[ -n "$TAPP_RUNTIME_HOME" ]]; then
|
|
26
|
+
CAPTURE_DIR="$TAPP_RUNTIME_HOME/captures/$(date +%Y%m%d-%H%M%S)"
|
|
26
27
|
else
|
|
27
28
|
CAPTURE_DIR="$PROJECT_ROOT/captures/$(date +%Y%m%d-%H%M%S)"
|
|
28
29
|
fi
|
|
29
|
-
if [[ -n "$
|
|
30
|
-
HARNESS_DERIVED="$
|
|
30
|
+
if [[ -n "$TAPP_RUNTIME_HOME" ]]; then
|
|
31
|
+
HARNESS_DERIVED="$TAPP_RUNTIME_HOME/harness-derived"
|
|
31
32
|
else
|
|
32
|
-
HARNESS_DERIVED="/tmp/
|
|
33
|
+
HARNESS_DERIVED="/tmp/tapp-harness-derived"
|
|
33
34
|
fi
|
|
34
35
|
HARNESS_PROJECT="$PROJECT_ROOT/Harness/OCQAHarness.xcodeproj"
|
|
35
|
-
DEFAULT_BUNDLE="
|
|
36
|
+
DEFAULT_BUNDLE="io.github.aarwitz.tapp.demoapp"
|
|
36
37
|
|
|
37
38
|
get_booted_sim() {
|
|
38
39
|
xcrun simctl list devices booted -j 2>/dev/null \
|
|
@@ -168,13 +169,13 @@ run_harness_test() {
|
|
|
168
169
|
"OCQA_MAX_ACTIONS": "$max_actions",
|
|
169
170
|
"OCQA_TIMEOUT_SECONDS": "$timeout_secs",
|
|
170
171
|
"OCQA_TEST_EMAIL": "${OCQA_TEST_EMAIL:-qa@example.com}",
|
|
171
|
-
"OCQA_TEST_PASSWORD": "${OCQA_TEST_PASSWORD:-
|
|
172
|
+
"OCQA_TEST_PASSWORD": "${OCQA_TEST_PASSWORD:-Tapp123!}"$interactive_line$overrides_line$launch_args_line$launch_env_line$login_steps_line$pr_target_line
|
|
172
173
|
}
|
|
173
174
|
CONF
|
|
174
175
|
|
|
175
176
|
local xctestrun=$(find "$HARNESS_DERIVED/Build/Products" -name "*.xctestrun" 2>/dev/null | head -1)
|
|
176
177
|
if [[ -z "$xctestrun" ]]; then
|
|
177
|
-
echo "ERROR: No xctestrun found. Run:
|
|
178
|
+
echo "ERROR: No xctestrun found. Run: tapp install" >&2
|
|
178
179
|
return 1
|
|
179
180
|
fi
|
|
180
181
|
|
|
@@ -382,12 +383,12 @@ case "$MODE" in
|
|
|
382
383
|
"OCQA_SESSION_RESULT_PATH": "$SESS_RES",
|
|
383
384
|
"OCQA_SESSION_TIMEOUT": "$SESS_TIMEOUT",
|
|
384
385
|
"OCQA_TEST_EMAIL": "${OCQA_TEST_EMAIL:-qa@example.com}",
|
|
385
|
-
"OCQA_TEST_PASSWORD": "${OCQA_TEST_PASSWORD:-
|
|
386
|
+
"OCQA_TEST_PASSWORD": "${OCQA_TEST_PASSWORD:-Tapp123!}"$sess_args_line$sess_env_line
|
|
386
387
|
}
|
|
387
388
|
CONF
|
|
388
389
|
xctestrun=$(find "$HARNESS_DERIVED/Build/Products" -name "*.xctestrun" 2>/dev/null | head -1)
|
|
389
390
|
if [[ -z "$xctestrun" ]]; then
|
|
390
|
-
echo "ERROR: No xctestrun. Run:
|
|
391
|
+
echo "ERROR: No xctestrun. Run: tapp install" >&2
|
|
391
392
|
exit 1
|
|
392
393
|
fi
|
|
393
394
|
export TEST_RUNNER_OCQA_CONFIG_PATH=/tmp/ocqa-run-config.json
|
|
@@ -399,7 +400,7 @@ CONF
|
|
|
399
400
|
;;
|
|
400
401
|
|
|
401
402
|
build-harness)
|
|
402
|
-
# Just (re)build the harness cache for the booted sim — used by `
|
|
403
|
+
# Just (re)build the harness cache for the booted sim — used by `tapp install`
|
|
403
404
|
# for a fast first tool call later. No capture output.
|
|
404
405
|
ensure_harness_built "$SIM_NAME"
|
|
405
406
|
rmdir "$CAPTURE_DIR" 2>/dev/null || true
|
package/scripts/run-flow.sh
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Run
|
|
2
|
+
# Run a Tapp Flow — a deterministic, authored/recorded E2E test.
|
|
3
3
|
#
|
|
4
4
|
# Replays the flow's steps against an app on the BOOTED simulator with wait-for-condition timing and
|
|
5
5
|
# poll-with-timeout assertions (no sleeps), then prints a scannable pass/fail report. Deterministic by
|
|
@@ -24,14 +24,14 @@ APP="${2:-}"
|
|
|
24
24
|
|
|
25
25
|
UDID="$(xcrun simctl list devices booted -j 2>/dev/null | python3 -c 'import sys,json; d=json.load(sys.stdin); print(next((x["udid"] for v in d["devices"].values() for x in v if x.get("state")=="Booted"), ""))')"
|
|
26
26
|
[ -z "$UDID" ] && { echo "❌ No booted simulator."; exit 2; }
|
|
27
|
-
# When run through the `
|
|
28
|
-
# AUTOTAP_HOME — check there first, then the repo-dev locations.
|
|
27
|
+
# When run through the installed `tapp` CLI, the harness cache lives under TAPP_HOME.
|
|
29
28
|
XCTR=""
|
|
30
|
-
|
|
29
|
+
TAPP_RUNTIME_HOME="${TAPP_HOME:-${AUTOTAP_HOME:-}}"
|
|
30
|
+
[ -n "$TAPP_RUNTIME_HOME" ] && XCTR="$(find "$TAPP_RUNTIME_HOME/harness-derived/Build/Products" -name '*.xctestrun' 2>/dev/null | head -1)"
|
|
31
31
|
[ -z "$XCTR" ] && XCTR="$(find "$HOME/Library/Developer/Xcode/DerivedData/OCQAHarness-"*/Build/Products -name '*.xctestrun' 2>/dev/null | head -1)"
|
|
32
|
-
[ -z "$XCTR" ] && XCTR="$(find /tmp/
|
|
32
|
+
[ -z "$XCTR" ] && XCTR="$(find /tmp/tapp-harness-derived/Build/Products -name '*.xctestrun' 2>/dev/null | head -1)"
|
|
33
33
|
[ -z "$XCTR" ] && XCTR="$(find /tmp/harness-build/Build/Products -name '*.xctestrun' 2>/dev/null | head -1)"
|
|
34
|
-
[ -z "$XCTR" ] && { echo "❌ Harness not built. Run:
|
|
34
|
+
[ -z "$XCTR" ] && { echo "❌ Harness not built. Run: tapp install"; exit 2; }
|
|
35
35
|
|
|
36
36
|
NAME="$(python3 -c "import sys,json;print(json.loads(sys.argv[1]).get('name','flow'))" "$FLOW_JSON")"
|
|
37
37
|
echo "▶️ Running flow \"$NAME\" against $APP …"
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Build and exercise every native Android fixture through the same release gate
|
|
3
|
-
# customers use. A connected, booted emulator/device is required.
|
|
4
|
-
set -euo pipefail
|
|
5
|
-
|
|
6
|
-
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
7
|
-
CORPUS="$ROOT/AndroidCorpus"
|
|
8
|
-
SERIAL="${1:-${ANDROID_SERIAL:-}}"
|
|
9
|
-
SERIAL_ARGS=()
|
|
10
|
-
[[ -n "$SERIAL" ]] && SERIAL_ARGS=(--serial "$SERIAL")
|
|
11
|
-
|
|
12
|
-
"$CORPUS/gradlew" -p "$CORPUS" \
|
|
13
|
-
:demoapp:assembleDebug :logindemo:assembleDebug :shopdemo:assembleDebug --no-daemon --quiet
|
|
14
|
-
|
|
15
|
-
run_fixture() {
|
|
16
|
-
local module="$1" app_id="$2" flow="$3"
|
|
17
|
-
local apk="$CORPUS/$module/build/outputs/apk/debug/$module-debug.apk"
|
|
18
|
-
local output_dir="${RUNNER_TEMP:-/tmp}/tapp-android-corpus-$module"
|
|
19
|
-
mkdir -p "$output_dir"
|
|
20
|
-
echo "Testing Android corpus fixture: $module ($app_id)"
|
|
21
|
-
"$ROOT/scripts/ci-gate.sh" --platform android --apk "$apk" --app-id "$app_id" \
|
|
22
|
-
"${SERIAL_ARGS[@]}" --actions 12 --timeout 180 --flows "$CORPUS/$module/.autotap/flows/$flow" \
|
|
23
|
-
--project-dir "$CORPUS/$module" --json-out "$output_dir/report.json" --md-out "$output_dir/report.md"
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
run_fixture demoapp io.tapp.corpus.demo smoke.yml
|
|
27
|
-
run_fixture logindemo io.tapp.corpus.login sign-in.yml
|
|
28
|
-
run_fixture shopdemo io.tapp.corpus.shop checkout.yml
|
|
29
|
-
|
|
30
|
-
echo "All Android corpus gates passed."
|
package/scripts/cleanup-xcode.sh
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Reclaim disk space consumed by Xcode build artifacts.
|
|
3
|
-
#
|
|
4
|
-
# Default run (no flags): deletes unavailable simulators and stale DerivedData
|
|
5
|
-
# from unrelated projects. Flags unlock more aggressive passes.
|
|
6
|
-
#
|
|
7
|
-
# Usage:
|
|
8
|
-
# scripts/cleanup-xcode.sh # safe defaults
|
|
9
|
-
# scripts/cleanup-xcode.sh --dry-run # preview only, nothing deleted
|
|
10
|
-
# scripts/cleanup-xcode.sh --device-support # also wipe iOS DeviceSupport caches
|
|
11
|
-
# scripts/cleanup-xcode.sh --all # everything above + Xcode caches
|
|
12
|
-
#
|
|
13
|
-
# AutoTap-related DerivedData (OCQAHarness, DemoApp, LoginDemo, etc.) is always
|
|
14
|
-
# preserved. The local build/ directory in this repo is never touched.
|
|
15
|
-
set -uo pipefail
|
|
16
|
-
|
|
17
|
-
DRY_RUN=0
|
|
18
|
-
CLEAN_DEVICE_SUPPORT=0
|
|
19
|
-
CLEAN_ALL=0
|
|
20
|
-
|
|
21
|
-
for arg in "$@"; do
|
|
22
|
-
case "$arg" in
|
|
23
|
-
--dry-run) DRY_RUN=1 ;;
|
|
24
|
-
--device-support) CLEAN_DEVICE_SUPPORT=1 ;;
|
|
25
|
-
--all) CLEAN_DEVICE_SUPPORT=1; CLEAN_ALL=1 ;;
|
|
26
|
-
-h|--help)
|
|
27
|
-
sed -n '/^# /s/^# //p' "$0" | head -15
|
|
28
|
-
exit 0 ;;
|
|
29
|
-
*) echo "Unknown flag: $arg (try --help)"; exit 1 ;;
|
|
30
|
-
esac
|
|
31
|
-
done
|
|
32
|
-
|
|
33
|
-
DERIVED="$HOME/Library/Developer/Xcode/DerivedData"
|
|
34
|
-
SIMS="$HOME/Library/Developer/CoreSimulator/Devices"
|
|
35
|
-
DEVICE_SUPPORT="$HOME/Library/Developer/Xcode/iOS DeviceSupport"
|
|
36
|
-
XCODE_CACHES="$HOME/Library/Caches/com.apple.dt.Xcode"
|
|
37
|
-
|
|
38
|
-
# Projects to preserve in DerivedData (all AutoTap-related build products).
|
|
39
|
-
KEEP_PREFIXES=("AutoTap" "OCQAHarness" "DemoApp" "LoginDemo" "WizardDemo"
|
|
40
|
-
"ShopDemo" "RestaurantDemo" "SymbolCache")
|
|
41
|
-
|
|
42
|
-
hr() { printf '%0.s─' {1..60}; echo; }
|
|
43
|
-
human() { du -sh "$1" 2>/dev/null | cut -f1; }
|
|
44
|
-
|
|
45
|
-
run() {
|
|
46
|
-
if [[ $DRY_RUN -eq 1 ]]; then
|
|
47
|
-
echo " [dry-run] $*"
|
|
48
|
-
else
|
|
49
|
-
eval "$@"
|
|
50
|
-
fi
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
echo ""
|
|
54
|
-
hr
|
|
55
|
-
echo " Xcode Cleanup $(date '+%Y-%m-%d %H:%M')"
|
|
56
|
-
[[ $DRY_RUN -eq 1 ]] && echo " DRY RUN — nothing will be deleted"
|
|
57
|
-
hr
|
|
58
|
-
|
|
59
|
-
# ── Before snapshot ──────────────────────────────────────────────────────────
|
|
60
|
-
echo ""
|
|
61
|
-
echo "Before:"
|
|
62
|
-
printf " %-38s %s\n" "CoreSimulator/Devices" "$(human "$SIMS")"
|
|
63
|
-
printf " %-38s %s\n" "Xcode/DerivedData" "$(human "$DERIVED")"
|
|
64
|
-
printf " %-38s %s\n" "Xcode/iOS DeviceSupport" "$(human "$DEVICE_SUPPORT")"
|
|
65
|
-
printf " %-38s %s\n" "Xcode caches" "$(human "$XCODE_CACHES")"
|
|
66
|
-
echo ""
|
|
67
|
-
|
|
68
|
-
# ── 1. Unavailable simulators ─────────────────────────────────────────────────
|
|
69
|
-
echo "1/3 Removing unavailable simulators …"
|
|
70
|
-
UNAVAIL=$(xcrun simctl list devices --json 2>/dev/null | \
|
|
71
|
-
python3 -c '
|
|
72
|
-
import sys, json
|
|
73
|
-
d = json.load(sys.stdin)
|
|
74
|
-
n = sum(1 for v in d["devices"].values() for x in v if not x.get("isAvailable", True))
|
|
75
|
-
print(n)
|
|
76
|
-
')
|
|
77
|
-
echo " Found $UNAVAIL unavailable device(s)."
|
|
78
|
-
if [[ "$UNAVAIL" -gt 0 ]]; then
|
|
79
|
-
run "xcrun simctl delete unavailable"
|
|
80
|
-
fi
|
|
81
|
-
|
|
82
|
-
# ── 2. Unrelated DerivedData ──────────────────────────────────────────────────
|
|
83
|
-
echo ""
|
|
84
|
-
echo "2/3 Scanning DerivedData for unrelated projects …"
|
|
85
|
-
STALE=()
|
|
86
|
-
if [[ -d "$DERIVED" ]]; then
|
|
87
|
-
while IFS= read -r -d '' entry; do
|
|
88
|
-
base="$(basename "$entry")"
|
|
89
|
-
keep=0
|
|
90
|
-
for prefix in "${KEEP_PREFIXES[@]}"; do
|
|
91
|
-
if [[ "$base" == "$prefix"* ]]; then
|
|
92
|
-
keep=1; break
|
|
93
|
-
fi
|
|
94
|
-
done
|
|
95
|
-
[[ $keep -eq 0 ]] && STALE+=("$entry")
|
|
96
|
-
done < <(find "$DERIVED" -maxdepth 1 -mindepth 1 -type d -print0)
|
|
97
|
-
fi
|
|
98
|
-
|
|
99
|
-
if [[ ${#STALE[@]} -eq 0 ]]; then
|
|
100
|
-
echo " Nothing to remove."
|
|
101
|
-
else
|
|
102
|
-
echo " Will remove ${#STALE[@]} unrelated build folder(s):"
|
|
103
|
-
for entry in "${STALE[@]}"; do
|
|
104
|
-
printf " %-8s %s\n" "$(human "$entry")" "$(basename "$entry")"
|
|
105
|
-
done
|
|
106
|
-
for entry in "${STALE[@]}"; do
|
|
107
|
-
run "rm -rf \"$entry\""
|
|
108
|
-
done
|
|
109
|
-
fi
|
|
110
|
-
|
|
111
|
-
# ── 3. iOS DeviceSupport (optional) ──────────────────────────────────────────
|
|
112
|
-
echo ""
|
|
113
|
-
echo "3/3 iOS DeviceSupport …"
|
|
114
|
-
if [[ $CLEAN_DEVICE_SUPPORT -eq 1 ]]; then
|
|
115
|
-
if [[ -d "$DEVICE_SUPPORT" ]]; then
|
|
116
|
-
echo " Removing $(human "$DEVICE_SUPPORT") of device symbol caches …"
|
|
117
|
-
echo " (Xcode re-downloads these automatically when a device is connected.)"
|
|
118
|
-
run "rm -rf \"$DEVICE_SUPPORT\""
|
|
119
|
-
else
|
|
120
|
-
echo " Directory not found — nothing to do."
|
|
121
|
-
fi
|
|
122
|
-
else
|
|
123
|
-
SIZE=$(human "$DEVICE_SUPPORT")
|
|
124
|
-
echo " Skipped ($SIZE on disk). Pass --device-support to remove."
|
|
125
|
-
fi
|
|
126
|
-
|
|
127
|
-
# ── 4. Xcode caches (--all only) ─────────────────────────────────────────────
|
|
128
|
-
if [[ $CLEAN_ALL -eq 1 ]]; then
|
|
129
|
-
echo ""
|
|
130
|
-
echo "4/4 Xcode caches …"
|
|
131
|
-
if [[ -d "$XCODE_CACHES" ]]; then
|
|
132
|
-
echo " Removing $(human "$XCODE_CACHES") …"
|
|
133
|
-
run "rm -rf \"$XCODE_CACHES\""
|
|
134
|
-
fi
|
|
135
|
-
fi
|
|
136
|
-
|
|
137
|
-
# ── Simulator data cache (always safe) ───────────────────────────────────────
|
|
138
|
-
SIM_CACHES="$HOME/Library/Developer/CoreSimulator/Caches"
|
|
139
|
-
if [[ -d "$SIM_CACHES" ]] && [[ "$(du -sk "$SIM_CACHES" 2>/dev/null | cut -f1)" -gt 0 ]]; then
|
|
140
|
-
echo ""
|
|
141
|
-
echo " Removing simulator caches ($(human "$SIM_CACHES")) …"
|
|
142
|
-
run "rm -rf \"$SIM_CACHES\""
|
|
143
|
-
fi
|
|
144
|
-
|
|
145
|
-
# ── After snapshot ─────────────────────────────────────────────────────────────
|
|
146
|
-
echo ""
|
|
147
|
-
if [[ $DRY_RUN -eq 0 ]]; then
|
|
148
|
-
hr
|
|
149
|
-
echo "After:"
|
|
150
|
-
printf " %-38s %s\n" "CoreSimulator/Devices" "$(human "$SIMS")"
|
|
151
|
-
printf " %-38s %s\n" "Xcode/DerivedData" "$(human "$DERIVED")"
|
|
152
|
-
printf " %-38s %s\n" "Xcode/iOS DeviceSupport" "$(human "$DEVICE_SUPPORT")"
|
|
153
|
-
printf " %-38s %s\n" "Xcode caches" "$(human "$XCODE_CACHES")"
|
|
154
|
-
echo ""
|
|
155
|
-
fi
|
|
156
|
-
|
|
157
|
-
echo "Done."
|
package/scripts/corpus-apps.txt
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# Corpus sweep list — curated from dkhamsing/open-source-ios-apps across backend classes.
|
|
2
|
-
# Class coverage: self-hostable-service client (login wall), public API (no key),
|
|
3
|
-
# public API (keyed), no-backend/local-first, heavyweight real-world.
|
|
4
|
-
https://github.com/Dimillian/IceCubesApp
|
|
5
|
-
https://github.com/weiran/Hackers
|
|
6
|
-
https://github.com/Ranchero-Software/NetNewsWire
|
|
7
|
-
https://github.com/yattee/yattee
|
|
8
|
-
https://github.com/wikimedia/wikipedia-ios
|
|
9
|
-
https://github.com/mastodon/mastodon-ios
|
package/scripts/corpus-sweep.sh
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Corpus sweep — run a list of real open-source iOS apps through the tapp pipeline and
|
|
3
|
-
# produce a friction scoreboard. Failures ARE the data: every app gets a row recording
|
|
4
|
-
# how far it got (clone → detect → build → install → explore) and why it stopped.
|
|
5
|
-
#
|
|
6
|
-
# Usage: scripts/corpus-sweep.sh <urls-file> [--actions N] [--out <dir>]
|
|
7
|
-
# urls-file: one git URL per line (# comments ok)
|
|
8
|
-
#
|
|
9
|
-
# Needs a booted simulator. Rows land in <out>/scoreboard.tsv; per-app logs in <out>/<app>/.
|
|
10
|
-
set -uo pipefail
|
|
11
|
-
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
12
|
-
|
|
13
|
-
URLS_FILE="${1:?usage: corpus-sweep.sh <urls-file> [--actions N] [--out dir]}"; shift
|
|
14
|
-
ACTIONS=25
|
|
15
|
-
OUT="/tmp/tapp-corpus"
|
|
16
|
-
while [[ $# -gt 0 ]]; do
|
|
17
|
-
case "$1" in
|
|
18
|
-
--actions) ACTIONS="$2"; shift 2 ;;
|
|
19
|
-
--out) OUT="$2"; shift 2 ;;
|
|
20
|
-
*) echo "unknown arg $1" >&2; exit 2 ;;
|
|
21
|
-
esac
|
|
22
|
-
done
|
|
23
|
-
mkdir -p "$OUT"
|
|
24
|
-
BOARD="$OUT/scoreboard.tsv"
|
|
25
|
-
[[ -f "$BOARD" ]] || echo -e "app\tstage\tbackend_signals\tbundle\tscreens\tactions\tfindings\tverdict\tnote" > "$BOARD"
|
|
26
|
-
|
|
27
|
-
UDID="$(xcrun simctl list devices booted -j | python3 -c 'import sys,json; d=json.load(sys.stdin); print(next((x["udid"] for v in d["devices"].values() for x in v if x.get("state")=="Booted"), ""))')"
|
|
28
|
-
[[ -n "$UDID" ]] || { echo "❌ boot a simulator first" >&2; exit 1; }
|
|
29
|
-
|
|
30
|
-
row() { echo -e "$1\t$2\t$3\t$4\t$5\t$6\t$7\t$8\t$9" >> "$BOARD"; }
|
|
31
|
-
|
|
32
|
-
# Cheap backend-class signals from the repo tree (feeds the classifier's rules).
|
|
33
|
-
backend_signals() {
|
|
34
|
-
local dir="$1" sig=()
|
|
35
|
-
[[ -n "$(find "$dir" -maxdepth 4 -name 'GoogleService-Info.plist' -print -quit 2>/dev/null)" ]] && sig+=("firebase-plist")
|
|
36
|
-
grep -rqli "firebase" "$dir"/Podfile "$dir"/*/Package.resolved "$dir"/Package.resolved 2>/dev/null && sig+=("firebase-dep")
|
|
37
|
-
grep -rqli "supabase" "$dir"/Podfile "$dir"/*/Package.resolved "$dir"/Package.resolved 2>/dev/null && sig+=("supabase-dep")
|
|
38
|
-
[[ -n "$(find "$dir" -maxdepth 2 -name 'docker-compose.y*ml' -print -quit 2>/dev/null)" ]] && sig+=("in-repo-backend")
|
|
39
|
-
grep -rqli "mastodon" "$dir"/Package.resolved 2>/dev/null && sig+=("mastodon-client")
|
|
40
|
-
grep -rql "YOUR_API_KEY\|INSERT_API_KEY\|<API_KEY>" --include="*.xcconfig" --include="*.plist" --include="*.swift" "$dir" 2>/dev/null | head -1 | grep -q . && sig+=("key-placeholder")
|
|
41
|
-
local IFS=","; echo "${sig[*]:-none}"
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
# fd 3 for the URL list — xcodebuild/git inside the loop consume stdin and would
|
|
45
|
-
# silently swallow the remaining lines (this truncated the first sweep run).
|
|
46
|
-
while IFS= read -r url <&3; do
|
|
47
|
-
[[ -z "$url" || "$url" == \#* ]] && continue
|
|
48
|
-
name="$(basename "$url" .git)"
|
|
49
|
-
dir="$OUT/$name"
|
|
50
|
-
log="$OUT/$name.log"
|
|
51
|
-
echo "━━━ $name"
|
|
52
|
-
|
|
53
|
-
# 1. clone
|
|
54
|
-
if [[ ! -d "$dir" ]]; then
|
|
55
|
-
git clone --depth 1 --recurse-submodules --shallow-submodules "$url" "$dir" >"$log" 2>&1 \
|
|
56
|
-
|| { row "$name" clone none - - - - - "git clone failed"; continue; }
|
|
57
|
-
fi
|
|
58
|
-
signals="$(backend_signals "$dir")"
|
|
59
|
-
|
|
60
|
-
# 2. detect workspace/project + scheme (workspace wins; ignore Pods)
|
|
61
|
-
target="$(find "$dir" -maxdepth 3 -name '*.xcworkspace' -not -path '*/Pods/*' -not -path '*/.*' | head -1)"
|
|
62
|
-
flag="-workspace"
|
|
63
|
-
if [[ -z "$target" ]]; then
|
|
64
|
-
target="$(find "$dir" -maxdepth 3 -name '*.xcodeproj' -not -path '*/Pods/*' | head -1)"
|
|
65
|
-
flag="-project"
|
|
66
|
-
fi
|
|
67
|
-
[[ -n "$target" ]] || { row "$name" detect "$signals" - - - - - "no xcworkspace/xcodeproj found"; continue; }
|
|
68
|
-
schemes="$(xcodebuild -list -json $flag "$target" 2>>"$log" | python3 -c 'import sys,json
|
|
69
|
-
try:
|
|
70
|
-
d=json.load(sys.stdin); o=d.get("workspace") or d.get("project") or {}
|
|
71
|
-
print("\n".join(o.get("schemes") or []))
|
|
72
|
-
except Exception: pass')"
|
|
73
|
-
# Prefer an explicitly-iOS scheme (multi-platform repos like NetNewsWire name-match the
|
|
74
|
-
# macOS scheme first, which can't build for an iOS Simulator destination).
|
|
75
|
-
scheme="$(echo "$schemes" | grep -i "ios" | grep -iv "test\|widget\|extension\|notification\|clip" | head -1)"
|
|
76
|
-
[[ -z "$scheme" ]] && scheme="$(echo "$schemes" | grep -ix "$name" | head -1)"
|
|
77
|
-
[[ -z "$scheme" ]] && scheme="$(echo "$schemes" | grep -iv "test\|uitests\|widget\|extension\|notification\|clip\|watch\|mac" | head -1)"
|
|
78
|
-
[[ -z "$scheme" ]] && scheme="$(echo "$schemes" | head -1)"
|
|
79
|
-
[[ -n "$scheme" ]] || { row "$name" detect "$signals" - - - - - "no schemes listed"; continue; }
|
|
80
|
-
echo " scheme: $scheme"
|
|
81
|
-
|
|
82
|
-
# 3. build for simulator (20 min cap; perl alarm — stock macOS has no `timeout`)
|
|
83
|
-
dd="$OUT/dd-$name"
|
|
84
|
-
if ! perl -e 'alarm shift; exec @ARGV' 1200 xcodebuild $flag "$target" -scheme "$scheme" \
|
|
85
|
-
-destination 'generic/platform=iOS Simulator' -derivedDataPath "$dd" \
|
|
86
|
-
CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO \
|
|
87
|
-
build >>"$log" 2>&1; then
|
|
88
|
-
reason="$(grep -m1 -E "error: |xcodebuild: error" "$log" | tail -c 120 || true)"
|
|
89
|
-
row "$name" build "$signals" - - - - - "build failed: ${reason:-see log}"
|
|
90
|
-
continue
|
|
91
|
-
fi
|
|
92
|
-
app="$(find "$dd/Build/Products" -maxdepth 2 -name '*.app' -path '*iphonesimulator*' | head -1)"
|
|
93
|
-
[[ -n "$app" ]] || { row "$name" build "$signals" - - - - - "built but no simulator .app (macOS-only scheme?)"; continue; }
|
|
94
|
-
bundle="$(/usr/libexec/PlistBuddy -c 'Print CFBundleIdentifier' "$app/Info.plist" 2>/dev/null || echo "?")"
|
|
95
|
-
|
|
96
|
-
# 4. install
|
|
97
|
-
xcrun simctl install "$UDID" "$app" >>"$log" 2>&1 \
|
|
98
|
-
|| { row "$name" install "$signals" "$bundle" - - - - "simctl install failed"; continue; }
|
|
99
|
-
|
|
100
|
-
# 5. explore + report
|
|
101
|
-
"$ROOT/scripts/quick-capture.sh" explore "$bundle" --actions "$ACTIONS" --timeout 420 >>"$log" 2>&1
|
|
102
|
-
cap="$(ls -td "$ROOT"/captures/*/ 2>/dev/null | head -1)"
|
|
103
|
-
if [[ ! -f "$cap/ocqa-markers.txt" ]]; then
|
|
104
|
-
row "$name" explore "$signals" "$bundle" - - - - "exploration produced no markers"
|
|
105
|
-
continue
|
|
106
|
-
fi
|
|
107
|
-
json="$OUT/$name-report.json"
|
|
108
|
-
node "$ROOT/mcp-server/src/ci-report.js" --markers "$cap/ocqa-markers.txt" --json-out "$json" >>"$log" 2>&1 || true
|
|
109
|
-
read -r screens acted findings verdict <<< "$(python3 -c "
|
|
110
|
-
import json,sys
|
|
111
|
-
try:
|
|
112
|
-
r=json.load(open('$json'))
|
|
113
|
-
print(r.get('screensExplored','?'), r.get('actionsPerformed','?'), r.get('findingCounts',{}).get('total','?'), r.get('verdict','?'))
|
|
114
|
-
except Exception: print('? ? ? ?')")"
|
|
115
|
-
row "$name" explored "$signals" "$bundle" "$screens" "$acted" "$findings" "$verdict" "capture: $(basename "$cap")"
|
|
116
|
-
echo " ✅ $verdict — $screens screens"
|
|
117
|
-
done 3< "$URLS_FILE"
|
|
118
|
-
|
|
119
|
-
echo ""
|
|
120
|
-
echo "═══ scoreboard: $BOARD ═══"
|
|
121
|
-
column -t -s $'\t' "$BOARD"
|
package/scripts/coverage-eval.sh
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# AutoTap penetration / coverage eval — the "real-app scoreboard".
|
|
3
|
-
#
|
|
4
|
-
# Unlike validation-matrix.sh (which asserts *expected findings* against DemoApp, i.e. accuracy on a
|
|
5
|
-
# known fixture), this measures how far AutoTap actually PENETRATES an arbitrary app: did it get past
|
|
6
|
-
# launch, did it hit a login wall, how many distinct screens it reached, how much of its budget went
|
|
7
|
-
# to productive exploration vs. stuck/recovery flailing, and what it found. There is no ground truth —
|
|
8
|
-
# this is a diagnostic you point at real apps to see where they stall, so coverage work is measurable.
|
|
9
|
-
#
|
|
10
|
-
# Usage:
|
|
11
|
-
# scripts/coverage-eval.sh [bundleId ...] # defaults to the local corpus apps
|
|
12
|
-
# scripts/coverage-eval.sh com.acme.app # a real app (must be installed on the booted sim)
|
|
13
|
-
# ACTIONS=120 scripts/coverage-eval.sh com.acme.app
|
|
14
|
-
# VISION_ESCALATION=1 ANTHROPIC_API_KEY=... scripts/coverage-eval.sh com.acme.app
|
|
15
|
-
# # opt-in: when the a11y tree goes blank or exploration gets stuck, a sidecar
|
|
16
|
-
# # (vision_escalation_responder.py) answers the harness's OCQA_VISION_QUERY with a
|
|
17
|
-
# # model-chosen next move — same channel the desktop app serves.
|
|
18
|
-
#
|
|
19
|
-
# Prereqs: a booted simulator, the harness built (scripts/deploy-and-build.sh --harness), and each
|
|
20
|
-
# app installed on the sim. Writes a JSON report to captures/coverage-eval-<timestamp>.json.
|
|
21
|
-
#
|
|
22
|
-
# NOTE: auth sessions stored in the keychain (Firebase et al) SURVIVE app reinstall — a "fresh"
|
|
23
|
-
# run may silently resume the previous account. For a genuinely fresh login:
|
|
24
|
-
# xcrun simctl keychain <udid> reset
|
|
25
|
-
set -uo pipefail
|
|
26
|
-
|
|
27
|
-
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
28
|
-
ACTIONS="${ACTIONS:-70}"
|
|
29
|
-
TIMEOUT="${TIMEOUT:-600}"
|
|
30
|
-
DEFAULT_APPS=(com.autotap.demoapp com.autotap.logindemo com.autotap.wizarddemo com.autotap.shopdemo com.autotap.restaurantdemo)
|
|
31
|
-
APPS=("$@"); [ ${#APPS[@]} -eq 0 ] && APPS=("${DEFAULT_APPS[@]}")
|
|
32
|
-
|
|
33
|
-
UDID="$(xcrun simctl list devices booted -j 2>/dev/null | python3 -c 'import sys,json; d=json.load(sys.stdin); print(next((x["udid"] for v in d["devices"].values() for x in v if x.get("state")=="Booted"), ""))')"
|
|
34
|
-
[ -z "$UDID" ] && { echo "❌ No booted simulator. Boot one first."; exit 2; }
|
|
35
|
-
|
|
36
|
-
XCTR="$(find "$HOME/Library/Developer/Xcode/DerivedData/OCQAHarness-"*/Build/Products -name '*.xctestrun' 2>/dev/null | head -1)"
|
|
37
|
-
[ -z "$XCTR" ] && XCTR="$(find /tmp/autotap-harness-derived/Build/Products -name '*.xctestrun' 2>/dev/null | head -1)"
|
|
38
|
-
[ -z "$XCTR" ] && XCTR="$(find /tmp/harness-build/Build/Products -name '*.xctestrun' 2>/dev/null | head -1)"
|
|
39
|
-
[ -z "$XCTR" ] && { echo "❌ Harness not built. Run: scripts/deploy-and-build.sh --harness"; exit 2; }
|
|
40
|
-
|
|
41
|
-
TS="$(date +%Y%m%d-%H%M%S)"
|
|
42
|
-
REPORT="$ROOT/captures/coverage-eval-$TS.json"
|
|
43
|
-
mkdir -p "$ROOT/captures"
|
|
44
|
-
echo "AutoTap coverage eval — $ACTIONS actions/app, sim $UDID"
|
|
45
|
-
echo "Apps: ${APPS[*]}"
|
|
46
|
-
echo ""
|
|
47
|
-
|
|
48
|
-
RESULTS_JSON="["
|
|
49
|
-
FIRST=1
|
|
50
|
-
for APP in "${APPS[@]}"; do
|
|
51
|
-
if ! xcrun simctl get_app_container "$UDID" "$APP" >/dev/null 2>&1; then
|
|
52
|
-
printf " %-32s ⏭ not installed — skipping\n" "$APP"
|
|
53
|
-
continue
|
|
54
|
-
fi
|
|
55
|
-
CFG="/tmp/ocqa-coverage-$APP.json"
|
|
56
|
-
LOG="/tmp/ocqa-coverage-$APP.log"
|
|
57
|
-
RESPONDER_PID=""
|
|
58
|
-
# Real test credentials (OCQA_TEST_EMAIL / OCQA_TEST_PASSWORD env) let the heuristic login
|
|
59
|
-
# preamble get PAST a real auth wall — the single biggest coverage unlock on gated apps.
|
|
60
|
-
CREDS_LINE=""
|
|
61
|
-
[ -n "${OCQA_TEST_EMAIL:-}" ] && CREDS_LINE=",
|
|
62
|
-
\"OCQA_TEST_EMAIL\": \"$OCQA_TEST_EMAIL\", \"OCQA_TEST_PASSWORD\": \"${OCQA_TEST_PASSWORD:-}\""
|
|
63
|
-
if [ "${VISION_ESCALATION:-0}" = "1" ] && [ -n "${ANTHROPIC_API_KEY:-}" ]; then
|
|
64
|
-
VRESP="/tmp/ocqa-vision-response-eval-$APP.json"
|
|
65
|
-
VDIR="/tmp/ocqa-vision-eval-$APP"; mkdir -p "$VDIR"
|
|
66
|
-
cat > "$CFG" <<JSON
|
|
67
|
-
{ "OCQA_BUNDLE_ID": "$APP", "OCQA_MAX_ACTIONS": "$ACTIONS", "OCQA_TIMEOUT_SECONDS": "$TIMEOUT",
|
|
68
|
-
"OCQA_VISION_ESCALATION": "1", "OCQA_VISION_RESPONSE_PATH": "$VRESP",
|
|
69
|
-
"OCQA_VISION_IMAGE_DIR": "$VDIR", "OCQA_VISION_BUDGET": "4", "OCQA_VISION_WAIT_TIMEOUT": "60"$CREDS_LINE }
|
|
70
|
-
JSON
|
|
71
|
-
: > "$LOG"
|
|
72
|
-
python3 "$ROOT/scripts/vision_escalation_responder.py" "$LOG" "$VRESP" > "/tmp/ocqa-vision-responder-$APP.log" 2>&1 &
|
|
73
|
-
RESPONDER_PID=$!
|
|
74
|
-
else
|
|
75
|
-
cat > "$CFG" <<JSON
|
|
76
|
-
{ "OCQA_BUNDLE_ID": "$APP", "OCQA_MAX_ACTIONS": "$ACTIONS", "OCQA_TIMEOUT_SECONDS": "$TIMEOUT"$CREDS_LINE }
|
|
77
|
-
JSON
|
|
78
|
-
fi
|
|
79
|
-
TEST_RUNNER_OCQA_CONFIG_PATH="$CFG" xcodebuild test-without-building \
|
|
80
|
-
-xctestrun "$XCTR" -destination "platform=iOS Simulator,id=$UDID" \
|
|
81
|
-
-only-testing:"OCQAHarnessUITests/ExplorerTests/testAutonomousExploration" > "$LOG" 2>&1
|
|
82
|
-
if [ -n "$RESPONDER_PID" ]; then kill "$RESPONDER_PID" 2>/dev/null; wait "$RESPONDER_PID" 2>/dev/null; fi
|
|
83
|
-
|
|
84
|
-
ROW="$(python3 "$ROOT/scripts/coverage_eval_parse.py" "$LOG" "$APP")"
|
|
85
|
-
echo "$ROW" | python3 -c 'import sys,json; d=json.load(sys.stdin); ca=d.get("crash_action") or {}; print(" %-32s screens=%-3s launch=%-3s login_wall=%-3s stuck=%-4s findings=%s%s" % (d["app"], d["screens"], "ok" if d["launch_ok"] else "NO", "YES" if d["login_wall"] else "no", d["stuck_ratio"], d["findings_total"], (" ⚠️ CRASHED on %s %s" % (ca.get("type",""), ca.get("screen","")) if d.get("crashed") else "")))'
|
|
86
|
-
[ $FIRST -eq 0 ] && RESULTS_JSON+=","
|
|
87
|
-
RESULTS_JSON+="$ROW"; FIRST=0
|
|
88
|
-
done
|
|
89
|
-
RESULTS_JSON+="]"
|
|
90
|
-
echo "$RESULTS_JSON" | python3 -m json.tool > "$REPORT" 2>/dev/null || echo "$RESULTS_JSON" > "$REPORT"
|
|
91
|
-
echo ""
|
|
92
|
-
echo "Report: $REPORT"
|