Skip to content

Stop launching the app in sample UI tests that never use it - #423

Merged
tylervick merged 1 commit into
mainfrom
tylervick/fix-flaky-launch
Aug 10, 2026
Merged

tylervick merged 1 commit into
mainfrom
tylervick/fix-flaky-launch

Conversation

@tylervick

@tylervick tylervick commented Aug 10, 2026 •

Copy link
Copy Markdown
Member

Closes #398.

The flake

FirstSuite, SecondSuite and ThirdSuite each called XCUIApplication().launch() in setUp with continueAfterFailure = false. When the simulator was slow the launch failed and took the test with it — FirstSuite.testOne is XCTAssert(true) plus an attachment and cannot fail on its own assertion, yet was observed failing (53adfaf passed under Test and failed under Codecov, same commit, same commands).

No test uses the UI

There are no taps, no element queries, and no XCUIApplication use anywhere in the sample app beyond the launch itself. The launch existed to make these genuine UI tests, on the assumption that the screen recording depended on it.

That assumption was wrong, and I tested it rather than reasoning about it

Removed the launch from SecondSuite alone, regenerated, and counted recordings in the fixture:

distinct screen recordings
before 9
after (SecondSuite only) 9

Had SecondSuite lost its recordings the count would have dropped by two. Xcode captures the simulator screen for UI test targets regardless of whether the app runs.

So the launches were pure flake risk with no fixture value.

Side effect: fixture generation is 41% faster locally

85.7s → 50.4s, because three suites no longer launch and terminate an app per test method.

Whether that carries to CI is for CI to measure — #412 records why local timing is not evidence for this repository, and the last attempt at that issue (#422) was closed after CI showed it slower despite a plausible local story. Same discipline applies here.

One honest cost

SanityResults.xcresult shrinks from ~51M to ~140K, because a recording of an idle screen is much smaller than one of a launching app.

TestResults.xcresult still carries 9 recordings and 16 .mp4 references, so .mp4 attachment coverage — the reason the launches were believed necessary — is intact. If a fixture is later needed that genuinely exercises a launching app, it should be one suite that does so deliberately, not every suite by default.

Verified

  • swift test → 23 tests, 1 skipped, 0 failures
  • TestResults reports All(16) Passed(9) Skipped(1) Failed(6), consistent with the suite after Add Swift Testing (@Test) fixture and structural assertions #417's Swift Testing fixture
  • zero launch failures in the generation log
  • SwiftFormat clean; the 4 unneeded_override SwiftLint warnings are pre-existing on main (empty tearDown overrides) and untouched here

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests
    • Updated UI test setup to prevent the application from launching automatically before every test.
    • Preserved failure-handling configuration during test initialization.

Closes #398.

FirstSuite, SecondSuite and ThirdSuite each called
XCUIApplication().launch() in setUp with continueAfterFailure = false.
When the simulator was slow, the launch failed and took the test with it
before its body ran — FirstSuite.testOne is XCTAssert(true) plus an
attachment and cannot fail on its own assertion, yet it was observed
failing.

No test in the sample app touches the app's UI. There are no taps, no
element queries, no XCUIApplication use beyond the launch itself. The
launch existed only to make these genuine UI tests, on the assumption
that the screen recording depended on it.

It does not. Verified by controlled experiment: removed the launch from
SecondSuite alone and regenerated — the fixture still contained 9
distinct screen recordings, exactly as before. Had SecondSuite lost its
recordings the count would have dropped by two. Xcode captures the
simulator screen for UI test targets regardless of whether the app runs.

So the launches were pure flake risk with no fixture value.

Fixture generation also drops from 85.7s to 50.4s locally, a 41%
saving, because three suites no longer launch and terminate an app per
test method. Whether that carries to CI is for CI to measure — see #412
for why local timing is not evidence here.

One honest cost: SanityResults.xcresult shrinks from ~51M to ~140K,
because a recording of an idle screen is much smaller than one of a
launching app. TestResults still carries 9 recordings and 16 .mp4
references, so .mp4 attachment coverage — the reason the launches were
thought necessary — is intact.

Verified: swift test passes 23 tests with 1 skipped; TestResults reports
All(16) Passed(9) Skipped(1) Failed(6), consistent with the suite after
the Swift Testing fixture in #417.
@coderabbitai

coderabbitai Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a5fc4a06-3dab-4890-a1a8-2bde51518d21

📥 Commits

Reviewing files that changed from the base of the PR and between 4e297a8 and 2f366f1.

📒 Files selected for processing (3)
  • XCTestHTMLReportSampleApp/SampleAppUITests/FirstSuite.swift
  • XCTestHTMLReportSampleApp/SampleAppUITests/SecondSuite.swift
  • XCTestHTMLReportSampleApp/SampleAppUITests/ThirdSuite.swift
💤 Files with no reviewable changes (3)
  • XCTestHTMLReportSampleApp/SampleAppUITests/FirstSuite.swift
  • XCTestHTMLReportSampleApp/SampleAppUITests/SecondSuite.swift
  • XCTestHTMLReportSampleApp/SampleAppUITests/ThirdSuite.swift

📝 Walkthrough

Walkthrough

The three sample UI test suites no longer launch XCUIApplication automatically in setUp(). Their remaining setup continues to configure test failure behavior.

Changes

UI test setup

Layer / File(s) Summary
Remove automatic application launch
XCTestHTMLReportSampleApp/SampleAppUITests/FirstSuite.swift, XCTestHTMLReportSampleApp/SampleAppUITests/SecondSuite.swift, XCTestHTMLReportSampleApp/SampleAppUITests/ThirdSuite.swift
Removed automatic application launches from the three UI test suites. continueAfterFailure setup remains. Removing the launch avoids launch failures before test bodies execute.

Estimated code review effort: 1 (Trivial) | ~3 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes removing unnecessary app launches from sample UI tests.
Linked Issues check ✅ Passed The changes remove the launch-related flakiness in all three suites while verification confirms required screen recordings and .mp4 coverage remain intact [#398].
Out of Scope Changes check ✅ Passed All changes are limited to removing unnecessary application launches from the three suites identified by the linked issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tylervick/fix-flaky-launch

Comment @coderabbitai help to get the list of available commands.

@tylervick
tylervick merged commit c590ac4 into main Aug 10, 2026
8 checks passed
@tylervick
tylervick deleted the tylervick/fix-flaky-launch branch August 10, 2026 21:34
tylervick added a commit that referenced this pull request Aug 11, 2026
Generating a report from TestResults.xcresult on main produced 16 .mp4
references, 0 .png/.jpeg, and 0 rendered `img class="screenshot"` elements.
The screenshot templates, the `screenshot` CSS class and the `-z` downsizing
branch were executed by no test at all -- that path could have been entirely
broken and `swift test` would still have been green. #357 (screenshots missing
from reports, open since June 2024) has been uninvestigable for the same
reason.

Adds FirstSuite.testAttachScreenshot, which attaches XCUIScreen.main.screenshot()
with .keepAlways. That yields a real public.png payload, which Attachment.swift
maps to isImage -> cssClass "screenshot". It deliberately does NOT launch the
app: XCUIScreen captures the simulator screen without it, and #423 removed the
launches from these suites as pure flake risk.

Restores the image assertion in CliTests, commented out when Xcode 15 began
attaching videos by default. Widened to include `img.screenshot-tail`, which
the original selector missed, and given failure messages that say what is
wrong.

Proved non-vacuous rather than assumed: removing the screenshot test and
regenerating makes it fail on BOTH the plain and the -z variants
("No image attachments rendered"). Restoring it returns the suite to green.

Also:
- CoreTests expected 16 rows; there are now 17 (14 XCTest + 3 Swift Testing).
  Its comment justified not asserting the pass/fail split on the grounds that
  the suites launch the app in setUp -- false since #423. Rewritten to say what
  is actually true.
- Excludes XCTestHTMLReportSampleApp/.derivedData from SwiftLint. #425 moved
  DerivedData inside the repo; SwiftLint does not read .gitignore and reported
  errors from Xcode-generated sources. CI never saw it because the lint job
  does not generate fixtures, so it only bit locally.

swift test: 23 tests, 1 skipped, 0 failures.

Closes #393.

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
tylervick added a commit that referenced this pull request Aug 12, 2026
Fixture generation boots a simulator and runs the sample app's UI tests
on every CI run; even after #425 it dominates the test job's ~10-minute
wall time (#412). The bundles are deterministic for a given toolchain
and sample app (#423/#430), and every assertion in the suite compares
within one run, so identical bytes are safe to serve across runs.

Cache the three .xcresult bundles keyed on the exact Xcode build, the
newest installed iOS simulator runtime, prepareTestResults.sh itself,
and the sample-app sources. No restore-keys: an inexact match would
serve fixtures from a different toolchain, which the drift detector
(#392) and the legacy-vs-modern differential test cannot tolerate. A
verification step asserts all three bundles exist whether restored or
generated, so a cache hit that restores nothing fails instead of
passing as a suspiciously fast green run.

Local flow is untouched: prepareTestResults.sh is unchanged and swift
test still reads the same paths.

Fixes #412

Co-authored-by: Claude Fable 5 <[email protected]>
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.

Sample UI tests flake under CI load, making fixtures nondeterministic

1 participant