Skip to content

fix(apple): prefer the last booted simulator over the first available one - #2848

Open
ahmdshrif wants to merge 1 commit into
react-native-community:mainfrom
ahmdshrif:fix-simulator-last-booted-preference
Open

fix(apple): prefer the last booted simulator over the first available one#2848
ahmdshrif wants to merge 1 commit into
react-native-community:mainfrom
ahmdshrif:fix-simulator-last-booted-preference

Conversation

@ahmdshrif

Copy link
Copy Markdown

Summary

findMatchingSimulator documents this priority when no simulator is requested:

If null, we'll go into default mode and return the currently booted simulator, the last booted simulator or if none is booted, it will be the first in the list.

The "last booted" tier never applies. In default mode (run-ios with no --simulator/--udid), the first available simulator in the list sets match on the very same iteration that could have recorded the fallback:

// If no match found, use first available simulator that was booted before
if (!!lastBootedAt && !match) {
  fallbackMatch = simulatorDescriptor;
}
// Keeps track of the first available simulator for use if we can't find one above.
if (simulatorName === null && !match) {
  match = simulatorDescriptor;   // <- always set on the first available device
}

For the first available device, fallbackMatch is only assigned when that same device also carries lastBootedAt — and match is then assigned to it anyway. For every later device both guards are dead because match is already truthy. So return match ?? fallbackMatch ?? null can never return fallbackMatch in default mode: the result is always the first available simulator, whichever simulator the developer actually used last.

That is the behaviour #1701 set out to change ("Sometimes, it's better to have the last booted simulator as a fallback, instead of the first available simulator"). #1709 then moved the assignment from match to a separate fallbackMatch so that an explicitly requested simulator wins (#1707), but the !match guard against the first-available assignment stayed, which leaves the feature unreachable.

In practice npx react-native run-ios boots whatever xcrun simctl list happens to return first for the newest runtime rather than the simulator you were last working on.

A second, smaller effect of the same shared match variable: when several simulators have been booted before, the one that is picked depends on list order rather than on which was booted most recently, and the tracked value is overwritten on each iteration despite the comment saying "first".

Fix

Split the single match accumulator into the three tiers the docblock describes, so each is recorded independently:

  • match — an exact --simulator name match (unchanged),
  • lastBootedMatch — the available simulator with the newest lastBootedAt,
  • firstAvailableMatch — the first available simulator, only tracked in default mode.

return match ?? lastBootedMatch ?? firstAvailableMatch ?? null.

Scope notes — this deliberately keeps the existing behaviour of every other path:

  • a currently Booted simulator still short-circuits and wins over everything;
  • an explicit --simulator <name> still wins over the last booted one (fix: pick selected simulator when defined instead of last booted #1709);
  • --udid still returns the exact match or null;
  • when --simulator <name> matches nothing, the previously-booted fallback is still returned rather than null, so getDestinationSimulator's error message and getFallbackSimulator's behaviour are untouched;
  • firstAvailableMatch is only tracked when no name was requested, so no path that returns null today starts returning a simulator.

Unparseable lastBootedAt values are still accepted as a fallback candidate (they just lose to any parseable one), so a malformed simctl value cannot turn a previously working selection into null.

Test Plan

Four tests added to packages/cli-platform-apple/src/tools/__tests__/findMatchingSimulator.test.ts.

Before the fix (tests applied, source unchanged) — 2 failed, 21 passed:

● findMatchingSimulator › should return the last booted simulator when it is not first in the list
    - "name": "iPhone 16 Pro"      (expected, the one with lastBootedAt)
    + "name": "iPhone 16"          (received, simply the first in the list)

● findMatchingSimulator › should return the most recently booted simulator when several were booted before
    - "name": "iPhone 16 Pro Max"  (expected, newest lastBootedAt)
    + "name": "iPhone 16"          (received, first in the list)

After the fix — 23 passed, with all 21 pre-existing tests passing unchanged, including should return the defined simulator in list even if another device is booted, should return picked simulator instead of last booted simulator in list, and the UDID and version-filter cases.

The other two new tests pass both before and after and are there as guards for the behaviour that must not change:

  • should prefer a currently booted simulator over the last booted one
  • should keep falling back to a previously booted simulator when the requested name is not available

Package and repo-wide:

$ yarn jest packages/cli-platform-apple
Test Suites: 8 passed, 8 total
Tests:       43 passed, 43 total

$ yarn jest packages
Test Suites: 55 passed, 55 total
Tests:       1 todo, 317 passed, 318 total
Snapshots:   62 passed, 62 total

$ node ./scripts/buildTs.js   # clean
$ yarn eslint packages/cli-platform-apple/src/tools/findMatchingSimulator.ts \
              packages/cli-platform-apple/src/tools/__tests__/findMatchingSimulator.test.ts   # clean

Verification is unit tests only — I have not linked this into a local react-native checkout, so the third checklist box is left unchecked. The change is confined to the selection logic in findMatchingSimulator; the fixtures in the test file are real xcrun simctl list --json devices shapes, including both the availability: '(available)' and the newer isAvailable: true forms.

Checklist

  • Documentation is up to date.
  • Follows commit message convention described in CONTRIBUTING.md.
  • For functional changes, my test plan has linked these CLI changes into a local react-native checkout (instructions) — not done; verified with unit tests only.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant