Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
06af7f7
ci: share one runner for iOS and macOS smoke phases
thymikee Oct 19, 2026
d39c491
chore(gates): enforce Apple phase routing and failure propagation
thymikee Oct 19, 2026
5b3d205
ci: retire crashed phase owners and upload shared artifacts once
thymikee Oct 19, 2026
1212c97
chore(gates): guard scoped cleanup and single diagnostic upload
thymikee Oct 19, 2026
cf57d8d
ci: point Apple coverage evidence at relocated smoke steps
thymikee Oct 19, 2026
a1d5b6c
chore(gates): use combined workflow in selection witnesses
thymikee Oct 19, 2026
cb6a208
ci: update selection and cache comments for shared Apple lane
thymikee Oct 19, 2026
83df6fb
chore(gates): preserve root doc skips on combined Apple workflow
thymikee Oct 19, 2026
2b609a7
fix(ci): resolve repository variables before entering iOS composites
thymikee Oct 19, 2026
fe8a125
chore(gates): guard composite context and optional artifact paths
thymikee Oct 19, 2026
03e8a51
fix(ci): decline the hosted runner network prompt before macOS replay
thymikee Oct 19, 2026
755d2ee
chore(gates): guard hosted prompt cleanup scope and ordering
thymikee Oct 19, 2026
f1403d7
fix(ci): restore host state between ownership-selected Apple phases
thymikee Oct 19, 2026
9776594
chore(gates): retire phase glob routing and enforce host isolation
thymikee Oct 19, 2026
c36f08a
chore(ci): capture hosted permission state at Apple phase boundaries
thymikee Oct 19, 2026
58cb5a0
fix(ci): match the captured hosted runner network prompt
thymikee Oct 19, 2026
86c0643
chore(gates): remove inactive workflow routing assertions
thymikee Oct 19, 2026
62268f8
chore(ci): retain selected iOS XCTest failure bundles
thymikee Oct 19, 2026
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
144 changes: 144 additions & 0 deletions .github/actions/smoke-ios-run/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
name: Run iOS smoke tests
description: Run iOS smoke tests
runs:
using: composite
steps:
- name: Set fixture producer wait policy
id: fixture-producer
run: |
set -euo pipefail
WAIT_SECONDS=1800
if [ "${{ github.event_name }}" = "pull_request" ] && [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then
echo "Fork pull request cannot publish the shared fixture artifact; a cache miss will build inline."
WAIT_SECONDS=0
fi
echo "wait-seconds=$WAIT_SECONDS" >> "$GITHUB_OUTPUT"
shell: bash
- name: Select iOS runner XCTests
id: xctest-impact
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
node --experimental-strip-types scripts/apple-ci-impact.ts xctest
shell: bash
- name: Establish host focus canary
id: host-focus-canary
run: |
set -euo pipefail
osascript -e 'tell application "Finder" to activate'
sleep 1
FRONTMOST="$(osascript -e 'tell application "System Events" to get bundle identifier of first application process whose frontmost is true')"
test "$FRONTMOST" = "com.apple.finder"
mkdir -p .tmp
echo "$FRONTMOST" > .tmp/ios-host-focus-before
shell: bash
- name: Restore and build iOS XCTest runner
uses: ./.github/actions/setup-apple-runner-build
with:
derived-path: ${{ env.AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH }}
cache-key-prefix: ios-runner-prebuilt
cache-key-suffix: -ios-${{ env.IOS_RUNTIME_VERSION }}-arm64
gate: swift-runner-ios
xcuitest-platform: ios
xcuitest-destination: generic/platform=iOS Simulator
- name: Boot iOS test simulator
id: ios-simulator
uses: ./.github/actions/boot-ios-test-simulator
with:
runtime-version: ${{ env.IOS_RUNTIME_VERSION }}
preferred-device-name: iPhone 17 Pro
- name: Run targeted iOS runner XCTest regressions
id: ios-xctest
if: steps.xctest-impact.outputs.run != 'false'
run: |
set -euo pipefail
XCTESTRUN_PATH="$(find "$AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH/Build/Products" -maxdepth 1 -name '*.xctestrun' -print -quit)"
test -n "$XCTESTRUN_PATH"
IOS_PR_TEST_ARGS=()
IOS_PR_TEST_IDS="$(node --experimental-strip-types scripts/check-xctest-selection.ts --ios-pr-tests)"
while IFS= read -r test_id; do
IOS_PR_TEST_ARGS+=("-only-testing:$test_id")
done <<< "$IOS_PR_TEST_IDS"
test "${#IOS_PR_TEST_ARGS[@]}" -gt 0
mkdir -p "$GITHUB_WORKSPACE/test/artifacts"
xcodebuild test-without-building \
-xctestrun "$XCTESTRUN_PATH" \
-destination "platform=iOS Simulator,id=${{ steps.ios-simulator.outputs.simulator-udid }}" \
-resultBundlePath "$GITHUB_WORKSPACE/test/artifacts/ios-runner-regressions.xcresult" \
"${IOS_PR_TEST_ARGS[@]}" 2>&1 | tee /tmp/agent-device-runner-regressions.log
node --input-type=module -e '
import { readFileSync } from "node:fs";
const log = readFileSync("/tmp/agent-device-runner-regressions.log", "utf8");
if (!/\] AGENT_DEVICE_RUNNER_OPTIONAL_PROBE_WAIT_COMPLETED$/m.test(log)) {
throw new Error("Optional observation ended the runner test before its wait completed");
}
'
shell: bash
- name: Preflight iOS runner through public CLI
run: |
pnpm clean:daemon
# The public-SDK record is the cold-startup evidence; the CLI timer only wraps attach.
node --input-type=module -e '
const { performance } = await import("node:perf_hooks");
const { ensureDaemon } = await import("./dist/src/index.js");
const t0 = performance.now();
const c = await ensureDaemon({ stateDir: process.env.AGENT_DEVICE_STATE_DIR });
console.log(JSON.stringify({ phase: "daemon_cold_startup", started: c.started, pid: c.daemon.pid, durationMs: Math.round(performance.now() - t0) }));
'
node bin/agent-device.mjs prepare ios-runner --platform ios --udid "${{ steps.ios-simulator.outputs.simulator-udid }}" --timeout "$AGENT_DEVICE_IOS_PREPARE_TIMEOUT_MS" --json
shell: bash
- name: Run iOS Settings replay smoke test
run: |
node bin/agent-device.mjs test test/integration/replays/ios/simulator/01-settings.ad --udid "${{ steps.ios-simulator.outputs.simulator-udid }}" --retries 2 --debug --artifacts-dir test/artifacts/replays-ios-simulator-smoke --report-junit test/artifacts/replays-ios-simulator-smoke.junit.xml
shell: bash
- name: Fetch current fixture app
id: fixture-app
uses: ./.github/actions/setup-fixture-app
with:
install: 'false'
wait-for-artifact-seconds: ${{ steps.fixture-producer.outputs.wait-seconds }}
- name: Report fixture cache source
run: |
echo "Fixture app source: ${{ steps.fixture-app.outputs.source }}" >> "$GITHUB_STEP_SUMMARY"
shell: bash
- name: Run gesture pan-duration smoke replay
run: |
xcrun simctl install "${{ steps.ios-simulator.outputs.simulator-udid }}" "${{ steps.fixture-app.outputs.app-path }}"
node bin/agent-device.mjs test examples/test-app/replays/gesture-pan-duration.ad --udid "${{ steps.ios-simulator.outputs.simulator-udid }}" --timeout 180000 --retries 2 --artifacts-dir test/artifacts/replays-ios-gesture-pan-duration --report-junit test/artifacts/replays-ios-gesture-pan-duration.junit.xml
shell: bash
- name: Run fixture-backed iOS simulator E2E smoke
env:
AGENT_DEVICE_FIXTURE_APP_ID: ${{ steps.fixture-app.outputs.app-id }}
AGENT_DEVICE_FIXTURE_APP_PATH: ${{ steps.fixture-app.outputs.app-path }}
AGENT_DEVICE_IOS_E2E: '1'
AGENT_DEVICE_IOS_E2E_TIER: smoke
AGENT_DEVICE_IOS_UDID: ${{ steps.ios-simulator.outputs.simulator-udid }}
run: |
xcrun simctl spawn "$AGENT_DEVICE_IOS_UDID" defaults write com.apple.keyboard.preferences AutomaticMinimizationEnabled -bool false
node --experimental-strip-types scripts/node-test-tmpdir.ts --test test/integration/smoke-ios-simulator-coverage.test.ts test/integration/smoke-ios-simulator.test.ts
shell: bash
- name: Assert simulator automation preserved host focus
if: ${{ always() }}
run: |
set -euo pipefail
test "$(cat .tmp/ios-host-focus-before)" = "com.apple.finder"
FRONTMOST="$(osascript -e 'tell application "System Events" to get bundle identifier of first application process whose frontmost is true')"
test "$FRONTMOST" = "com.apple.finder"
shell: bash
- name: Run iOS physical device smoke replay
if: env.IOS_UDID != ''
run: |
pnpm clean:daemon
node bin/agent-device.mjs test test/integration/replays/ios/device/01-physical-lifecycle.ad --udid "$IOS_UDID" --retries 2 --artifacts-dir test/artifacts/replays-ios-device-smoke --report-junit test/artifacts/replays-ios-device-smoke.junit.xml
shell: bash
- name: Assert iOS XCTest selection was honored
if: always()
env:
SELECTED: ${{ steps.xctest-impact.outputs.run }}
OUTCOME: ${{ steps.ios-xctest.outcome }}
run: |
if [ "$SELECTED" = 'true' ] && [ "$OUTCOME" = 'skipped' ]; then
echo '::error::iOS runner XCTest was selected but its step was skipped.'
exit 1
fi
shell: bash
101 changes: 101 additions & 0 deletions .github/actions/smoke-macos/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Run macOS smoke tests
description: Run macOS smoke tests
runs:
using: composite
steps:
- name: Run macOS command coverage contract
uses: ./.github/actions/run-gate
with:
gate: macos-coverage
- name: Check the packaged runner Swift parses and keeps its line numbering
uses: ./.github/actions/run-gate
with:
gate: packaged-runner-swift
- name: Run iOS snapshot Swift/TypeScript differential
uses: ./.github/actions/run-gate
with:
gate: ios-snapshot-differential
- name: Restore and build macOS XCTest runner
uses: ./.github/actions/setup-apple-runner-build
with:
derived-path: ${{ env.AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH }}
cache-key-prefix: macos-runner-prebuilt
gate: swift-runner-macos
xcuitest-platform: macos
xcuitest-destination: platform=macOS,arch=arm64
- name: Run the host-runnable runner XCTests
run: |
set -euo pipefail
XCTESTRUN_PATH="$(find "$AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH/Build/Products" -maxdepth 1 -name '*.xctestrun' -print -quit)"
test -n "$XCTESTRUN_PATH"
mkdir -p "$(dirname "$RESULT_BUNDLE_PATH")"
xcodebuild test-without-building \
-xctestrun "$XCTESTRUN_PATH" \
-destination 'platform=macOS,arch=arm64' \
-skip-testing:AgentDeviceRunnerUITests/RunnerTests/testCommand \
-resultBundlePath "$RESULT_BUNDLE_PATH"
shell: bash
- name: Extract the test-results summary
if: always()
run: |
set -uo pipefail
[ -d "$RESULT_BUNDLE_PATH" ] || exit 0
xcrun xcresulttool get test-results summary --path "$RESULT_BUNDLE_PATH" --compact \
> "$RESULT_SUMMARY_PATH" 2>/dev/null ||
xcrun xcresulttool get test-results summary --path "$RESULT_BUNDLE_PATH" \
> "$RESULT_SUMMARY_PATH" 2>/dev/null ||
rm -f "$RESULT_SUMMARY_PATH"
exit 0
shell: bash
- name: Report the run and assert it executed the source-derived set
if: always()
run: |
set -euo pipefail
if [ ! -s "$RESULT_SUMMARY_PATH" ]; then
echo 'No usable test-results summary was produced, so nothing here can show a test ran.'
echo 'The suite step above failed before or during the run; read its log first.'
echo '### iOS runner host XCTest lane' >> "$GITHUB_STEP_SUMMARY"
echo 'No result-bundle summary; see the run log and the uploaded bundle.' >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
node --experimental-strip-types scripts/xctest-run-summary.ts
shell: bash
- name: Upload result bundle
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: xctest-host-results-${{ github.run_id }}-${{ github.run_attempt }}
include-hidden-files: true
path: .tmp/xctest-host
if-no-files-found: warn
- name: macOS helper build and tests
uses: ./.github/actions/run-gate
with:
gate: macos-helper
- name: Run macOS integration test
uses: ./.github/actions/run-gate
with:
gate: replay-macos
args: |
--retries
2
--report-junit
test/artifacts/replays-macos.junit.xml
- name: Select Apple bridge proof
if: github.event_name == 'pull_request'
id: bridge-impact
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
node --experimental-strip-types scripts/apple-ci-impact.ts bridge
shell: bash
- name: Verify the darwin-only native source proofs (snapshot bridge, fold-helper -Werror, recording scripts)
if: github.event_name == 'pull_request' && steps.bridge-impact.outputs.run != 'false'
run: |
pnpm exec vitest run \
packages/platform-apple/src/snapshot-source/cache-native-runtime.test.ts \
packages/platform-apple/src/foldable/fold-helper-cache.test.ts \
packages/platform-apple/src/foldable/simulator-hid-native.test.ts \
packages/capture-kit/src/recording/__tests__/swift-cache-recording-scripts.test.ts
pnpm check:package -- --verify-snapshot-bridge-preparation
shell: bash
6 changes: 6 additions & 0 deletions .github/actions/upload-agent-device-artifacts/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ inputs:
required: false
default: '.agent-device-no-runner-derived'

additional-paths:
description: 'Optional additional diagnostic paths for a shared runner'
required: false
default: ''

runs:
using: 'composite'
steps:
Expand All @@ -30,3 +35,4 @@ runs:
${{ inputs.runner-derived-path }}/Logs/**
test/artifacts/**
test/screenshots/**
${{ inputs.additional-paths }}
Loading
Loading