Enable parallel UI test execution via per-session data isolation

Each test class now gets a unique session ID (UUID) passed to the app
via UI_TEST_SESSION_ID environment variable. The app uses this to:

- Route GroupUserDefaults to a session-specific UserDefaults suite,
  preventing tests from clobbering each other's AppStorage state
- Create an in-memory SwiftData container instead of the shared
  on-disk App Group store, eliminating SQLite contention

Refactored 8 test classes that bypassed BaseUITestCase.setUp() with
custom launch args — they now use overridable `localeArguments` and
`extraLaunchArguments` properties, keeping session ID injection
centralized. Added `relaunchApp(resetState:bypassSubscription:)` to
BaseUITestCase for tests that need mid-test relaunch with different
subscription state.

Includes a ParallelUITests.xctestplan with class-level parallelism
enabled and random execution ordering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-03-24 15:04:55 -05:00
parent 8231750cff
commit 2ef1c1ec51
13 changed files with 128 additions and 145 deletions

View File

@@ -1,19 +1,18 @@
import XCTest
class HierarchyDumpTest: XCTestCase {
class HierarchyDumpTest: BaseUITestCase {
override var seedFixture: String? { nil }
func testDumpAccessibilityTree() {
let app = XCUIApplication()
app.launchArguments = ["--ui-testing", "--reset-state", "--disable-animations", "--bypass-subscription", "--skip-onboarding"]
app.launch()
sleep(3)
print("\n=== ELEMENT QUERIES ===")
print("otherElements[mood_header]: \(app.otherElements[\"mood_header\"].exists)")
print("descendants[mood_header]: \(app.descendants(matching: .any)[\"mood_header\"].firstMatch.exists)")
print("groups[mood_header]: \(app.groups[\"mood_header\"].exists)")
print("scrollViews[mood_header]: \(app.scrollViews[\"mood_header\"].exists)")
print("staticTexts[mood_header]: \(app.staticTexts[\"mood_header\"].exists)")
print("buttons[mood_button_great]: \(app.buttons[\"mood_button_great\"].exists)")
print("otherElements[mood_header]: \(app.otherElements["mood_header"].exists)")
print("descendants[mood_header]: \(app.descendants(matching: .any)["mood_header"].firstMatch.exists)")
print("groups[mood_header]: \(app.groups["mood_header"].exists)")
print("scrollViews[mood_header]: \(app.scrollViews["mood_header"].exists)")
print("staticTexts[mood_header]: \(app.staticTexts["mood_header"].exists)")
print("buttons[mood_button_great]: \(app.buttons["mood_button_great"].exists)")
print("tabBars count: \(app.tabBars.count)")
if app.tabBars.count > 0 {
let tb = app.tabBars.firstMatch
@@ -21,15 +20,15 @@ class HierarchyDumpTest: XCTestCase {
print(" tab button: \(b.identifier) label=\(b.label)")
}
}
print("otherElements[settings_header]: \(app.otherElements[\"settings_header\"].exists)")
print("otherElements[settings_header]: \(app.otherElements["settings_header"].exists)")
print("\n=== HIERARCHY (first 200 lines) ===")
let desc = app.debugDescription
let lines = desc.components(separatedBy: "\n")
for (i, line) in lines.prefix(200).enumerated() {
print("\(i): \(line)")
}
XCTAssertTrue(true) // always pass
}
}