Ctrl K
ComunidadGuías…

EchoUITests/SignInUITests.swift

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import XCTest

/// Signing in to a Dashboard through a browser, with the real thing at every step: the app's
/// button, the system's sign-in sheet on the Dashboard's own login page, and the way back to the
/// app over the phone's loopback. Then Settings says who is signed in, and Sign out forgets it.
///
/// Needs the lab's Dashboard, which has a login (`scripts/hermes-lab/lab.sh up <tag> approval`:
/// lab / labpass-labpass on 127.0.0.1:19119, which a simulator shares with the Mac). Skips
/// without it.
@MainActor
final class SignInUITests: XCTestCase {
    private let dashboard = "http://127.0.0.1:19119"
    private let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")

    /// For this launch only, the app's Dashboard is the lab's, with no username and no password.
    /// `signedOut` also forgets a sign-in an earlier run left behind.
    private func launch(screen: String, signedOut: Bool = false) -> XCUIApplication {
        let app = XCUIApplication()
        app.launchArguments = XCUIApplication.straightToChat
            + ["-echo.screen", screen, "-transport", "hermesServe", "-serveURL", dashboard, "-serveUsername", ""]
        app.launchEnvironment["ECHO_TEST_SERVE_PASSWORD"] = ""   // empty deletes
        if signedOut { app.launchEnvironment["ECHO_TEST_SERVE_SIGNIN"] = "" }
        app.launch()
        return app
    }

    private func labIsUp() -> Bool {
        var request = URLRequest(url: URL(string: dashboard + "/api/status")!)
        request.timeoutInterval = 3
        let done = DispatchSemaphore(value: 0)
        nonisolated(unsafe) var gated = false
        URLSession.shared.dataTask(with: request) { data, _, _ in
            gated = data.map { String(decoding: $0, as: UTF8.self).contains("native_pkce") } ?? false
            done.signal()
        }.resume()
        _ = done.wait(timeout: .now() + 5)
        return gated
    }

    private func keep(_ screenshot: XCUIScreenshot, as name: String) {
        let attachment = XCTAttachment(screenshot: screenshot)
        attachment.name = name
        attachment.lifetime = .keepAlways
        add(attachment)
    }

    func testSigningInThroughTheSheetThenOut() throws {
        continueAfterFailure = false
        try XCTSkipUnless(labIsUp(), "No lab Dashboard with a login on 127.0.0.1:19119 (scripts/hermes-lab/lab.sh up <tag> approval)")

        // Setup, on the Dashboard connection: the address is in, and there is nothing to type a login into.
        var app = launch(screen: "setup", signedOut: true)
        XCTAssertTrue(app.navigationBars["Set up Redde"].waitForExistence(timeout: 20), "Setup didn't open")
        // The Dashboard's fields are below the welcome.
        let signIn = app.buttons["Sign in with a browser"]
        for _ in 0 ..< 6 where !(signIn.exists && signIn.isHittable) { app.swipeUp() }
        XCTAssertTrue(signIn.exists && signIn.isHittable, "Setup doesn't offer the browser sign-in")
        signIn.tap()

        // iOS asks before an app opens a sign-in sheet.
        let consent = springboard.alerts.buttons["Continue"]
        if consent.waitForExistence(timeout: 10) { consent.tap() }

        // The Dashboard's own login page, in the sheet. The app sees none of this.
        let sheet = XCUIApplication(bundleIdentifier: "com.apple.SafariViewService")
        let page = app.webViews.firstMatch.waitForExistence(timeout: 20) ? app.webViews.firstMatch : sheet.webViews.firstMatch
        XCTAssertTrue(page.waitForExistence(timeout: 20), "The sign-in sheet didn't open the Dashboard's page")
        let username = page.textFields.firstMatch
        XCTAssertTrue(username.waitForExistence(timeout: 20), "The Dashboard's login page has no username field")
        keep(XCUIScreen.main.screenshot(), as: "sign-in sheet")
        username.tap()
        username.typeText("lab")
        let password = page.secureTextFields.firstMatch
        password.tap()
        password.typeText("labpass-labpass")
        // (The page draws its button in capitals.)
        page.buttons.matching(NSPredicate(format: "label ==[c] %@", "Sign in")).firstMatch.tap()

        // Back in the app, by way of its loopback listener: signed in, and the connection test passes.
        XCTAssertTrue(app.text(containing: "Signed in through a browser").waitForExistence(timeout: 30),
                      "The sheet didn't bring the sign-in back to the app")
        XCTAssertTrue(app.text(containing: "Login accepted").waitForExistence(timeout: 20), "The Dashboard didn't take the token")
        keep(XCUIScreen.main.screenshot(), as: "signed in, in setup")
        app.terminate()

        // Settings says who, and Sign out forgets it.
        app = launch(screen: "settings")
        XCTAssertTrue(app.navigationBars["Settings"].waitForExistence(timeout: 20), "Settings didn't open")
        let details = app.buttons["Connection details"]
        for _ in 0 ..< 8 where !(details.exists && details.isHittable) { app.swipeUp() }
        details.tap()
        XCTAssertTrue(app.staticTexts["Signed in"].waitForExistence(timeout: 15), "Settings doesn't show the sign-in")
        // The name is asked of the Dashboard and arrives a moment later. However the row is read
        // out (two texts, or one with a value), "lab" is in it.
        let named = NSPredicate(format: "label ENDSWITH %@ OR value ENDSWITH %@", "lab", "lab")
        let who = app.descendants(matching: .any).matching(named).firstMatch
        if !who.waitForExistence(timeout: 15) {
            let seen = app.staticTexts.allElementsBoundByIndex.map { "\($0.label)|\(String(describing: $0.value))" }
            XCTFail("Settings doesn't say who is signed in. On screen: \(seen)")
        }
        keep(XCUIScreen.main.screenshot(), as: "signed in, in Settings")
        app.buttons["Sign out"].tap()
        XCTAssertTrue(app.buttons["Sign in with a browser"].waitForExistence(timeout: 10), "Signing out didn't bring the button back")
        XCTAssertFalse(app.staticTexts["Signed in"].exists)
    }
}