Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions actions/run-ios-comment-session/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,58 @@ runs:
fi
date +%s > /tmp/sim-boot-end

- name: Wait for public SimDeck iOS session access
shell: bash
run: |
set -euo pipefail

udid="${SIMULATOR_UDID}"
public_simulators_url="${{ steps.stream.outputs.url }}/api/simulators?simdeckToken=${{ steps.stream.outputs.access_token }}"
tunnel_host="${{ steps.stream.outputs.url }}"
tunnel_host="${tunnel_host#https://}"
tunnel_host="${tunnel_host%%/*}"

fetch_public_simulators() {
curl -fsS "${public_simulators_url}" -o public-simulators.json ||
curl -fsS --resolve "${tunnel_host}:443:104.16.230.132" "${public_simulators_url}" -o public-simulators.json ||
curl -fsS --resolve "${tunnel_host}:443:104.16.231.132" "${public_simulators_url}" -o public-simulators.json
}

verify_selected_simulator() {
SIMDECK_READY_UDID="${udid}" python3 - <<'PY'
import json
import os

with open("public-simulators.json", "r", encoding="utf-8") as handle:
data = json.load(handle)

udid = os.environ["SIMDECK_READY_UDID"]
simulators = data.get("simulators", data if isinstance(data, list) else [])
for simulator in simulators:
if simulator.get("udid") == udid and simulator.get("isBooted") is True:
raise SystemExit(0)

raise SystemExit(1)
PY
}

for attempt in {1..120}; do
if fetch_public_simulators && verify_selected_simulator; then
echo "Public SimDeck URL lists booted simulator ${udid}."
exit 0
fi
echo "Waiting for public SimDeck simulator list (${attempt}/120)."
sleep 1
done

echo "SimDeck public simulator list did not become accessible for ${udid}" >&2
if [[ -f public-simulators.json ]]; then
cat public-simulators.json >&2 || true
fi
cat cloudflared.log >&2 || true
cat simdeck-daemon.log >&2 || true
exit 1

- name: Update status comment with booted simulator URL
shell: bash
run: |
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/github-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,5 +192,5 @@ Supported quality values include `tiny`, `low`, `economy`, `fast`, `smooth`, `ba
- Picks or creates an iOS Simulator or Android emulator.
- Downloads the app artifact for the PR head commit.
- Installs and launches the app.
- Posts a browser URL back to the pull request after the simulator or emulator is booted.
- Posts a browser URL back to the pull request after the simulator or emulator is booted. The iOS action waits until the public URL can load the selected booted simulator from `/api/simulators`.
- Stops after the configured keepalive window.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"format": "prettier --write . && cargo fmt --manifest-path server/Cargo.toml",
"format:check": "prettier --check . && cargo fmt --manifest-path server/Cargo.toml --check",
"lint": "npm run format:check && cargo clippy --manifest-path server/Cargo.toml --all-targets -- -D warnings && npm run --prefix client typecheck",
"test": "cargo test --manifest-path server/Cargo.toml && npm run --prefix client test",
"test": "cargo test --manifest-path server/Cargo.toml && npm run --prefix client test && npm run test:github-actions",
"test:integration:cli": "node scripts/integration/cli.mjs",
"test:integration:cli:verbose": "SIMDECK_INTEGRATION_VERBOSE=1 SIMDECK_INTEGRATION_SHOW_SIMULATOR=1 node scripts/integration/cli.mjs",
"test:integration:fixture": "node scripts/integration/prebuild-fixture.mjs",
Expand All @@ -77,6 +77,7 @@
"test:e2e:webrtc:headed": "SIMDECK_E2E_HEADFUL=1 node scripts/e2e-webrtc-reliability.mjs",
"test:e2e:webrtc:stress": "node scripts/e2e-webrtc-stress.mjs",
"test:studio-provider": "node --test scripts/studio-provider-bridge.test.mjs scripts/studio-host-provider.test.mjs",
"test:github-actions": "node --test scripts/github-actions.test.mjs",
"test:stress": "node scripts/stress/simdeck.mjs",
"bench:encoder:build": "scripts/bench/build-encoder-benchmark.sh",
"ci": "npm run lint && npm run build:all && npm run test && npm run package:vscode-extension",
Expand Down
52 changes: 52 additions & 0 deletions scripts/github-actions.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { test } from "node:test";

const iosAction = readFileSync(
new URL("../actions/run-ios-comment-session/action.yml", import.meta.url),
"utf8",
);

test("iOS PR comment waits for public simulator list access", () => {
const prebootIndex = iosAction.indexOf(
"- name: Select and preboot simulator",
);
const readinessIndex = iosAction.indexOf(
"- name: Wait for public SimDeck iOS session access",
);
const commentIndex = iosAction.indexOf(
"- name: Update status comment with booted simulator URL",
);

assert.notEqual(prebootIndex, -1, "preboot step should exist");
assert.notEqual(
commentIndex,
-1,
"booted simulator comment step should exist",
);
assert(
readinessIndex > prebootIndex,
"readiness check should run after simulator preboot",
);
assert(
readinessIndex < commentIndex,
"readiness check should run before posting the PR URL",
);

const readinessStep = iosAction.slice(readinessIndex, commentIndex);
assert.match(
readinessStep,
/\$\{\{ steps\.stream\.outputs\.url \}\}\/api\/simulators\?simdeckToken=/,
"readiness check should use the public tunnel URL",
);
assert.match(
readinessStep,
/SIMULATOR_UDID/,
"readiness check should look for the selected simulator",
);
assert.match(
readinessStep,
/isBooted/,
"readiness check should require the selected simulator to be booted",
);
});
Loading