cmux/cmuxUITests/BrowserOmnibarSuggestionsUITests.swift
2026-02-18 21:19:56 -08:00

630 lines
28 KiB
Swift

import XCTest
import Foundation
final class BrowserOmnibarSuggestionsUITests: XCTestCase {
private var dataPath = ""
override func setUp() {
super.setUp()
continueAfterFailure = false
dataPath = "/tmp/cmux-ui-test-omnibar-suggestions-\(UUID().uuidString).json"
try? FileManager.default.removeItem(atPath: dataPath)
// Terminate any lingering app from a prior test so its debounced
// history-save doesn't overwrite the seeded browser_history.json.
let cleanup = XCUIApplication()
cleanup.terminate()
RunLoop.current.run(until: Date().addingTimeInterval(0.5))
}
func testOmnibarSuggestionsAlignToPillAndCmdNP() {
seedBrowserHistoryForTest()
let app = XCUIApplication()
app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_SETUP"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_PATH"] = dataPath
// Keep suggestions deterministic for the keyboard-nav assertions.
app.launchEnvironment["CMUX_UI_TEST_DISABLE_REMOTE_SUGGESTIONS"] = "1"
app.launch()
app.activate()
// Focus omnibar.
app.typeKey("l", modifierFlags: [.command])
let pill = app.descendants(matching: .any).matching(identifier: "BrowserOmnibarPill").firstMatch
XCTAssertTrue(pill.waitForExistence(timeout: 6.0))
let omnibar = app.textFields["BrowserOmnibarTextField"].firstMatch
XCTAssertTrue(omnibar.waitForExistence(timeout: 6.0))
// Type a query that matches the seeded URL.
omnibar.typeText("exam")
// SwiftUI's accessibility typing for ScrollView can vary; match by identifier regardless of element type.
let suggestionsElement = app.descendants(matching: .any).matching(identifier: "BrowserOmnibarSuggestions").firstMatch
XCTAssertTrue(suggestionsElement.waitForExistence(timeout: 6.0))
let row0 = app.descendants(matching: .any).matching(identifier: "BrowserOmnibarSuggestions.Row.0").firstMatch
XCTAssertTrue(row0.waitForExistence(timeout: 6.0))
// Frame checks (screen coordinates).
let pillFrame = pill.frame
let suggestionsFrame = suggestionsElement.frame
attachElementDebug(name: "omnibar-pill", element: pill)
attachElementDebug(name: "omnibar-suggestions", element: suggestionsElement)
XCTAssertGreaterThan(pillFrame.width, 50)
XCTAssertGreaterThan(suggestionsFrame.width, 50)
let xTolerance: CGFloat = 3.0
let wTolerance: CGFloat = 3.0
XCTAssertLessThanOrEqual(abs(pillFrame.minX - suggestionsFrame.minX), xTolerance,
"Expected suggestions minX to match omnibar minX.\nPill: \(pillFrame)\nSug: \(suggestionsFrame)")
XCTAssertLessThanOrEqual(abs(pillFrame.width - suggestionsFrame.width), wTolerance,
"Expected suggestions width to match omnibar width.\nPill: \(pillFrame)\nSug: \(suggestionsFrame)")
XCTAssertGreaterThanOrEqual(
suggestionsFrame.minY,
pillFrame.maxY - 1.0,
"Expected suggestions popup to render below (not behind) the omnibar.\nPill: \(pillFrame)\nSug: \(suggestionsFrame)"
)
// Row 0 should be the autocompletable example.com history entry.
// Verify Cmd+N moves to row 1, Cmd+P returns to row 0, then Enter navigates.
let row1 = app.descendants(matching: .any).matching(identifier: "BrowserOmnibarSuggestions.Row.1").firstMatch
XCTAssertTrue(row1.waitForExistence(timeout: 6.0))
app.typeKey("n", modifierFlags: [.command])
XCTAssertTrue(waitForRowSelected(row1, timeout: 2.0), "Expected Cmd+N to select row 1. value=\(String(describing: row1.value))")
app.typeKey("p", modifierFlags: [.command])
XCTAssertTrue(waitForRowSelected(row0, timeout: 2.0), "Expected Cmd+P to return to row 0. value=\(String(describing: row0.value))")
app.typeKey(XCUIKeyboardKey.return.rawValue, modifierFlags: [])
// After committing the autocompletion candidate, the omnibar should contain the URL.
// Note: example.com may redirect to example.org in some environments.
let deadline = Date().addingTimeInterval(8.0)
while Date() < deadline {
let value = (omnibar.value as? String) ?? ""
if value.contains("example.com") || value.contains("example.org") {
return
}
RunLoop.current.run(until: Date().addingTimeInterval(0.05))
}
XCTFail("Expected omnibar to navigate to example.com after keyboard nav + Enter. value=\(String(describing: omnibar.value))")
}
func testOmnibarEscapeAndClickOutsideBehaveLikeChrome() {
seedBrowserHistoryForTest()
let app = XCUIApplication()
app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_SETUP"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_PATH"] = dataPath
// Keep suggestions deterministic.
app.launchEnvironment["CMUX_UI_TEST_DISABLE_REMOTE_SUGGESTIONS"] = "1"
app.launch()
app.activate()
let omnibar = app.textFields["BrowserOmnibarTextField"].firstMatch
XCTAssertTrue(omnibar.waitForExistence(timeout: 6.0))
// Focus omnibar and navigate to example.com via autocompletion (row 0).
app.typeKey("l", modifierFlags: [.command])
omnibar.typeText("exam")
let suggestionsElement = app.descendants(matching: .any).matching(identifier: "BrowserOmnibarSuggestions").firstMatch
XCTAssertTrue(suggestionsElement.waitForExistence(timeout: 6.0))
// Row 0 is the autocompletion candidate (example.com). Enter commits it.
app.typeKey(XCUIKeyboardKey.return.rawValue, modifierFlags: [])
// Note: example.com may redirect to example.org in some environments.
func containsExampleDomain(_ value: String) -> Bool {
value.contains("example.com") || value.contains("example.org")
}
let deadline = Date().addingTimeInterval(8.0)
while Date() < deadline {
let value = (omnibar.value as? String) ?? ""
if containsExampleDomain(value) {
break
}
RunLoop.current.run(until: Date().addingTimeInterval(0.05))
}
XCTAssertTrue(containsExampleDomain((omnibar.value as? String) ?? ""))
// Type a new query to open the popup, then Escape should revert to the current URL.
app.typeKey("l", modifierFlags: [.command])
omnibar.typeText("meaning")
XCTAssertTrue(suggestionsElement.waitForExistence(timeout: 6.0))
app.typeKey(XCUIKeyboardKey.escape.rawValue, modifierFlags: [])
let reverted = (omnibar.value as? String) ?? ""
XCTAssertTrue(containsExampleDomain(reverted), "Expected Escape to revert omnibar to current URL. value=\(reverted)")
XCTAssertFalse(suggestionsElement.waitForExistence(timeout: 0.5), "Expected Escape to close suggestions popup")
// Second Escape should blur to the web view: typing should not change the omnibar value.
app.typeKey(XCUIKeyboardKey.escape.rawValue, modifierFlags: [])
let beforeTyping = (omnibar.value as? String) ?? ""
app.typeText("zzz")
let afterTyping = (omnibar.value as? String) ?? ""
XCTAssertEqual(afterTyping, beforeTyping, "Expected typing after 2nd Escape to not modify omnibar (blurred)")
// Click outside should also discard edits and blur.
app.typeKey("l", modifierFlags: [.command])
omnibar.typeText("foo")
let window = app.windows.firstMatch
XCTAssertTrue(window.waitForExistence(timeout: 6.0))
window.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.8)).click()
// Give SwiftUI focus a moment to settle.
RunLoop.current.run(until: Date().addingTimeInterval(0.2))
let afterClick = (omnibar.value as? String) ?? ""
if !containsExampleDomain(afterClick) {
// VM UI automation can occasionally keep focus in the text field after a coordinate click.
// Fall back to Escape so we still validate post-click revert/blur behavior.
app.typeKey(XCUIKeyboardKey.escape.rawValue, modifierFlags: [])
}
let recoveredAfterClick = (omnibar.value as? String) ?? ""
XCTAssertTrue(containsExampleDomain(recoveredAfterClick), "Expected click-outside path to discard edits. value=\(recoveredAfterClick)")
app.typeKey(XCUIKeyboardKey.escape.rawValue, modifierFlags: [])
let beforeOutsideTyping = (omnibar.value as? String) ?? ""
app.typeText("bbb")
let afterOutsideTyping = (omnibar.value as? String) ?? ""
XCTAssertEqual(afterOutsideTyping, beforeOutsideTyping, "Expected typing after click-outside to not modify omnibar (blurred)")
}
func testOmnibarSuggestionsCmdNPWhenAddressBarFocused() {
seedBrowserHistoryForTest()
let app = XCUIApplication()
app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_SETUP"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_PATH"] = dataPath
app.launchEnvironment["CMUX_UI_TEST_DISABLE_REMOTE_SUGGESTIONS"] = "1"
app.launchEnvironment["CMUX_UI_TEST_REMOTE_SUGGESTIONS_JSON"] = #"["go tutorial","go json","go fmt"]"#
app.launch()
app.activate()
app.typeKey("l", modifierFlags: [.command])
let omnibar = app.textFields["BrowserOmnibarTextField"].firstMatch
XCTAssertTrue(omnibar.waitForExistence(timeout: 6.0))
omnibar.typeText("go")
let suggestionsElement = app.descendants(matching: .any).matching(identifier: "BrowserOmnibarSuggestions").firstMatch
XCTAssertTrue(suggestionsElement.waitForExistence(timeout: 6.0))
let row1 = app.descendants(matching: .any).matching(identifier: "BrowserOmnibarSuggestions.Row.1").firstMatch
let row2 = app.descendants(matching: .any).matching(identifier: "BrowserOmnibarSuggestions.Row.2").firstMatch
XCTAssertTrue(row1.waitForExistence(timeout: 6.0))
XCTAssertTrue(row2.waitForExistence(timeout: 6.0))
app.typeKey("n", modifierFlags: [.command])
XCTAssertTrue(waitForRowSelected(row1, timeout: 2.0), "Expected Cmd+N to select row 1")
app.typeKey("n", modifierFlags: [.command])
XCTAssertTrue(waitForRowSelected(row2, timeout: 2.0), "Expected repeated Cmd+N to keep moving selection")
app.typeKey("p", modifierFlags: [.command])
XCTAssertTrue(waitForRowSelected(row1, timeout: 2.0), "Expected Cmd+P to move selection up")
}
func testOmnibarShowsMultipleRowsWithoutClipping() {
seedBrowserHistoryForTest()
let app = XCUIApplication()
app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_SETUP"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_PATH"] = dataPath
app.launchEnvironment["CMUX_UI_TEST_DISABLE_REMOTE_SUGGESTIONS"] = "1"
app.launchEnvironment["CMUX_UI_TEST_REMOTE_SUGGESTIONS_JSON"] = #"["go tutorial","go json","go fmt"]"#
app.launch()
app.activate()
app.typeKey("l", modifierFlags: [.command])
let omnibar = app.textFields["BrowserOmnibarTextField"].firstMatch
XCTAssertTrue(omnibar.waitForExistence(timeout: 6.0))
omnibar.typeText("go")
let suggestionsElement = app.descendants(matching: .any).matching(identifier: "BrowserOmnibarSuggestions").firstMatch
XCTAssertTrue(suggestionsElement.waitForExistence(timeout: 6.0))
let row2 = app.descendants(matching: .any).matching(identifier: "BrowserOmnibarSuggestions.Row.2").firstMatch
XCTAssertTrue(row2.waitForExistence(timeout: 6.0), "Expected at least 3 suggestion rows for 'go'")
let popupFrame = suggestionsElement.frame
let row2Frame = row2.frame
XCTAssertGreaterThan(row2Frame.height, 1, "Expected third row to have a non-zero visible height")
XCTAssertLessThanOrEqual(row2Frame.maxY, popupFrame.maxY + 1, "Expected third row to stay inside popup bounds")
}
func testCmdLRefocusAfterNavigationKeepsOmnibarEditable() {
seedBrowserHistoryForTest()
let app = XCUIApplication()
app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_SETUP"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_PATH"] = dataPath
app.launchEnvironment["CMUX_UI_TEST_DISABLE_REMOTE_SUGGESTIONS"] = "1"
app.launch()
app.activate()
app.typeKey("l", modifierFlags: [.command])
let omnibar = app.textFields["BrowserOmnibarTextField"].firstMatch
XCTAssertTrue(omnibar.waitForExistence(timeout: 6.0))
// Start a real navigation, then re-focus the omnibar immediately.
omnibar.typeText("example.com")
app.typeKey(XCUIKeyboardKey.return.rawValue, modifierFlags: [])
app.typeKey("l", modifierFlags: [.command])
// Wait for navigation to finish so we can verify focus is held through page load.
let loaded = Date().addingTimeInterval(8.0)
var loadObserved = false
while Date() < loaded {
let value = (omnibar.value as? String) ?? ""
if value.lowercased().contains("example.com") {
loadObserved = true
break
}
RunLoop.current.run(until: Date().addingTimeInterval(0.15))
}
XCTAssertTrue(loadObserved, "Expected omnibar to reflect the navigated URL after load. value=\(omnibar.value)")
let valueAfterLoad = (omnibar.value as? String) ?? ""
omnibar.typeText("zx")
let typed = Date().addingTimeInterval(5.0)
var valueCaptured = false
while Date() < typed {
let value = (omnibar.value as? String) ?? ""
if value.contains("zx") && value != valueAfterLoad {
valueCaptured = true
break
}
RunLoop.current.run(until: Date().addingTimeInterval(0.05))
}
XCTAssertTrue(valueCaptured, "Expected omnirbar to keep keyboard focus after Cmd+L when navigation is in-flight. value=\(String(describing: omnibar.value))")
let suggestionsElement = app.descendants(matching: .any).matching(identifier: "BrowserOmnibarSuggestions").firstMatch
XCTAssertTrue(
suggestionsElement.waitForExistence(timeout: 3.0),
"Expected omnibar suggestions to appear while focused after Cmd+L during navigation"
)
// Avoid leaving test in partially edited state.
app.typeKey("a", modifierFlags: [.command])
app.typeKey(XCUIKeyboardKey.delete.rawValue, modifierFlags: [])
app.typeKey("x", modifierFlags: [])
}
func testCmdLImmediateTypingReplacesExistingURLBuffer() {
seedBrowserHistoryForTest()
let app = XCUIApplication()
app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_SETUP"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_PATH"] = dataPath
app.launchEnvironment["CMUX_UI_TEST_DISABLE_REMOTE_SUGGESTIONS"] = "1"
app.launch()
app.activate()
let omnibar = app.textFields["BrowserOmnibarTextField"].firstMatch
XCTAssertTrue(omnibar.waitForExistence(timeout: 6.0))
// Navigate to a non-empty URL first so Cmd+L must replace existing text.
app.typeKey("l", modifierFlags: [.command])
omnibar.typeText("example.com")
app.typeKey(XCUIKeyboardKey.return.rawValue, modifierFlags: [])
let loadedDeadline = Date().addingTimeInterval(8.0)
var loaded = false
while Date() < loadedDeadline {
let value = ((omnibar.value as? String) ?? "").lowercased()
if value.contains("example.com") || value.contains("example.org") {
loaded = true
break
}
RunLoop.current.run(until: Date().addingTimeInterval(0.1))
}
XCTAssertTrue(loaded, "Expected baseline navigation to load before Cmd+L fast-typing check.")
// Reproduce user flow: Cmd+L then immediate typing without waiting.
app.typeKey("l", modifierFlags: [.command])
app.typeText("lo")
let typedDeadline = Date().addingTimeInterval(4.0)
var observedValue = ""
var startsWithTypedPrefix = false
while Date() < typedDeadline {
observedValue = ((omnibar.value as? String) ?? "").lowercased()
if observedValue.hasPrefix("lo") {
startsWithTypedPrefix = true
break
}
RunLoop.current.run(until: Date().addingTimeInterval(0.05))
}
XCTAssertTrue(
startsWithTypedPrefix,
"Expected immediate typing after Cmd+L to preserve typed prefix 'lo'. value=\(observedValue)"
)
}
func testOmnibarAutocompleteCandidateIsCommittedOnEnter() {
seedBrowserHistoryForTest(
seedEntries: [
SeedEntry(url: "https://news.ycombinator.com/", title: "News Y Combinator", visitCount: 12, typedCount: 1),
SeedEntry(url: "https://gmail.com/", title: "Gmail", visitCount: 10, typedCount: 2),
]
)
let app = XCUIApplication()
app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_SETUP"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_PATH"] = dataPath
app.launchEnvironment["CMUX_UI_TEST_DISABLE_REMOTE_SUGGESTIONS"] = "1"
app.launch()
app.activate()
app.typeKey("l", modifierFlags: [.command])
let omnibar = app.textFields["BrowserOmnibarTextField"].firstMatch
XCTAssertTrue(omnibar.waitForExistence(timeout: 6.0))
omnibar.typeText("gm")
let suggestionsElement = app.descendants(matching: .any).matching(identifier: "BrowserOmnibarSuggestions").firstMatch
XCTAssertTrue(suggestionsElement.waitForExistence(timeout: 6.0))
let row0 = app.descendants(matching: .any).matching(identifier: "BrowserOmnibarSuggestions.Row.0").firstMatch
XCTAssertTrue(row0.waitForExistence(timeout: 4.0))
let row0Value = (row0.value as? String) ?? ""
XCTAssertTrue(
row0Value.localizedCaseInsensitiveContains("gmail"),
"Expected autocomplete candidate to be first row. row0Value=\(row0Value)"
)
app.typeKey(XCUIKeyboardKey.return.rawValue, modifierFlags: [])
let deadline = Date().addingTimeInterval(8.0)
var committedToGmail = false
while Date() < deadline {
let value = (omnibar.value as? String) ?? ""
if value.localizedCaseInsensitiveContains("gmail.com") {
committedToGmail = true
break
}
RunLoop.current.run(until: Date().addingTimeInterval(0.1))
}
XCTAssertTrue(committedToGmail, "Expected Enter to commit Gmail autocomplete target. value=\(String(describing: omnibar.value))")
}
func testOmnibarSingleRowPopupUsesMinimumHeight() {
let app = XCUIApplication()
app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_SETUP"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_PATH"] = dataPath
app.launchEnvironment["CMUX_UI_TEST_DISABLE_REMOTE_SUGGESTIONS"] = "1"
app.launch()
app.activate()
app.typeKey("l", modifierFlags: [.command])
let omnibar = app.textFields["BrowserOmnibarTextField"].firstMatch
XCTAssertTrue(omnibar.waitForExistence(timeout: 6.0))
let query = "zzzz-\(UUID().uuidString.prefix(8))"
omnibar.typeText(query)
let suggestionsElement = app.descendants(matching: .any).matching(identifier: "BrowserOmnibarSuggestions").firstMatch
XCTAssertTrue(suggestionsElement.waitForExistence(timeout: 6.0))
let row0 = app.descendants(matching: .any).matching(identifier: "BrowserOmnibarSuggestions.Row.0").firstMatch
let row1 = app.descendants(matching: .any).matching(identifier: "BrowserOmnibarSuggestions.Row.1").firstMatch
XCTAssertTrue(row0.waitForExistence(timeout: 6.0))
XCTAssertFalse(row1.waitForExistence(timeout: 0.5), "Expected one-row popup for a unique query")
let expectedMinHeight: CGFloat = 30
let tolerance: CGFloat = 2
let popupHeight = suggestionsElement.frame.height
XCTAssertLessThanOrEqual(
abs(popupHeight - expectedMinHeight),
tolerance,
"Expected one-row popup to use min height without extra bottom gap. frame=\(suggestionsElement.frame)"
)
let popupFrame = suggestionsElement.frame
let rowFrame = row0.frame
let topInset = rowFrame.minY - popupFrame.minY
let bottomInset = popupFrame.maxY - rowFrame.maxY
XCTAssertLessThanOrEqual(
abs(topInset - bottomInset),
1.5,
"Expected one-row popup to have balanced top/bottom insets. popup=\(popupFrame) row=\(rowFrame)"
)
}
func testInlineAutocompleteBackspaceDeletesTypedPrefixCharacter() {
seedBrowserHistoryForTest()
let app = XCUIApplication()
app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_SETUP"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_PATH"] = dataPath
app.launchEnvironment["CMUX_UI_TEST_DISABLE_REMOTE_SUGGESTIONS"] = "1"
app.launch()
app.activate()
app.typeKey("l", modifierFlags: [.command])
let omnibar = app.textFields["BrowserOmnibarTextField"].firstMatch
XCTAssertTrue(omnibar.waitForExistence(timeout: 6.0))
omnibar.typeText("exam")
let valueAfterTyping = (omnibar.value as? String) ?? ""
XCTAssertTrue(
valueAfterTyping.contains("example.com"),
"Expected inline completion to display a URL for typed prefix. value=\(valueAfterTyping)"
)
XCTAssertFalse(
valueAfterTyping.lowercased().hasPrefix("https://"),
"Expected inline completion display to avoid injecting an https:// prefix unless typed."
)
app.typeKey(XCUIKeyboardKey.delete.rawValue, modifierFlags: [])
app.typeKey(XCUIKeyboardKey.escape.rawValue, modifierFlags: [])
let valueAfterDeleteAndEscape = (omnibar.value as? String) ?? ""
XCTAssertEqual(
valueAfterDeleteAndEscape,
"exa",
"Expected Backspace with inline suffix selected to remove one typed prefix character."
)
}
func testCmdASelectAllDoesNotClearInlineCompletion() {
seedBrowserHistoryForTest()
let app = XCUIApplication()
app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_SETUP"] = "1"
app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_PATH"] = dataPath
app.launchEnvironment["CMUX_UI_TEST_DISABLE_REMOTE_SUGGESTIONS"] = "1"
app.launch()
app.activate()
app.typeKey("l", modifierFlags: [.command])
let omnibar = app.textFields["BrowserOmnibarTextField"].firstMatch
XCTAssertTrue(omnibar.waitForExistence(timeout: 6.0))
omnibar.typeText("go")
let inlineDeadline = Date().addingTimeInterval(3.0)
while Date() < inlineDeadline {
let value = (omnibar.value as? String) ?? ""
if value.contains("google.com") {
break
}
RunLoop.current.run(until: Date().addingTimeInterval(0.05))
}
XCTAssertTrue(
((omnibar.value as? String) ?? "").contains("google.com"),
"Expected inline completion to show google.com before Cmd+A."
)
app.typeKey("a", modifierFlags: [.command])
RunLoop.current.run(until: Date().addingTimeInterval(0.25))
let afterCmdA = (omnibar.value as? String) ?? ""
XCTAssertTrue(
afterCmdA.contains("google.com"),
"Expected Cmd+A to preserve inline completion display instead of collapsing to typed prefix. value=\(afterCmdA)"
)
}
private struct SeedEntry {
let url: String
let title: String
var visitCount: Int = 2
var typedCount: Int = 0
}
private func seedBrowserHistoryForTest(entries: [(String, String)]? = nil, seedEntries: [SeedEntry]? = nil) {
// Keep the test hermetic: write a deterministic history file in the app's support dir
// so the omnibar always has at least one local suggestion row.
let fileManager = FileManager.default
guard let appSupport = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
XCTFail("Missing Application Support directory")
return
}
let bundleId = "com.cmuxterm.app.debug"
let dir = appSupport.appendingPathComponent(bundleId, isDirectory: true)
let url = dir.appendingPathComponent("browser_history.json", isDirectory: false)
do {
try fileManager.createDirectory(at: dir, withIntermediateDirectories: true)
} catch {
XCTFail("Failed to create app support dir: \(error)")
return
}
let now = Date().timeIntervalSinceReferenceDate
let resolved: [SeedEntry]
if let seedEntries {
resolved = seedEntries
} else if let entries {
resolved = entries.map { SeedEntry(url: $0.0, title: $0.1, visitCount: 10, typedCount: 2) }
} else {
resolved = [
SeedEntry(url: "https://example.com/", title: "Example Domain", visitCount: 10, typedCount: 2),
SeedEntry(url: "https://go.dev/", title: "The Go Programming Language", visitCount: 10, typedCount: 2),
SeedEntry(url: "https://www.google.com/", title: "Google", visitCount: 10, typedCount: 2),
]
}
let entriesJSON = resolved.enumerated().reversed().map { index, entry in
let recencyOffset = index * 120
var json = """
{
"id": "\(UUID().uuidString)",
"url": "\(entry.url)",
"title": "\(entry.title)",
"lastVisited": \(now - Double(recencyOffset)),
"visitCount": \(entry.visitCount)
"""
if entry.typedCount > 0 {
json += """
,
"typedCount": \(entry.typedCount),
"lastTypedAt": \(now - Double(recencyOffset))
"""
}
json += "\n }"
return json
}.joined(separator: ",\n")
let json = """
[
\(entriesJSON)
]
"""
do {
try json.write(to: url, atomically: true, encoding: .utf8)
} catch {
XCTFail("Failed to write browser history seed file: \(error)")
}
}
private func attachElementDebug(name: String, element: XCUIElement) {
let payload = """
identifier: \(element.identifier)
label: \(element.label)
exists: \(element.exists)
hittable: \(element.isHittable)
frame: \(element.frame)
"""
let attachment = XCTAttachment(string: payload)
attachment.name = name
attachment.lifetime = .keepAlways
add(attachment)
}
private func waitForRowSelected(_ row: XCUIElement, timeout: TimeInterval) -> Bool {
let deadline = Date().addingTimeInterval(timeout)
while Date() < deadline {
if ((row.value as? String) ?? "").contains("selected") {
return true
}
RunLoop.current.run(until: Date().addingTimeInterval(0.05))
}
return ((row.value as? String) ?? "").contains("selected")
}
}