Stop asserting simulator reliability in testResultStatusCount by tylervick · Pull Request #397 · XCTestHTMLReport/XCTestHTMLReport · GitHub
Skip to content

Stop asserting simulator reliability in testResultStatusCount - #397

Merged
tylervick merged 1 commit into
mainfrom
fix-flaky-status-count
Aug 7, 2026
Merged

tylervick merged 1 commit into
mainfrom
fix-flaky-status-count

Conversation

@tylervick

Copy link
Copy Markdown
Member

The Codecov run on main is failing. Test on the same commit passes.

53adfaf  Test    -> success
53adfaf  Codecov -> failure

Both run ./prepareTestResults.sh && swift test. Same code, same commit, different outcome — so this is nondeterminism, not a code defect.

What happens

CoreTests.swift:65: XCTAssertEqual failed: ("Optional(6)") is not equal to ("Optional(7)")
CoreTests.swift:67: XCTAssertEqual failed: ("Optional(6)") is not equal to ("Optional(5)")

Passed went 7→6 and Failed went 5→6; the total stayed 13. One test moved buckets. The fixture-generation log shows FirstSuite.testOne as both passed and failed:

1 Test Case '-[SampleAppUITests.FirstSuite testOne]' passed
1 Test Case '-[SampleAppUITests.FirstSuite testOne]' failed

Its body is XCTAssert(true) plus an attachment — it cannot fail on its own assertion. The failure comes from setUp, which calls XCUIApplication().launch() with continueAfterFailure = false. Under CI load the simulator is sometimes slow to launch the app, and the test fails before its body runs.

Why it started now

This is a consequence of #379. Fixtures used to be fixed artifacts downloaded from a private bucket — byte-identical every run, so exact per-bucket assertions were safe. They are now regenerated on every run, which is a better trade overall (they can't go stale, and fork PRs can run them), but it means UI-test flakiness feeds straight into assertions that were written for stable input.

The fix

Assert only what the sample sources actually determine:

Assertion Why it's stable
all == 13 one row per test method, fixed by source
skipped == 1 SampleAppUnitTests.testSkipped is an unconditional XCTSkipIf
mixed == 0 TestResults excludes RetryTests
passed + failed == all - skipped every test lands in exactly one bucket
failed >= 5 five sample tests fail deliberately

The exact pass/fail split is dropped, because it measures whether the simulator launched an app in time — not anything this project does.

This still catches real regressions. Losing a test breaks all == 13. Silently dropping failures breaks failed >= 5. Miscounting breaks the sum invariant. Under the flaked run (passed=6, failed=6) the new assertions correctly pass.

testMixedStatus is left alone: it reads RetryResults, and RetryTests has no setUp and never launches the app, so it isn't exposed to this.

Follow-up

The underlying flakiness is worth its own issue — the sample UI tests launch the app purely so the run produces screen recordings (the only .mp4 fixture coverage), and none of them actually interact with the UI. There may be a way to keep the recordings without the launch being a failure point.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

The Codecov run on main failed while the Test run on the same commit
passed: 53adfaf produced Passed(6)/Failed(6) instead of Passed(7)/Failed(5).
FirstSuite.testOne appears as both passed and failed across the fixture
generation logs. Its body is XCTAssert(true) plus an attachment, so it
cannot fail on its own assertion — the failure comes from setUp, which
calls XCUIApplication().launch() with continueAfterFailure = false. A slow
simulator turns a would-be pass into a failure.

This became reachable when fixtures started being regenerated on every
run instead of downloaded as fixed artifacts. Exact per-bucket counts
were safe against byte-identical fixtures; they are not against freshly
generated ones.

Assert what the sample sources actually determine — total, skipped, mixed,
the bucket-sum invariant, and a floor on deliberate failures — and stop
asserting the pass/fail split, which measures the simulator rather than
this project. A lost test still breaks the "all == 13" assertion, dropped
failures still break "failed >= 5", and miscounting still breaks the sum.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@tylervick
tylervick force-pushed the fix-flaky-status-count branch from 9d9f916 to 895611c Compare August 7, 2026 20:24
@tylervick
tylervick merged commit 69b32fe into main Aug 7, 2026
6 checks passed
@tylervick
tylervick deleted the fix-flaky-status-count branch August 7, 2026 20:38
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