pi-remote-ios/UITests/BackgroundLifecycleUITests....

175 lines
8.0 KiB
Swift

// BackgroundLifecycleUITests.swift
// T-2.10 Background lifecycle UI tests.
//
// Launch args used:
// --uitest existing arg: skips live WS, shows placeholder
// --uitest-with-stub-connection NEW (T-2.10): impl agent must add handling
// in MainTerminalView.initialBootstrap() so that
// a stub/in-process connection is established and
// the background lifecycle hooks (suspend/resume)
// fire and update the status bar. Without this arg
// the app uses plain --uitest mode (no connection,
// no reconnect status text) and the Reconnecting
// assertions below FAIL which is the intended
// failing state for TDD step 1.
//
// Status bar text expected values (T-2.10 impl):
// During reconnect window: "Reconnecting" (new)
// After reconnect settled: " <sessionId>" (existing connected text)
//
// Currently MainTerminalView shows "Connecting" for .connecting state (not
// "Reconnecting") and --uitest-with-stub-connection is not handled at all.
// All assertions about "Reconnecting" will therefore FAIL on the TDD branch.
import XCTest
@MainActor
final class BackgroundLifecycleUITests: XCTestCase {
override func setUpWithError() throws {
continueAfterFailure = false
}
// =========================================================================
// MARK: 1. Crash-safety smoke test (should PASS regression guard)
// =========================================================================
/// The app must survive a background foreground transition without
/// crashing, regardless of connection state.
func test_backgroundForeground_appDoesNotCrash() throws {
let app = XCUIApplication()
// Use --uitest so no real WS is needed; lifecycle hooks still fire.
app.launchArguments = ["--uitest", "--reset-state"]
app.launch()
XCTAssertTrue(app.wait(for: .runningForeground, timeout: 10),
"App must reach runningForeground state")
// Background the app
XCUIDevice.shared.press(.home)
// Brief wait in background
Thread.sleep(forTimeInterval: 0.5)
// Foreground the app
app.activate()
// Verify the app returned to foreground (no crash / hang)
XCTAssertTrue(app.wait(for: .runningForeground, timeout: 5),
"App must return to runningForeground after activate() — crash guard")
}
// =========================================================================
// MARK: 2. Status bar shows "Reconnecting" after foreground transition
//
// FAILS until impl adds:
// (a) --uitest-with-stub-connection launch arg handling in
// MainTerminalView.initialBootstrap() that creates a stub connection
// so lifecycle hooks fire and status text updates.
// (b) "Reconnecting" (not "Connecting") text in the status bar when
// SessionConnection re-enters .connecting after a suspend/resume.
// (c) The background lifecycle wiring in MainTerminalView (subscribe to
// AppState.lifecycleTransitions, call suspend/resume on the active
// connection).
// =========================================================================
/// After returning to foreground, the status bar must briefly show
/// "Reconnecting" (not just "Connecting") to indicate this is a
/// re-connection of an existing session, not a first-time attach.
///
/// FAILS: --uitest-with-stub-connection is not handled app shows
/// " uitest" (plain --uitest behaviour) "Reconnecting" not found.
func test_statusBar_showsReconnectingAfterForeground() throws {
let app = XCUIApplication()
// --uitest-with-stub-connection: impl agent must add this mode.
// Until then, the app falls back to --uitest behavior (" uitest").
app.launchArguments = ["--uitest", "--uitest-with-stub-connection", "--reset-state"]
app.launch()
XCTAssertTrue(app.wait(for: .runningForeground, timeout: 10),
"App must launch successfully")
// Background the app
XCUIDevice.shared.press(.home)
Thread.sleep(forTimeInterval: 0.5)
// Foreground the app
app.activate()
// After foregrounding, status bar must show "Reconnecting" briefly.
// Query by text content the status bar text element has no accessibility
// identifier yet. Impl agent should add accessibilityIdentifier
// "statusbar.connectionStatus" to the connectionStatus Text in StatusBar.swift.
let reconnectingText = app.staticTexts.matching(
NSPredicate(format: "label CONTAINS 'Reconnecting'")
).firstMatch
XCTAssertTrue(
reconnectingText.waitForExistence(timeout: 3.0),
"T-2.10: Status bar must show 'Reconnecting…' (or similar) text " +
"during the reconnect window after app foreground. " +
"MISSING IMPL: (1) add --uitest-with-stub-connection mode, " +
"(2) update MainTerminalView status wiring for .connecting during resume, " +
"(3) show 'Reconnecting…' not 'Connecting…' when lastSeq != nil."
)
}
/// After ~2 seconds the status must revert from "Reconnecting" to the
/// normal connected indicator (" <sessionId>" or " stub").
///
/// FAILS: prerequisite test_statusBar_showsReconnectingAfterForeground
/// also fails; continueAfterFailure=false so this test is skipped.
/// When the impl is complete, this guard prevents the reconnect
/// state from being stuck forever.
func test_statusBar_recoversFromReconnectingWithin2s() throws {
let app = XCUIApplication()
app.launchArguments = ["--uitest", "--uitest-with-stub-connection", "--reset-state"]
app.launch()
XCTAssertTrue(app.wait(for: .runningForeground, timeout: 10),
"App must launch successfully")
XCUIDevice.shared.press(.home)
Thread.sleep(forTimeInterval: 0.5)
app.activate()
// First the reconnecting state must appear
let reconnectingText = app.staticTexts.matching(
NSPredicate(format: "label CONTAINS 'Reconnecting'")
).firstMatch
XCTAssertTrue(reconnectingText.waitForExistence(timeout: 3.0),
"T-2.10: Must see 'Reconnecting…' state first (prerequisite)")
// Then it must disappear (connected text takes over) within 2 s
let normalStatus = app.staticTexts.matching(
NSPredicate(format: "label BEGINSWITH '●'")
).firstMatch
XCTAssertTrue(normalStatus.waitForExistence(timeout: 2.0),
"T-2.10: Status bar must revert to '● <sessionId>' within 2 s " +
"after the reconnect completes (P-3 acceptance)")
}
// =========================================================================
// MARK: 3. Multiple background/foreground cycles are stable
//
// FAILS: same missing impl as above; smoke guards for future regression.
// =========================================================================
/// Two consecutive backgroundforeground cycles must not crash.
func test_repeatedBackgroundForeground_doesNotCrash() throws {
let app = XCUIApplication()
app.launchArguments = ["--uitest", "--reset-state"]
app.launch()
XCTAssertTrue(app.wait(for: .runningForeground, timeout: 10))
for i in 0..<2 {
XCUIDevice.shared.press(.home)
Thread.sleep(forTimeInterval: 0.3)
app.activate()
XCTAssertTrue(app.wait(for: .runningForeground, timeout: 5),
"App must survive bg/fg cycle \(i + 1)")
}
}
}