| @@ -33,8 +33,10 @@ final class LiveSmokeUITests: XCTestCase { |
| 33 | app = XCUIApplication() |
33 | app = XCUIApplication() |
| 34 | // xcodebuild runs tests on simulator clones, so the base |
34 | // xcodebuild runs tests on simulator clones, so the base |
| 35 | // device's appearance never applies; force it per run. |
35 | // device's appearance never applies; force it per run. |
| 36 | if ProcessInfo.processInfo.environment["GITBAY_UITEST_DARK"] == "1" { |
36 | switch ProcessInfo.processInfo.environment["GITBAY_UITEST_DARK"] { |
| 37 | app.launchArguments.append("-gb-dark") |
37 | case "1": app.launchArguments.append("-gb-dark") |
| |
38 | case "0": app.launchArguments.append("-gb-light") |
| |
39 | default: break // follow the device, as a real launch does |
| 38 | } |
40 | } |
| 39 | app.launch() |
41 | app.launch() |
| 40 | if name.contains("testRepoManagementFlows") { |
42 | if name.contains("testRepoManagementFlows") { |
| @@ -44,6 +46,9 @@ final class LiveSmokeUITests: XCTestCase { |
| 44 | ensureFixtureIssue() |
46 | ensureFixtureIssue() |
| 45 | try requireFixtureBranch() |
47 | try requireFixtureBranch() |
| 46 | } |
48 | } |
| |
49 | if name.contains("testBlameAndEditFlows") { |
| |
50 | try requireFixtureRepo() |
| |
51 | } |
| 47 | } |
52 | } |
| 48 | |
53 | |
| 49 | override func tearDownWithError() throws { |
54 | override func tearDownWithError() throws { |
| @@ -273,6 +278,21 @@ final class LiveSmokeUITests: XCTestCase { |
| 273 | keys.tap() |
278 | keys.tap() |
| 274 | } |
279 | } |
| 275 | |
280 | |
| |
281 | /// The edit flow saves a change to a real file. Seeding a |
| |
282 | /// repository's first commit is a push, which no test can make, so |
| |
283 | /// this only checks — and says why, rather than failing later at |
| |
284 | /// "cmc/ui-smoke-edit not in the repo list". |
| |
285 | private func requireFixtureRepo() throws { |
| |
286 | guard let data = readCommand(["repo", "show", "cmc/ui-smoke-edit"]), |
| |
287 | let envelope = try? JSONSerialization.jsonObject(with: data) as? [String: Any] |
| |
288 | else { return } // no token, or the instance is unreachable |
| |
289 | try XCTSkipUnless(envelope["data"] != nil, """ |
| |
290 | cmc/ui-smoke-edit is missing. The edit flow saves a change to \ |
| |
291 | notes.txt in it; recreate the repo with that file (see its \ |
| |
292 | README) and run again. |
| |
293 | """) |
| |
294 | } |
| |
295 | |
| 276 | /// Scroll a list until an element is in the hierarchy. Offscreen |
296 | /// Scroll a list until an element is in the hierarchy. Offscreen |
| 277 | /// rows do not exist to XCUITest, so waitForExistence alone fails on |
297 | /// rows do not exist to XCUITest, so waitForExistence alone fails on |
| 278 | /// anything below the fold. |
298 | /// anything below the fold. |
| @@ -291,7 +311,12 @@ final class LiveSmokeUITests: XCTestCase { |
| 291 | /// swallowed silently, so this taps until the tab reports selected. |
311 | /// swallowed silently, so this taps until the tab reports selected. |
| 292 | func selectTab(_ name: String, |
312 | func selectTab(_ name: String, |
| 293 | file: StaticString = #filePath, line: UInt = #line) { |
313 | file: StaticString = #filePath, line: UInt = #line) { |
| 294 | let tab = app.tabBars.buttons[name].firstMatch |
314 | // iPadOS renders a TabView as a sidebar or top bar rather than a |
| |
315 | // bottom tab bar, so the control is not under app.tabBars there. |
| |
316 | var tab = app.tabBars.buttons[name].firstMatch |
| |
317 | if !tab.waitForExistence(timeout: 5) { |
| |
318 | tab = app.buttons[name].firstMatch |
| |
319 | } |
| 295 | XCTAssertTrue(tab.waitForExistence(timeout: 15), |
320 | XCTAssertTrue(tab.waitForExistence(timeout: 15), |
| 296 | "\(name) tab missing", file: file, line: line) |
321 | "\(name) tab missing", file: file, line: line) |
| 297 | // The app is interactive once its own chrome is hittable. |
322 | // The app is interactive once its own chrome is hittable. |
| @@ -994,3 +1019,36 @@ extension LiveSmokeUITests { |
| 994 | .waitForExistence(timeout: 20), "build detail shows no timing") |
1019 | .waitForExistence(timeout: 20), "build detail shows no timing") |
| 995 | } |
1020 | } |
| 996 | } |
1021 | } |
| |
1022 | |
| |
1023 | extension LiveSmokeUITests { |
| |
1024 | |
| |
1025 | /// Store screenshots on iPad. It navigates by tab and through |
| |
1026 | /// Explore rather than reusing the iPhone checkpoint: that one drives |
| |
1027 | /// the repo list's `.searchable`, which iPadOS lays out differently, |
| |
1028 | /// and chasing those differences is the iPad pass this release |
| |
1029 | /// deferred. Tabs and a list row work on both. |
| |
1030 | func testIPadScreenshotCheckpoint() throws { |
| |
1031 | signInIfNeeded() |
| |
1032 | snap("dashboard") |
| |
1033 | |
| |
1034 | selectTab("Explore") |
| |
1035 | snap("explore") |
| |
1036 | |
| |
1037 | // First public repo in the list — no search field involved. |
| |
1038 | let firstRepo = app.cells.firstMatch |
| |
1039 | XCTAssertTrue(firstRepo.waitForExistence(timeout: 20), "explore listed nothing") |
| |
1040 | firstRepo.tap() |
| |
1041 | XCTAssertTrue(app.staticTexts["Files"].firstMatch.waitForExistence(timeout: 20), |
| |
1042 | "repo screen did not open") |
| |
1043 | snap("repo") |
| |
1044 | |
| |
1045 | selectTab("Feed") |
| |
1046 | snap("feed") |
| |
1047 | |
| |
1048 | selectTab("My Profile") |
| |
1049 | XCTAssertTrue(app.staticTexts |
| |
1050 | .containing(NSPredicate(format: "label BEGINSWITH 'Repositories'")).firstMatch |
| |
1051 | .waitForExistence(timeout: 20), "profile did not load") |
| |
1052 | snap("profile") |
| |
1053 | } |
| |
1054 | } |