diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index 9459c286ce..b8e046bc3a 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -199,7 +199,6 @@ jobs: run: | xcrun simctl install "${{ steps.ios-simulator.outputs.simulator-udid }}" "${{ steps.fixture-app.outputs.app-path }}" node --experimental-strip-types src/bin.ts 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 - pnpm clean:daemon - name: Run fixture-backed iOS simulator E2E smoke env: diff --git a/.github/workflows/replays-manual.yml b/.github/workflows/replays-manual.yml index d5e0d8ad2e..59c76e1d2c 100644 --- a/.github/workflows/replays-manual.yml +++ b/.github/workflows/replays-manual.yml @@ -156,9 +156,7 @@ jobs: - name: Prove selector drag reaches its destination on iOS run: | xcrun simctl install "${{ steps.ios-simulator.outputs.simulator-udid }}" "${{ steps.fixture-app.outputs.app-path }}" - pnpm clean:daemon node --experimental-strip-types src/bin.ts replay examples/test-app/replays/drag.ad --platform ios --udid "${{ steps.ios-simulator.outputs.simulator-udid }}" --session nightly-ios-target-drag --json - pnpm clean:daemon - name: Run full fixture-backed iOS simulator E2E env: diff --git a/test/integration/ios-simulator-e2e/live-runner.ts b/test/integration/ios-simulator-e2e/live-runner.ts index c06669e0c9..7f4511f423 100644 --- a/test/integration/ios-simulator-e2e/live-runner.ts +++ b/test/integration/ios-simulator-e2e/live-runner.ts @@ -45,6 +45,7 @@ import { } from './snapshot-backend-conformance.ts'; const C = PUBLIC_COMMANDS; +const DEFAULT_RUNNER_PREPARE_TIMEOUT_MS = 420_000; type AgentDeviceSdk = typeof import('../../../src/sdk/index.ts'); @@ -85,6 +86,7 @@ export async function runIosSimulatorE2E(): Promise { const context = createContext(); let primaryError: unknown; try { + await prepareRunner(context); await executeLiveScenarios(context); } catch (error) { primaryError = error; @@ -93,6 +95,31 @@ export async function runIosSimulatorE2E(): Promise { throwLiveRunErrors(primaryError, cleanupError); } +/** A runner start must not spend the first runner-backed command's 90 s request timeout. */ +async function prepareRunner(context: LiveContext): Promise { + const timeoutMs = Number( + process.env.AGENT_DEVICE_IOS_PREPARE_TIMEOUT_MS ?? DEFAULT_RUNNER_PREPARE_TIMEOUT_MS, + ); + await runStep( + context, + 'prepare iOS runner', + [ + 'prepare', + 'ios-runner', + '--platform', + 'ios', + '--udid', + context.udid, + '--state-dir', + context.stateDir, + '--timeout', + String(timeoutMs), + '--json', + ], + { commonFlags: false, timeoutMs: timeoutMs + 30_000 }, + ); +} + async function executeLiveScenarios(context: LiveContext): Promise { let assertionCaptureVerified = false; for (const scenario of LIVE_SCENARIOS.filter((candidate) => candidate.tier === 'smoke')) { @@ -141,6 +168,11 @@ async function assertLiveAssertionCapture(context: LiveContext): Promise { async function finalizeLiveRun(context: LiveContext): Promise { let cleanupError = await finalizeSessionCleanup(context, sessionExists, cleanupSession); + try { + await stopDaemon(context); + } catch (error) { + cleanupError = combineErrors(cleanupError, error, 'cleanup and daemon stop failed'); + } try { writeCoverageReport(context); } catch (error) { @@ -149,6 +181,18 @@ async function finalizeLiveRun(context: LiveContext): Promise { return cleanupError; } +/** The daemon holds this run's TMPDIR and claims directory, which the test wrapper deletes. */ +async function stopDaemon(context: LiveContext): Promise { + await runStep( + context, + 'stop daemon', + ['daemon', 'stop', '--state-dir', context.stateDir, '--json'], + { + commonFlags: false, + }, + ); +} + /** * Decides whether session-scoped cleanup runs: full:device-lifecycle reboots the * simulator and the session lease does not survive that in every environment, so this