129 lines
7.1 KiB
Swift
129 lines
7.1 KiB
Swift
import XCTest
|
|
|
|
/// Local fixture + fake notification center/registrar only. No APNs provider or
|
|
/// system permission dialog is involved in these deterministic UI scenarios.
|
|
@MainActor final class AgentPushUITests: XCTestCase {
|
|
let app = XCUIApplication()
|
|
override func setUp() async throws {
|
|
continueAfterFailure = false
|
|
try await control(["action": "reset", "count": 3])
|
|
app.launchArguments = ["-sybil.ios.apiBaseURL", "http://127.0.0.1:8799/api", "-sybil.ios.adminToken", "fixture-token"]
|
|
app.launchEnvironment = ["SYBIL_PUSH_FIXTURE": "1", "SYBIL_PUSH_PERMISSION": "authorized"]
|
|
}
|
|
override func tearDown() async throws { app.terminate() }
|
|
@discardableResult private func control(_ body: [String: Any]) async throws -> [String: Any] {
|
|
var request = URLRequest(url: URL(string: "http://127.0.0.1:8799/control")!)
|
|
request.httpMethod = "POST"; request.httpBody = try JSONSerialization.data(withJSONObject: body)
|
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
let (data, _) = try await URLSession.shared.data(for: request)
|
|
return try XCTUnwrap(JSONSerialization.jsonObject(with: data) as? [String: Any])
|
|
}
|
|
private func settings() {
|
|
if !app.buttons["Settings"].isHittable {
|
|
XCTAssertTrue(app.buttons["Show sidebar"].waitForExistence(timeout: 20))
|
|
app.buttons["Show sidebar"].tap()
|
|
}
|
|
XCTAssertTrue(app.buttons["Settings"].waitForExistence(timeout: 5)); app.buttons["Settings"].tap()
|
|
XCTAssertTrue(app.buttons["agent-push-refresh"].waitForExistence(timeout: 5))
|
|
if !app.buttons["agent-push-refresh"].isHittable { app.scrollViews.firstMatch.swipeUp() }
|
|
}
|
|
private func capture(_ name: String) {
|
|
let attachment = XCTAttachment(screenshot: XCUIScreen.main.screenshot())
|
|
attachment.name = name; attachment.lifetime = .keepAlways; add(attachment)
|
|
}
|
|
private func openAgent() {
|
|
if !app.buttons["agent-sidebar"].waitForExistence(timeout: 2) || !app.buttons["agent-sidebar"].isHittable {
|
|
XCTAssertTrue(app.buttons["Show sidebar"].waitForExistence(timeout: 20)); app.buttons["Show sidebar"].tap()
|
|
}
|
|
app.buttons["agent-sidebar"].tap()
|
|
XCTAssertTrue(app.textFields["agent-composer"].waitForExistence(timeout: 5))
|
|
}
|
|
private func ensureDisabled() {
|
|
if app.buttons["agent-push-disable"].exists { app.buttons["agent-push-disable"].tap() }
|
|
XCTAssertTrue(app.buttons["agent-push-enable"].waitForExistence(timeout: 5))
|
|
}
|
|
|
|
func testExplicitEnableRefreshAndDisable() async throws {
|
|
app.launch(); settings(); ensureDisabled()
|
|
let before = try await control(["action": "state"])
|
|
let count = (before["pushRequests"] as? [[String: Any]])?.count ?? 0
|
|
app.buttons["agent-push-enable"].tap()
|
|
XCTAssertTrue(app.buttons["agent-push-disable"].waitForExistence(timeout: 5))
|
|
let status = app.staticTexts["agent-push-status"]
|
|
let enabled = NSPredicate(format: "label == %@", "Notifications are enabled.")
|
|
let enabledExpectation = expectation(for: enabled, evaluatedWith: status)
|
|
await fulfillment(of: [enabledExpectation], timeout: 8)
|
|
capture("push-enabled-generic-preview")
|
|
app.buttons["agent-push-refresh"].tap()
|
|
let state = try await control(["action": "state"])
|
|
let requests = try XCTUnwrap(state["pushRequests"] as? [[String: Any]])
|
|
XCTAssertEqual(requests.count, count + 1)
|
|
XCTAssertEqual(requests.last?["environment"] as? String, "sandbox")
|
|
app.buttons["agent-push-disable"].tap()
|
|
XCTAssertTrue(app.buttons["agent-push-enable"].waitForExistence(timeout: 5))
|
|
let revoked = try await control(["action": "state"])
|
|
XCTAssertEqual(revoked["pushActive"] as? Int, 0)
|
|
capture("push-disabled-revoked")
|
|
}
|
|
|
|
func testColdTapRoutesAgentAndPreservesAttachmentCaption() async throws {
|
|
app.launch()
|
|
openAgent()
|
|
let composer = app.textFields["agent-composer"]
|
|
XCTAssertTrue(composer.waitForExistence(timeout: 5))
|
|
composer.tap(); composer.typeText("Push fixture saved caption")
|
|
app.terminate()
|
|
app.launchEnvironment["SYBIL_PUSH_TAP"] = "1"
|
|
app.launch()
|
|
XCTAssertTrue(composer.waitForExistence(timeout: 20))
|
|
XCTAssertTrue((composer.value as? String ?? "").contains("Push fixture saved caption"))
|
|
XCTAssertTrue(app.buttons["agent-attach"].exists)
|
|
capture("push-cold-tap-agent-draft-preserved")
|
|
}
|
|
|
|
func testTapBeforeLoginWaitsThenRoutesAgent() async throws {
|
|
try await control(["action": "auth-required", "enabled": true])
|
|
app.launchArguments = ["-sybil.ios.apiBaseURL", "http://127.0.0.1:8799/api", "-sybil.ios.adminToken", ""]
|
|
app.launchEnvironment["SYBIL_PUSH_TAP"] = "1"
|
|
app.launch()
|
|
XCTAssertTrue(app.buttons["Connect"].waitForExistence(timeout: 15))
|
|
XCTAssertFalse(app.textFields["agent-composer"].exists)
|
|
let state = try await control(["action": "state"])
|
|
XCTAssertEqual(state["readCursor"] as? Int, 0)
|
|
app.secureTextFields.firstMatch.tap(); app.secureTextFields.firstMatch.typeText("fixture-token")
|
|
app.buttons["Connect"].tap()
|
|
XCTAssertTrue(app.textFields["agent-composer"].waitForExistence(timeout: 20))
|
|
capture("push-pending-auth-routed-after-login")
|
|
}
|
|
|
|
func testDeniedAndOldServerLeaveAgentUsable() async throws {
|
|
app.launchEnvironment["SYBIL_PUSH_PERMISSION"] = "denied"
|
|
app.launch(); settings()
|
|
XCTAssertTrue(app.buttons["Open iOS Notification Settings"].waitForExistence(timeout: 5))
|
|
capture("push-denied-settings")
|
|
try await control(["action": "push-available", "enabled": false])
|
|
app.buttons["agent-push-refresh"].tap()
|
|
XCTAssertTrue(app.buttons["Show sidebar"].exists); app.buttons["Show sidebar"].tap()
|
|
app.buttons["agent-sidebar"].tap()
|
|
XCTAssertTrue(app.textFields["agent-composer"].waitForExistence(timeout: 10))
|
|
XCTAssertTrue(app.staticTexts["History message 3: local simulator fixture."].waitForExistence(timeout: 5))
|
|
capture("push-old-server-agent-compatible")
|
|
}
|
|
|
|
func testWarmTapDismissesAttachmentPickerPreservingDraft() async throws {
|
|
app.launchEnvironment["SYBIL_PUSH_TAP_ON_FOREGROUND"] = "1"
|
|
app.launch(); openAgent()
|
|
let composer = app.textFields["agent-composer"]
|
|
composer.tap(); composer.typeText(" Warm push preserved draft")
|
|
app.buttons["agent-attach"].tap(); app.buttons["Files"].tap()
|
|
XCTAssertTrue(app.buttons["Cancel"].firstMatch.waitForExistence(timeout: 5))
|
|
XCUIDevice.shared.press(.home); app.activate()
|
|
XCTAssertTrue(composer.waitForExistence(timeout: 10))
|
|
let visible = expectation(for: NSPredicate(format: "hittable == true"), evaluatedWith: composer)
|
|
await fulfillment(of: [visible], timeout: 5)
|
|
XCTAssertFalse(app.buttons["Cancel"].firstMatch.exists)
|
|
XCTAssertTrue((composer.value as? String ?? "").contains("Warm push preserved draft"))
|
|
capture("push-warm-tap-dismissed-picker-draft-preserved")
|
|
}
|
|
}
|