also looking at this
test(ios): verify app termination before local notification delivery
#308
5 files
+96
-21
| @@ -94,8 +94,9 @@ final class NotificationTapColdStartTests: XCTestCase { | ||
| 94 | 94 | print("CRAFT-E2E-NOTIFICATION-RECEIVED-RESULT \(receipts.label)") |
| 95 | 95 | |
| 96 | 96 | // #258: a notification the page scheduled itself, with data. The |
| 97 | // local link has the page schedule it a few seconds out, and the app | |
| 98 | // is killed before it fires, so its tap is a cold start like the push. | |
| 97 | // local link carries a bounded scheduling window. The page reports | |
| 98 | // its earliest delivery deadline so slow UI calls cannot silently | |
| 99 | // turn this into a foreground-delivery test (#307). | |
| 99 | 100 | app.terminate() |
| 100 | 101 | XCTAssertTrue(app.wait(for: .notRunning, timeout: 15), "the app did not stop before the local link") |
| 101 | 102 | XCUIDevice.shared.system.open(URL(string: localLink)!) |
| @@ -105,10 +106,20 @@ final class NotificationTapColdStartTests: XCTestCase { | ||
| 105 | 106 | .containing(NSPredicate(format: "label BEGINSWITH 'CRAFT-E2E-LOCAL-SCHEDULED '")) |
| 106 | 107 | .firstMatch |
| 107 | 108 | XCTAssertTrue(scheduled.waitForExistence(timeout: 60), "the page never said whether it scheduled the notification") |
| 108 | XCTAssertEqual(scheduled.label, "CRAFT-E2E-LOCAL-SCHEDULED ok") | |
| 109 | let schedulingReport = scheduled.label.split(separator: " ") | |
| 110 | guard schedulingReport.count == 3, schedulingReport[1] == "ok", | |
| 111 | let fireAtMs = Double(schedulingReport[2]), fireAtMs.isFinite, fireAtMs > 0 else { | |
| 112 | XCTFail("invalid local scheduling report: \(scheduled.label)") | |
| 113 | return | |
| 114 | } | |
| 115 | let fireAt = Date(timeIntervalSince1970: fireAtMs / 1000) | |
| 116 | XCTAssertLessThanOrEqual(fireAt.timeIntervalSinceNow, 180, "local scheduling window is unbounded") | |
| 109 | 117 | app.terminate() |
| 110 | 118 | XCTAssertTrue(app.wait(for: .notRunning, timeout: 15), "the app did not stop, so the local tap would not be a cold start") |
| 111 | tapBanner(springboard, app: app, containing: localBody) | |
| 119 | let remaining = fireAt.timeIntervalSinceNow | |
| 120 | print("CRAFT-E2E-LOCAL-TIMING deadlineMs=\(fireAtMs) remainingAfterStopSeconds=\(remaining)") | |
| 121 | XCTAssertGreaterThan(remaining, 0, "local notification deadline elapsed before the app stopped; cold-start precondition was not met") | |
| 122 | tapBanner(springboard, app: app, containing: localBody, timeout: remaining + 30) | |
| 112 | 123 | |
| 113 | 124 | let local = app.staticTexts |
| 114 | 125 | .containing(NSPredicate(format: "label BEGINSWITH 'CRAFT-E2E-NOTIFICATION '")) |
| @@ -130,11 +141,11 @@ final class NotificationTapColdStartTests: XCTestCase { | ||
| 130 | 141 | /// enough for the banner to go, and the test fails saying the tap could |
| 131 | 142 | /// not find it. There is no retrying around that: a tap that misses ends |
| 132 | 143 | /// the test method, even inside XCTExpectFailure. |
| 133 | private func tapBanner(_ springboard: XCUIApplication, app: XCUIApplication, containing text: String) { | |
| 144 | private func tapBanner(_ springboard: XCUIApplication, app: XCUIApplication, containing text: String, timeout: TimeInterval = 120) { | |
| 134 | 145 | let banner = springboard.descendants(matching: .any) |
| 135 | 146 | .matching(NSPredicate(format: "label CONTAINS %@", text)) |
| 136 | 147 | .firstMatch |
| 137 | XCTAssertTrue(banner.waitForExistence(timeout: 120), "no banner for \(text) ever appeared") | |
| 148 | XCTAssertTrue(banner.waitForExistence(timeout: timeout), "no banner for \(text) ever appeared") | |
| 138 | 149 | banner.tap() |
| 139 | 150 | XCTAssertTrue(app.wait(for: .runningForeground, timeout: 60), "tapping the banner did not start the app") |
| 140 | 151 | } |