Ctrl K
ComunidadGuías…

EchoUITests/VoiceOptionsUITests.swift

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

Archivo original ↗Preguntar a KiBot
import XCTest

/// Settings → Voice → Stop phrases, and voice mode asked for on a named Hermes profile, the way
/// Siri's "Ask Work in Redde" asks (`-echo.askProfile` stands in for Siri). Neither needs a server.
@MainActor
final class VoiceOptionsUITests: XCTestCase {
    private func shot(_ name: String) {
        let attachment = XCTAttachment(screenshot: XCUIScreen.main.screenshot())
        attachment.name = name
        attachment.lifetime = .keepAlways
        add(attachment)
    }

    private func reach(_ element: XCUIElement, in app: XCUIApplication, _ what: String) {
        for _ in 0 ..< 14 where !(element.exists && element.isHittable) { app.swipeUp() }
        XCTAssertTrue(element.exists && element.isHittable, "\(what) isn't on the screen")
    }

    func testAStopPhraseOfYourOwnIsAddedAndRemoved() {
        continueAfterFailure = false
        let app = XCUIApplication()
        app.launchArguments = XCUIApplication.straightToChat + ["-echo.screen", "settings"]
        app.launch()
        XCTAssertTrue(app.navigationBars["Settings"].waitForExistence(timeout: 20), "Settings didn't open")
        app.openSettingsPage("Voice")
        let row = app.buttons.matching(NSPredicate(format: "label BEGINSWITH 'Stop phrases'")).firstMatch
        reach(row, in: app, "The Stop phrases row")
        row.tap()
        XCTAssertTrue(app.navigationBars["Stop phrases"].waitForExistence(timeout: 10), "The Stop phrases screen didn't open")
        XCTAssertTrue(app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'stop listening'")).firstMatch.exists,
                      "The built-in phrases aren't shown")

        let field = app.textFields["Add a phrase"]
        XCTAssertTrue(field.waitForExistence(timeout: 5))
        let add = app.buttons["Add phrase"]
        XCTAssertFalse(add.isEnabled, "Nothing typed, nothing to add")
        field.tap()
        field.typeText("Stop")
        XCTAssertFalse(add.isEnabled, "A built-in phrase can't be added again")
        field.typeText(" it right there")
        XCTAssertTrue(add.isEnabled)
        add.tap()
        let phrase = app.staticTexts["Stop it right there"]
        XCTAssertTrue(phrase.waitForExistence(timeout: 5), "The phrase isn't in the list")
        XCTAssertEqual(field.value as? String, "Add a phrase", "The field wasn't cleared for the next one")
        shot("stop phrases")

        // The Voice section counts it.
        // By name: on an iPhone Duo closed, the bar's buttons go down the side of the screen, out of
        // the navigation bar, and its first button there is Edit.
        app.buttons["BackButton"].firstMatch.tap()
        XCTAssertTrue(app.buttons.matching(NSPredicate(format: "label BEGINSWITH 'Stop phrases' AND (label CONTAINS '+1' OR value CONTAINS '+1')")).firstMatch
            .waitForExistence(timeout: 5), "The row doesn't say a phrase was added")

        // And off again, so the next run starts from none.
        row.tap()
        XCTAssertTrue(phrase.waitForExistence(timeout: 5))
        phrase.swipeLeft()
        app.buttons["Delete"].firstMatch.tap()
        XCTAssertTrue(phrase.waitForNonExistence(timeout: 5), "The phrase wasn't removed")
    }

    /// Settings → Voice → Thinking sound: off until chosen, and choosing plays it, like a
    /// ringtone. (What it sounds like is for ears; that the app is still there after the sample
    /// has played and let go of the audio is what this sees.)
    func testAThinkingSoundIsChosen() {
        continueAfterFailure = false
        let app = XCUIApplication()
        app.launchArguments = XCUIApplication.straightToChat + ["-echo.screen", "settings"]
        app.launch()
        XCTAssertTrue(app.navigationBars["Settings"].waitForExistence(timeout: 20), "Settings didn't open")
        app.openSettingsPage("Voice")
        let row = app.buttons.matching(NSPredicate(format: "label BEGINSWITH 'Thinking sound'")).firstMatch
        reach(row, in: app, "The Thinking sound row")
        func shows(_ choice: String) -> Bool {
            app.buttons.matching(NSPredicate(format: "label BEGINSWITH 'Thinking sound' AND (label CONTAINS %@ OR value CONTAINS %@)", choice, choice)).firstMatch
                .waitForExistence(timeout: 5)
        }
        row.tap()
        let car = app.buttons["In the car"]
        XCTAssertTrue(car.waitForExistence(timeout: 5), "The choices didn't open")
        XCTAssertTrue(app.buttons["Off"].exists && app.buttons["Always"].exists)
        car.tap()
        XCTAssertTrue(shows("In the car"), "The choice didn't take")
        shot("thinking sound")
        XCTAssertFalse(app.wait(for: .notRunning, timeout: 6), "Redde went away while the sample played")

        // Off again, for whatever runs next.
        row.tap()
        app.buttons["Off"].tap()
        XCTAssertTrue(shows("Off"))
    }

    /// The list an iPhone 11 was sent to pick another language from, and which never came up
    /// there: its newer transcriber knows no language. The simulator's doesn't either, so the
    /// languages here are the older transcriber's, as they are on that phone.
    func testTheListeningLanguagesLoad() {
        continueAfterFailure = false
        let app = XCUIApplication()
        app.launchArguments = XCUIApplication.straightToChat + ["-echo.screen", "settings"]
        app.launch()
        XCTAssertTrue(app.navigationBars["Settings"].waitForExistence(timeout: 20), "Settings didn't open")
        app.openSettingsPage("Voice")
        let row = app.buttons.matching(NSPredicate(format: "label BEGINSWITH 'Listening language'")).firstMatch
        reach(row, in: app, "The Listening language row")
        row.tap()
        XCTAssertTrue(app.navigationBars["Listening language"].waitForExistence(timeout: 10), "The Listening language screen didn't open")
        XCTAssertTrue(app.staticTexts["Languages"].waitForExistence(timeout: 15), "No languages to pick from")
        shot("listening languages")
        // A long list, and a row is only there once it has been scrolled to.
        reach(app.buttons.matching(NSPredicate(format: "label BEGINSWITH 'English (United States)'")).firstMatch,
              in: app, "English (United States)")
    }

    /// Settings → Voice → Nod or shake to answer belongs to answering requests by voice, and is
    /// offered only with it. The simulator can't read AirPods' movement; `-echo.headGestures`
    /// says it can, as far as offering the switch goes.
    func testTheNodSwitchComesWithAnsweringByVoice() {
        continueAfterFailure = false
        let app = XCUIApplication()
        app.launchArguments = XCUIApplication.straightToChat + ["-echo.screen", "settings", "-echo.headGestures"]
        app.launch()
        XCTAssertTrue(app.navigationBars["Settings"].waitForExistence(timeout: 20), "Settings didn't open")
        app.openSettingsPage("Voice")
        let aloud = app.switches["Answer requests by voice"]
        reach(aloud, in: app, "The Answer requests by voice switch")
        func flip(_ row: XCUIElement) {
            let knob = row.switches.firstMatch
            (knob.exists ? knob : row).tap()
        }
        let nod = app.switches["Nod or shake to answer"]
        if aloud.value as? String == "1" { flip(aloud) }   // (left on by a run that stopped halfway)
        XCTAssertTrue(nod.waitForNonExistence(timeout: 5), "The nod switch is offered without answering by voice")

        flip(aloud)
        XCTAssertTrue(nod.waitForExistence(timeout: 5), "Answering by voice is on and the nod switch isn't offered")
        XCTAssertEqual(nod.value as? String, "0", "Nothing reads the AirPods until it is asked for")
        shot("nod switch")

        // Off again, for whatever runs next.
        flip(aloud)
        XCTAssertTrue(nod.waitForNonExistence(timeout: 5))
    }

    func testAskingForAProfileOpensVoiceModeOnIt() {
        continueAfterFailure = false
        let app = XCUIApplication()
        // The profile in use at the start: the main one.
        app.launchArguments = XCUIApplication.straightToChat + ["-echo.askProfile", "default"]
        app.launch()
        // On the voice screen whatever it is doing (its close button is "Stop" while it listens).
        let end = app.buttons["Switch to typing"]
        XCTAssertTrue(end.waitForExistence(timeout: 60), "Voice mode didn't open")
        app.terminate()

        // "Ask field-notes in Redde".
        app.launchArguments = XCUIApplication.straightToChat + ["-echo.askProfile", "field-notes"]
        app.launch()
        XCTAssertTrue(end.waitForExistence(timeout: 30), "Voice mode didn't open for the profile")
        shot("voice on a profile")
        app.terminate()

        // The app is on that profile now: the picker shows it chosen.
        app.launchArguments = XCUIApplication.straightToChat + ["-echo.screen", "profiles"]
        app.launch()
        XCTAssertTrue(app.navigationBars["Profile"].waitForExistence(timeout: 20), "The profile picker didn't open")
        let named = app.buttons["field-notes"]
        XCTAssertTrue(named.waitForExistence(timeout: 5), "The profile asked for by name isn't listed")
        XCTAssertTrue(named.isSelected, "The app didn't move to the profile that was asked for")
        XCTAssertFalse(app.buttons.matching(NSPredicate(format: "label BEGINSWITH 'Default'")).firstMatch.isSelected)
        shot("profile picker")
        app.terminate()

        // Back to the main profile, and the name forgotten, for whatever runs next.
        app.launchArguments = XCUIApplication.straightToChat + ["-echo.askProfile", "default"]
        app.launch()
        XCTAssertTrue(end.waitForExistence(timeout: 30))
        app.terminate()
        app.launchArguments = XCUIApplication.straightToChat + ["-echo.screen", "profiles"]
        app.launch()
        XCTAssertTrue(app.navigationBars["Profile"].waitForExistence(timeout: 20))
        XCTAssertTrue(app.buttons.matching(NSPredicate(format: "label BEGINSWITH 'Default'")).firstMatch.isSelected, "The app isn't back on the main profile")
        XCTAssertTrue(named.waitForExistence(timeout: 5))
        named.swipeLeft()
        app.buttons["Delete"].firstMatch.tap()
        XCTAssertTrue(named.waitForNonExistence(timeout: 5), "The name wasn't forgotten")
    }
}