Skip to content

test(ios): wait out the launch an accepted deep-link confirmation releases - #2902

Merged
thymikee merged 2 commits into
fix/ios-webview-wait-runner-busy-2491from
fix/ios-deep-link-accept-launch-2491
Sep 24, 2026
Merged

thymikee merged 2 commits into
fix/ios-webview-wait-runner-busy-2491from
fix/ios-deep-link-accept-launch-2491

Conversation

@thymikee

@thymikee thymikee commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Summary

Wait for a deep-link destination after accepting an iOS “Open in …?” confirmation, including a launch delayed by a busy host. A readable destination timeout can still conceal a real confirmation, so the helper now probes on typed readable misses as well as APP_NOT_RUNNING. A no-prompt stalled capture gets bounded retries; a readable wrong route returns to its caller after one probe. Five 15-second waits cover the 51.9-second XCTest restart observed in the linked failure.

This PR is stacked on #2901: its fast absent-alert query is required before probing a rendered WebView on a readable miss. Five files changed relative to that base. Refs #2491.

Validation

26459db72c8deb251f3d11e12f56a50a44dae7dd: focused helper tests 10/10 passed, including planted-red readable-timeout/prompt and retry cases. pnpm check:affected --run passed all runnable checks on this exact stacked commit. The synthetic tests prove the branch decisions and bounded waits; the 51.9-second duration comes from the failed CI run. Exact-head iOS CI is pending. Do not merge before #2901 is validated and landed.

@thymikee

Copy link
Copy Markdown
Member Author

Live evidence for 3c285bbbe6. The sim is an iPhone 17 Pro on iOS 26.2. com.apple.launchservices.schemeapproval is deleted before every run, so each run meets the "Open in …?" prompt. Each line shows a step, its exit status and its duration in ms.

CI tap to foreground, from the runner session logs of 11 passing runs (ms): 1669, 1422, 834, 956, 1467, 3105, 2238, 925, 1491, 761, 1293. The failing run 35991523779 took 20 700.

Pre-fix, natural load (main helper): the prompt run failed wait for Automation lab with APP_NOT_RUNNING. The tap was at 14:13:02.57 and the app reached the foreground at 14:13:29.99 (27.4 s).

Pre-fix, held launch (terminate after accept, deep link lands 20 s later):

deep link:0 | wait for deep-link destination before inspecting system UI:1:APP_NOT_RUNNING | inspect alert:0 | accept:0 | wait for Automation lab:1:10131:APP_NOT_RUNNING
Fix, natural: 10/10 prompt-path runs
run 1: PASS prompt=true totalMs=37725 steps=cold launch fixture through a deep link:0:14207 | wait for the deep-link destination (1/4):1:15831:COMMAND_FAILED | inspect delayed deep-link system alert:0:1340 | accept deep-link confirmation:0:3254 | wait for the deep-link destination (2/4):0:2600 | wait for Automation lab:0:482
run 2: PASS prompt=true totalMs=50044 steps=cold launch fixture through a deep link:0:17748 | wait for the deep-link destination (1/4):1:15338:COMMAND_FAILED | inspect delayed deep-link system alert:0:4721 | accept deep-link confirmation:0:5920 | wait for the deep-link destination (2/4):0:4371 | wait for Automation lab:0:1870
run 3: PASS prompt=true totalMs=61491 steps=cold launch fixture through a deep link:0:34299 | wait for the deep-link destination (1/4):1:16229:COMMAND_FAILED | inspect delayed deep-link system alert:0:1567 | accept deep-link confirmation:0:3157 | wait for the deep-link destination (2/4):0:3722 | wait for Automation lab:0:2496
run 4: PASS prompt=true totalMs=54866 steps=cold launch fixture through a deep link:0:33868 | wait for the deep-link destination (1/4):1:15632:COMMAND_FAILED | inspect delayed deep-link system alert:0:1181 | accept deep-link confirmation:0:2945 | wait for the deep-link destination (2/4):0:693 | wait for Automation lab:0:541
run 5: PASS prompt=true totalMs=36299 steps=cold launch fixture through a deep link:0:11646 | wait for the deep-link destination (1/4):1:15585:COMMAND_FAILED | inspect delayed deep-link system alert:0:1718 | accept deep-link confirmation:0:3368 | wait for the deep-link destination (2/4):0:3770 | wait for Automation lab:0:204
run 6: PASS prompt=true totalMs=44441 steps=cold launch fixture through a deep link:0:23053 | wait for the deep-link destination (1/4):1:15321:COMMAND_FAILED | inspect delayed deep-link system alert:0:1200 | accept deep-link confirmation:0:1670 | wait for the deep-link destination (2/4):0:2929 | wait for Automation lab:0:265
run 7: PASS prompt=true totalMs=30647 steps=cold launch fixture through a deep link:0:9821 | wait for the deep-link destination (1/4):1:15251:COMMAND_FAILED | inspect delayed deep-link system alert:0:1131 | accept deep-link confirmation:0:3087 | wait for the deep-link destination (2/4):0:1126 | wait for Automation lab:0:218
run 8: PASS prompt=true totalMs=31471 steps=cold launch fixture through a deep link:0:9752 | wait for the deep-link destination (1/4):1:15161:COMMAND_FAILED | inspect delayed deep-link system alert:0:1233 | accept deep-link confirmation:0:2510 | wait for the deep-link destination (2/4):0:2397 | wait for Automation lab:0:413
run 9: PASS prompt=true totalMs=38099 steps=cold launch fixture through a deep link:0:15675 | wait for the deep-link destination (1/4):1:15206:COMMAND_FAILED | inspect delayed deep-link system alert:0:1374 | accept deep-link confirmation:0:2827 | wait for the deep-link destination (2/4):0:2593 | wait for Automation lab:0:415
run 10: PASS prompt=true totalMs=43643 steps=cold launch fixture through a deep link:0:18925 | wait for the deep-link destination (1/4):1:15772:COMMAND_FAILED | inspect delayed deep-link system alert:0:1669 | accept deep-link confirmation:0:2842 | wait for the deep-link destination (2/4):0:2957 | wait for Automation lab:0:1471
Fix, launch held 20 s after accept: 5/5
run 1: PASS held=20000ms landedAfterRelease=8906 totalMs=66011 steps=deep link:0:13659 | wait for the deep-link destination (1/4):1:15227:COMMAND_FAILED | inspect delayed deep-link system alert:0:1367 | accept deep-link confirmation:0:2784 | wait for the deep-link destination (2/4):1:15695:COMMAND_FAILED | wait for the deep-link destination (3/4):0:12699 | wait for Automation lab:0:509
run 2: PASS held=20000ms landedAfterRelease=7549 totalMs=62722 steps=deep link:0:12147 | wait for the deep-link destination (1/4):1:16003:COMMAND_FAILED | inspect delayed deep-link system alert:0:1681 | accept deep-link confirmation:0:2332 | wait for the deep-link destination (2/4):1:15454:COMMAND_FAILED | wait for the deep-link destination (3/4):0:11855 | wait for Automation lab:0:237
run 3: PASS held=20000ms landedAfterRelease=4035 totalMs=54034 steps=deep link:0:8411 | wait for the deep-link destination (1/4):1:15222:COMMAND_FAILED | inspect delayed deep-link system alert:0:1238 | accept deep-link confirmation:0:2584 | wait for the deep-link destination (2/4):1:15607:COMMAND_FAILED | wait for the deep-link destination (3/4):0:8243 | wait for Automation lab:0:182
run 4: PASS held=20000ms landedAfterRelease=7657 totalMs=53476 steps=deep link:0:5900 | wait for the deep-link destination (1/4):1:15111:COMMAND_FAILED | inspect delayed deep-link system alert:0:1148 | accept deep-link confirmation:0:2415 | wait for the deep-link destination (2/4):1:15164:COMMAND_FAILED | wait for the deep-link destination (3/4):0:12304 | wait for Automation lab:0:187
run 5: PASS held=20000ms landedAfterRelease=5110 totalMs=60887 steps=deep link:0:12850 | wait for the deep-link destination (1/4):1:15259:COMMAND_FAILED | inspect delayed deep-link system alert:0:1252 | accept deep-link confirmation:0:2782 | wait for the deep-link destination (2/4):1:15491:COMMAND_FAILED | wait for the deep-link destination (3/4):0:9413 | wait for Automation lab:0:202

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.81 MB 4.81 MB 0 B
Package (unpacked) 4.81 MB 4.81 MB 0 B
Package (download) 1.44 MB 1.44 MB 0 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 27.2 ms 27.7 ms +0.5 ms
CLI --help 80.0 ms 79.5 ms -0.5 ms

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed at 3c285bb. The change looks correct, and moving the deep-link confirmation wait into live-deep-link-confirmation.ts reads clean. I found nothing to fix. The iOS Smoke Tests job runs exactly these scenarios, so its result is the direct check for this change; it was still running when I looked. There are no conflicts.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Sep 24, 2026
@thymikee

Copy link
Copy Markdown
Member Author

I found a prompt path the current APP_NOT_RUNNING condition misses. In the passing iOS run 35961586126, the first Automation destination wait made five successful XCTest snapshot_capture reads, then timed out after 15 s; alert get returned the real “Open in…” prompt and alert accept succeeded. wait-polling.ts maps a deadline after readable captures to wait_deadline_exceeded without details.runnerErrorCode: APP_NOT_RUNNING (lines 137–149, 185–187, 215–220). answerDeepLinkConfirmation therefore returns before probing and the caller cannot reach the route. The 10/10 prompt runs in this PR exercise the other failure shape. Please add a deterministic readable-capture timeout → Open prompt case and preserve that probe, while keeping the rendered-WebView case from querying every element. Also keep bounded recovery for a stalled/restarting runner: the failed run's XCTest listener became ready 51.9 s after the first WebView landmark wait began, beyond the current helper-plus-page 35 s budget. I would hold #2902's merge and #2897's closure until those paths are covered.

@thymikee
thymikee changed the base branch from main to fix/ios-webview-wait-runner-busy-2491 September 24, 2026 14:15
@thymikee
thymikee force-pushed the fix/ios-deep-link-accept-launch-2491 branch from 3c285bb to 26459db Compare September 24, 2026 14:15
@thymikee

Copy link
Copy Markdown
Member Author

Addressed the readable-timeout and restart finding in 26459db72c. A typed readable timeout now checks for the real Open confirmation, and accepting one always gets a bounded follow-up wait. Stalled or truncated captures retry within five 15-second waits; a readable wrong route without a prompt returns after one probe. The two new edge tests were red before the fix and the exact-head local gate passed. I stacked this PR on #2901 because its fast absent-alert query is required for a safe probe on a rendered WebView. Exact-head device CI is pending; #2901 must land first.

@thymikee
thymikee added this pull request to stack #2914 September 24, 2026 14:44
@thymikee

Copy link
Copy Markdown
Member Author

At 26459db the wait loop and its four new wait-reason branches trace correctly against the WaitReason taxonomy in packages/contracts/src/wait.ts and against src/commands/interaction/runtime/wait-polling.ts, so I have no blocking findings this round.

Not blocking: test/integration/ios-simulator-e2e/live-deep-link-confirmation.ts#L14 keys its four new branches (plus the existing APP_NOT_RUNNING check) on string literals instead of importing WAIT_REASONS/WaitReason from @agent-device/contracts/wait the way smoke-ios-target-activation.test.ts already does, so a future rename in the taxonomy would silently stop matching instead of failing to compile — take it or leave it.

I verified this by reading the code against the taxonomy, not by running the unit test file or pnpm check:affected, so the trace is static.

Both iOS Smoke Tests checks are still queued or in progress on this head, so there is no failure to attribute yet, and the exact-head local gate and 10/10 helper-test count you reported stay unverified beyond this trace.

I could only confirm that commit 56e1253 from #2901 is already an ancestor of this branch's merge base, not how GitHub will order the actual merge.

Before this is merge-ready, the iOS Smoke Tests job needs to finish and pass on 26459db, exercising the deep-link-confirmation scenario end to end on a real simulator, showing the readable-timeout probe path and the 5x15s budget hold under real XCTest latency rather than only under the unit tests' predictions.

…eases

The smoke automation scenario accepted SpringBoard's "Open in …?" prompt and then gave the
route one 10 s wait. On a loaded host the released launch reached the foreground 20.7 s after
the Open tap (CI run 35991523779; 27.4 s locally), so every read answered the retriable
APP_NOT_RUNNING and the step failed although the app later rendered the deep-linked route.

The deep-link confirmation helper moves to its own module and keys every step on the typed
details.runnerErrorCode: only a destination wait that ends on APP_NOT_RUNNING probes for the
confirmation or earns another wait, up to four 15 s waits. Any other miss returns to the
caller's own destination assertion, and the alert probe never queries a rendered screen.

Refs #2491

@thymikee thymikee left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 3922d06. The recovery shape is right. The details left are that the helper spells wait vocabulary and alert failure semantics ad hoc instead of anchoring them in the types/errors that own those meanings.

const details = arrived.json?.error?.details;
const launchPending = details?.runnerErrorCode === APP_NOT_RUNNING;
const reason = details?.reason;
const readableMiss = reason === 'wait_target_absent' || reason === 'wait_deadline_exceeded';

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These literals duplicate the canonical wait vocabulary in packages/contracts/src/wait.ts. The helper is deciding routing from the same wait reasons that wait polling already classifies, so importing WAIT_REASONS would keep the spelling and the mapping from drifting when wait-polling changes.


async function acceptOpenConfirmation(device: DeepLinkConfirmationDevice): Promise<boolean> {
const alert = await device.inspectAlert();
if (alert.status !== 0) return false;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Treating every nonzero alert.status as "no prompt" conflates absence with other alert-get failures. Apple's no-alert case is a typed ALERT_NOT_FOUND failure, but busy, transport, and malformed failures also surface as nonzero. Distinguish the typed absence shape from other errors; the helper should not answer an Open-in probe as though it had just proved there is no system prompt.

@thymikee
thymikee merged commit 86d719d into main Sep 24, 2026
17 checks passed
@thymikee
thymikee deleted the fix/ios-deep-link-accept-launch-2491 branch September 24, 2026 15:24
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-24 15:26 UTC

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed the delta since 26459db at 3922d06. The wait-loop logic is unchanged since the earlier clean review: the retries stay bounded, and a readable wrong route still returns after one probe.

All checks were still queued or running at review time. If one fails, compare it first with the upstream files the rebase picked up, because they do not overlap this PR's iOS e2e helper. The iOS Smoke Tests lane is the live check for the deep-link confirmation path.

The branch now conflicts with its base, so I removed ready-for-human until the conflict is resolved.

@thymikee thymikee removed the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Sep 24, 2026
@thymikee

Copy link
Copy Markdown
Member Author

Addressed the nonblocking taxonomy note in #2919, since #2902 merged while I was validating it. The helper and its test fixtures now use WAIT_REASONS for all four wait-reason branches. The APP_NOT_RUNNING runner code remains a wire literal: its canonical constant is private to the Apple runner package, and exporting it solely for this integration helper would widen that package's API.

At 14cb69fe19, the focused helper suite passes 10/10 and pnpm check:affected --run passes all runnable checks. GitHub CI on #2919 is pending. The change does not alter the #2902 retry behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant