You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(open): answer the iOS launch confirmation for --launch-url (#3084)
* feat(apple): answer the launch confirmation open --launch-url raises
On an iOS Simulator, `open <bundle> --launch-url <scheme-url>` hands the URL
to SpringBoard, which can hold the launch behind `Open in "<App>"?`. After the
launch settles, the open reads the alert once through the runner. A
confirmation naming the session app (display name from `simctl listapps`) is
accepted, the released launch is observed again, and the outcome carries
`launchConfirmation: 'accepted'`. A confirmation naming any other app is never
accepted: the open fails with `reason: launch_confirmation_foreign_app` and the
named app in `details.appName`. No alert (the runner's typed ALERT_NOT_FOUND)
or a non-confirmation alert leaves the outcome as before. The read is skipped
once the launch budget (IOS_APP_LAUNCH_TIMEOUT_MS) is spent, and a
confirmable open demands the runner even under an observation-only plan.
Tests and the mutation that makes each fail:
- launch-confirmation.test.ts
- accepts ... curly/straight quotes: drop `"` from the title pattern.
- foreign app never accepted: replace `namedApp !== sessionAppName` with
`false`.
- no alert costs one read: remove the `isAlertNotFoundError` return.
- non-confirmation alert left for the caller: make the title pattern
match any `Open in|Allow` prefix.
- failed alert read reported as itself: catch every read error as absence.
- port reads/accepts for the session app: drop `appBundleId` from the
alert target, or return the first listed app's name.
- lifecycle.test.ts
- accepted and reported: remove the re-settle after accept (event order),
or keep `resolveAppleSimulatorRunnerDemand` for confirmable opens
(runnerDemand 'none').
- no confirmation costs one alert read: same runner-demand mutation.
- foreign app fails without accepting: skip the foreign-app check.
- no/web launch URL never reads an alert: drop `!isWebUrl(url)` from
`confirmable`.
- physical iOS never reads an alert: drop `device.kind === 'simulator'`
from `confirmable`.
* feat(open): report launchConfirmation in the open response
`completeOpenCommand` forwards the lifecycle outcome's `launchConfirmation`
into `buildOpenResult`, which sets `data.launchConfirmation` only when the
platform answered a confirmation. The Node client's `apps.open` result carries
it as `launchConfirmation: 'accepted'`. The open command help names both
outcomes.
Tests and the mutation that makes each fail:
- session-open-surface.test.ts "buildOpenResult reports an answered launch
confirmation...": remove `if (launchConfirmation) result.launchConfirmation`.
- session-open-execution-runtime.test.ts "open reports the launch confirmation
its platform answered": drop `launchConfirmation: outcome.launchConfirmation`
from the `buildOpenResult` call.
- client.test.ts "apps.open reports an answered launch confirmation only when
the daemon answered one": replace `data.launchConfirmation === 'accepted'`
with `false`.
* docs(commands): describe how open answers the iOS launch confirmation
Adds both outcomes of an iOS Simulator `open --launch-url` prompt to the Navigation notes: a prompt naming the session app is answered and reported as launchConfirmation: "accepted"; a prompt naming another app fails the open with launch_confirmation_foreign_app and details.appName. Docs only; no test.
* test(ios-smoke): delete answerDeepLinkConfirmation now that open answers it
`open --launch-url` answers the `Open in "<App>"?` confirmation itself, so the
smoke lane's `answerDeepLinkConfirmation` / `acceptDeepLinkConfirmationIfPresent`
helper and its unit test are deleted. The three deep-link scenarios (automation
lab, WebView lab, visible-depth frontier) now assert the open either raised no
confirmation or reports `launchConfirmation: "accepted"`, then wait once for
their route landmark with a 30 s budget (the released launch has taken over
20 s to reach the foreground on a loaded CI host). `assertWaitSelector` takes
the same optional `timeoutMs` as `assertWaitText`.
No new unit test: the changed files are live-device scenario code, exercised
only by the iOS Simulator smoke lane. The deleted test covered the deleted
helper.
* feat(cli): print launchConfirmation in open --json output
The CLI serializes open through the Node client result, and serializeOpenResult listed its fields explicitly, so an answered confirmation reached the client but not `open --json`. It now carries `launchConfirmation` when present. Found by the live iOS Simulator run: the runner log showed the confirmation read and accepted while the CLI JSON had no field.
Test and the mutation that makes it fail:
- result-serialization.test.ts "serializeOpenResult carries an answered launch confirmation": delete the launchConfirmation spread in serializeOpenResult.
* fix(apple): read the launch confirmation only when the bridge cannot see the app
The alert read now runs only after the host AX bridge reports the launched
app unobservable, within what is left of the launch budget, so an open whose
app is observed keeps its runner demand and timing. A failed or late read
leaves the open as it was. A prompt is accepted only when its name resolves
to exactly one installed bundle and that bundle is the session app; a name
no single bundle carries is left unanswered, and the foreign-app failure
names foreignAppBundleId and sessionAppBundleId.
* test(client): move apps namespace cases into client-apps.test.ts
client.test.ts grew past the test-file size ratchet (1573 lines vs 1552 at
the merge-base). Move the apps.open and apps.installFromSource cases, which
exercise the client's apps namespace, into their own file. The cases are
moved unchanged.
* fix(apple): read the launch confirmation only when the launched app itself is unreadable
The Simulator launch observation now reports probe-failed, with its typed failure, for an
unresolvable target, an open bridge circuit, or a bridge failure that is not a launch
transition. Only an app with no running process or an expired launch-transition window stays
unobservable, so only those opens read the alert. An open that reads it records runner demand
'required'.
* fix(apple): attribute the launch confirmation to the URL scheme owner within one budget
The sheet title now only recognizes a launch confirmation. The app it would open is the URL
scheme owner from resolveIosSimulatorDeepLinkBundleId, so a localized app name can no longer
attribute a foreign app's sheet to the session app; the listapps name matching is deleted.
The alert read, the runner interactor resolution, the owner lookup and the accept share the
remaining launch budget. A failed or late leg leaves the open as it was, with a diagnostic.
* docs(open): state that open answers only a confirmation present before it sees the app
The --launch-url help and the commands page now attribute the prompt to the URL scheme owner
and say a prompt that appears after open has seen the app is left for alert accept.
* test(ios-smoke): retry the deep-link destination wait in one shared helper
The three deep-link scenarios wait through waitForDeepLinkDestination: up to five 15 s waits,
retried only on a miss that says the destination was not observed yet, with no alert step.
assertLaunchConfirmationAnswered could not fail, so it is deleted.
* refactor(apple): carry the session app with the confirmable launch URL
AppleLaunchPlan.confirmation holds the URL and the bundle it was checked for, so the answer no longer re-checks appBundleId. followUpUrl stays separate: it says how the URL is dispatched, confirmation says whether SpringBoard may hold it, and a direct Simulator launch has both.
* fix(apple): let the confirmation answer finish instead of racing a budget
The race only stopped the waiting: a late accept could still tap the sheet after open returned unanswered. Each step is already bounded (one runner alert get, the simctl owner lookup, actOnAppleAlert's 2 s retry window), so the timer, the Leg enum and the launch-budget arithmetic go away and the answer awaits each step to its own end.
* refactor(apple): read a typed alert absence as no alert at the port
alert.ts maps the backend's typed absence to undefined in alertIfPresent and keeps isAlertNotFoundError private; the answer no longer branches on which step failed.
* refactor(apple): build the confirmation port from one resolved interactor
The port binds readAlert and acceptAlert once; the answer resolves the runner interactor a single time and leaves the open unanswered when it cannot.
* refactor(apple): answer the launch confirmation inside the one open settle
settleAppleOpen observes the launched app as a typed LaunchObservation; when the verdict is unobservable and the plan can answer a confirmation, it answers once and observes again, then writes timing once. The second settle, the delete of a stale failure and the reads back from timing go away, and postOpenSettleDurationMs now spans the whole settle.
* refactor(contracts): give the post-open observation failure one meaning per field
PostOpenObservationFailure is a union keyed by source: capture (Android error code and reason), target (iOS target resolution error code and reason), bridge (bridge failure kind and code), circuit (no fields). The circuit no longer invents a code pair, the bridge no longer swaps kind into code, and the doc note that explained the swap is gone. Android's released fields keep their meaning and gain source.
* fix(apple): report a launch alert open cannot recognize as a confirmation
The runner reports an alert only as its localized title and button labels, and matching the app name is unsafe because permission alerts name the app too, so the confirmation stays recognized by its English title. An alert that is present but not recognized now emits a warn diagnostic with its title and buttons, and commands.md and the open help state the English-only limit.
* fix(apple): bound and abort the launch URL owner lookup
The settle's URL-owner lookup ran simctl listapps and one plutil per
installed app with no timeout and no signal, so a wedged CoreSimulator
held open --launch-url without limit. The lookup now shares one 10 s
deadline across every spawn and the open's signal aborts it.
* test(apple): clear the wedged spawn's timeout when its signal aborts
* test(apple): end a wedged spawn at once when its signal is already aborted
* docs(open): an owner the lookup cannot read is one more unresolved case
* fix(apple): keep the app-list conversion inside the owner lookup budget and surface its timeout
* test(apple): bound the spawn wait and index the last arg with at()
0 commit comments