EchoUITests/VoiceOptionsUITests.swift
Rama main · réplica de lectura del repositorio original.
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")
}
}