Commit 604f2da53e
Unsigned
Layout: unified · split
gitbayUITests/LiveSmokeUITests.swift +41 −23
| @@ -11,9 +11,10 @@ import XCTest | ||
| 11 | 11 | /// -only-testing:gitbayUITests ... |
| 12 | 12 | /// |
| 13 | 13 | /// They run as the `ios-smoke` account, a krz admin that owns the |
| 14 | /// scratch and fixture repositories (smoke-account.sh provisions it), and | |
| 15 | /// assume branch `ui-smoke` exists on krz/gitbay-ios. A signed-out device | |
| 16 | /// is signed in at setUp from TEST_RUNNER_GITBAY_UITEST_TOKEN. | |
| 14 | /// scratch and fixture repositories (smoke-account.sh provisions it). | |
| 15 | /// Everything they write goes to those, never to krz/gitbay-ios. A | |
| 16 | /// signed-out device is signed in at setUp from | |
| 17 | /// TEST_RUNNER_GITBAY_UITEST_TOKEN. | |
| 17 | 18 | final class LiveSmokeUITests: XCTestCase { |
| 18 | 19 | |
| 19 | 20 | private var app: XCUIApplication! |
| @@ -32,7 +33,11 @@ final class LiveSmokeUITests: XCTestCase { | ||
| 32 | 33 | static let smokeUser = "ios-smoke" |
| 33 | 34 | static let smokeProfileDescription = "Live UI smoke account for gitbay-ios" |
| 34 | 35 | static let smokePGPEmail = "ios-smoke+pgp@gitbay.org" |
| 35 | static let editRepo = "\(smokeUser)/ui-smoke-edit" | |
| 36 | ||
| 37 | /// The repository the write flows work in: the fixture issue and | |
| 38 | /// merge requests, and the file testBlameAndEditFlows edits. | |
| 39 | /// smoke-account.sh seeds it, with `main` and `ui-smoke` divergent. | |
| 40 | static let fixtureRepo = "\(smokeUser)/ui-smoke-edit" | |
| 36 | 41 | |
| 37 | 42 | override func setUpWithError() throws { |
| 38 | 43 | try XCTSkipUnless( |
| @@ -56,6 +61,8 @@ final class LiveSmokeUITests: XCTestCase { | ||
| 56 | 61 | deleteScratchRepo() |
| 57 | 62 | } |
| 58 | 63 | if name.contains("testCreationAndEditingFlows") { |
| 64 | try requireFixtureRepo() | |
| 65 | ensureFixtureMilestone() | |
| 59 | 66 | ensureFixtureIssue() |
| 60 | 67 | try requireFixtureBranch() |
| 61 | 68 | } |
| @@ -69,6 +76,7 @@ final class LiveSmokeUITests: XCTestCase { | ||
| 69 | 76 | ensureOrgFixtures() |
| 70 | 77 | } |
| 71 | 78 | if name.contains("testCloseInFavourFlow") { |
| 79 | try requireFixtureRepo() | |
| 72 | 80 | try requireFixtureBranch() |
| 73 | 81 | closeSmokeMergeRequests() |
| 74 | 82 | try openSupersedeFixtures() |
| @@ -143,7 +151,7 @@ final class LiveSmokeUITests: XCTestCase { | ||
| 143 | 151 | "--title", Self.successorTitle]) |
| 144 | 152 | let open = openSmokeMergeRequests().map { $0.title } |
| 145 | 153 | try XCTSkipUnless(open.contains(Self.supersededTitle) && open.contains(Self.successorTitle), """ |
| 146 | could not open both fixture merge requests on krz/gitbay-ios \ | |
| 154 | could not open both fixture merge requests on \(Self.fixtureRepo) \ | |
| 147 | 155 | (have \(open)); the ui-smoke branch may be level with main. |
| 148 | 156 | """) |
| 149 | 157 | } |
| @@ -175,6 +183,19 @@ final class LiveSmokeUITests: XCTestCase { | ||
| 175 | 183 | _ = runCommand(["repo", "delete", Self.scratchRepo + "-2", "--yes"]) |
| 176 | 184 | } |
| 177 | 185 | |
| 186 | /// The milestone the editing flow assigns. Created rather than | |
| 187 | /// required: unlike a branch, the API can make one. | |
| 188 | static let fixtureMilestone = "v1.0.0" | |
| 189 | ||
| 190 | private func ensureFixtureMilestone() { | |
| 191 | guard let data = readCommand(["milestone", "list", Self.fixtureRepo, "--state", "all"]), | |
| 192 | let envelope = try? JSONSerialization.jsonObject(with: data) as? [String: Any] | |
| 193 | else { return } | |
| 194 | let rows = envelope["data"] as? [[String: Any]] ?? [] | |
| 195 | if rows.contains(where: { $0["title"] as? String == Self.fixtureMilestone }) { return } | |
| 196 | _ = runCommand(["milestone", "create", Self.fixtureRepo, Self.fixtureMilestone]) | |
| 197 | } | |
| 198 | ||
| 178 | 199 | /// The issue the editing flow works on. It is a fixture, not a real |
| 179 | 200 | /// issue: pinning the test to whatever issue happened to be open put |
| 180 | 201 | /// it at the mercy of the project moving on, and it duly broke when |
| @@ -200,8 +221,6 @@ final class LiveSmokeUITests: XCTestCase { | ||
| 200 | 221 | ) |
| 201 | 222 | } |
| 202 | 223 | |
| 203 | static let fixtureRepo = "krz/gitbay-ios" | |
| 204 | ||
| 205 | 224 | /// The MR flow needs a source branch carrying a commit main does not |
| 206 | 225 | /// have. Nothing in the app or the API can create a branch — that is |
| 207 | 226 | /// a push — so this only checks, and says so plainly rather than |
| @@ -214,9 +233,9 @@ final class LiveSmokeUITests: XCTestCase { | ||
| 214 | 233 | else { return } // no token, or the instance is unreachable |
| 215 | 234 | let names = branches.compactMap { $0["name"] as? String } |
| 216 | 235 | try XCTSkipUnless(names.contains("ui-smoke"), """ |
| 217 | krz/gitbay-ios has no ui-smoke branch. The MR flow opens a \ | |
| 218 | merge request from it; recreate it with a commit main does \ | |
| 219 | not have (see .gitbay/ui-smoke.md on that branch). | |
| 236 | \(Self.fixtureRepo) has no ui-smoke branch. The MR flows open \ | |
| 237 | one merge request each way between it and main, so each side \ | |
| 238 | needs a commit the other lacks; smoke-account.sh seeds both. | |
| 220 | 239 | """) |
| 221 | 240 | } |
| 222 | 241 | |
| @@ -265,9 +284,9 @@ final class LiveSmokeUITests: XCTestCase { | ||
| 265 | 284 | /// MR create, MR close. One test so the flows share navigation and |
| 266 | 285 | /// the writes happen exactly once. |
| 267 | 286 | func testCreationAndEditingFlows() throws { |
| 268 | openRepo("krz/gitbay-ios") | |
| 287 | openRepo(Self.fixtureRepo) | |
| 269 | 288 | |
| 270 | // --- the fixture issue: milestone picker assigns v1.0.0 --- | |
| 289 | // --- the fixture issue: milestone picker assigns the fixture --- | |
| 271 | 290 | app.staticTexts["Issues"].firstMatch.tap() |
| 272 | 291 | let issueRow = app.staticTexts |
| 273 | 292 | .containing(NSPredicate(format: "label CONTAINS 'ui-smoke fixture'")).firstMatch |
| @@ -280,11 +299,11 @@ final class LiveSmokeUITests: XCTestCase { | ||
| 280 | 299 | XCTAssertTrue(milestoneMenu.waitForExistence(timeout: 10)) |
| 281 | 300 | milestoneMenu.tap() |
| 282 | 301 | let milestoneChoice = app.buttons |
| 283 | .containing(NSPredicate(format: "label BEGINSWITH 'v1.0.0'")).firstMatch | |
| 302 | .containing(NSPredicate(format: "label BEGINSWITH %@", Self.fixtureMilestone)).firstMatch | |
| 284 | 303 | XCTAssertTrue(milestoneChoice.waitForExistence(timeout: 5), "milestone menu did not open") |
| 285 | 304 | milestoneChoice.tap() |
| 286 | 305 | // The reload renders the assigned milestone in the header row. |
| 287 | XCTAssertTrue(app.staticTexts["v1.0.0"].firstMatch | |
| 306 | XCTAssertTrue(app.staticTexts[Self.fixtureMilestone].firstMatch | |
| 288 | 307 | .waitForExistence(timeout: 10), "milestone not shown after assign") |
| 289 | 308 | |
| 290 | 309 | // --- the fixture issue: edit sheet saves (content unchanged) --- |
| @@ -415,18 +434,17 @@ final class LiveSmokeUITests: XCTestCase { | ||
| 415 | 434 | keys.tap() |
| 416 | 435 | } |
| 417 | 436 | |
| 418 | /// The edit flow saves a change to a real file. Seeding a | |
| 419 | /// repository's first commit is a push, which no test can make, so | |
| 420 | /// this only checks — and says why, rather than failing later at | |
| 421 | /// "ios-smoke/ui-smoke-edit not in the repo list". | |
| 437 | /// Seeding a repository's first commit is a push, which no test can | |
| 438 | /// make, so this only checks — and says why, rather than failing | |
| 439 | /// later at "ios-smoke/ui-smoke-edit not in the repo list". | |
| 422 | 440 | private func requireFixtureRepo() throws { |
| 423 | guard let data = readCommand(["repo", "show", Self.editRepo]), | |
| 441 | guard let data = readCommand(["repo", "show", Self.fixtureRepo]), | |
| 424 | 442 | let envelope = try? JSONSerialization.jsonObject(with: data) as? [String: Any] |
| 425 | 443 | else { return } // no token, or the instance is unreachable |
| 426 | 444 | try XCTSkipUnless(envelope["data"] != nil, """ |
| 427 | \(Self.editRepo) is missing. The edit flow saves a change to \ | |
| 428 | notes.txt in it; gitbayUITests/smoke-account.sh seeds it (or \ | |
| 429 | push it by hand, see that script) and run again. | |
| 445 | \(Self.fixtureRepo) is missing. The write flows work in it; \ | |
| 446 | gitbayUITests/smoke-account.sh seeds it (or push it by hand, \ | |
| 447 | see that script) and run again. | |
| 430 | 448 | """) |
| 431 | 449 | } |
| 432 | 450 | |
| @@ -1186,7 +1204,7 @@ extension LiveSmokeUITests { | ||
| 1186 | 1204 | // previous query, and clearing it from the harness is unreliable. |
| 1187 | 1205 | app.terminate() |
| 1188 | 1206 | app.launch() |
| 1189 | openRepo(Self.editRepo) | |
| 1207 | openRepo(Self.fixtureRepo) | |
| 1190 | 1208 | app.staticTexts["Files"].firstMatch.tap() |
| 1191 | 1209 | let notes = app.staticTexts["notes.txt"].firstMatch |
| 1192 | 1210 | XCTAssertTrue(notes.waitForExistence(timeout: 15), "notes.txt not in the tree") |
gitbayUITests/smoke-account.sh +53 −16
| @@ -4,7 +4,9 @@ | ||
| 4 | 4 | # what already exists, so a failed run is rerun as is. It creates the |
| 5 | 5 | # account with a fresh SSH key (~/.ssh/gitbay-ios-smoke), makes it a krz |
| 6 | 6 | # admin, sets its profile description, registers a PGP key, and pushes |
| 7 | # the ios-smoke/ui-smoke-edit fixture that testBlameAndEditFlows edits. | |
| 7 | # the ios-smoke/ui-smoke-edit fixture the write flows work in: notes.txt | |
| 8 | # for testBlameAndEditFlows, and a ui-smoke branch divergent from main | |
| 9 | # for the merge-request flows. | |
| 8 | 10 | # LiveSmokeUITests asserts on the same values, so keep the two in step. |
| 9 | 11 | set -eu |
| 10 | 12 | |
| @@ -39,37 +41,72 @@ if ! as_smoke pgp list --json | grep -q "$pgp_email"; then | ||
| 39 | 41 | rm -rf "$gnupg" |
| 40 | 42 | fi |
| 41 | 43 | |
| 42 | if as_smoke repo show "$user/ui-smoke-edit" --json > /dev/null 2>&1; then | |
| 43 | echo "$user/ui-smoke-edit exists" | |
| 44 | exit 0 | |
| 45 | fi | |
| 46 | as_smoke repo create "$user/ui-smoke-edit" | |
| 44 | repo=$user/ui-smoke-edit | |
| 45 | url=ssh://git@gitbay.org/$repo.git | |
| 47 | 46 | work=$(mktemp -d) |
| 48 | git -C "$work" init -q -b main | |
| 49 | cat > "$work/README.md" <<'EOF' | |
| 47 | trap 'rm -rf "$work"' EXIT | |
| 48 | ||
| 49 | commit_in_work() { | |
| 50 | git -C "$work" add -A | |
| 51 | git -C "$work" -c user.name="$user" -c user.email="$user@gitbay.org" \ | |
| 52 | -c commit.gpgsign=false commit -q -m "$1" | |
| 53 | } | |
| 54 | ||
| 55 | push_work() { | |
| 56 | GIT_SSH_COMMAND="$smoke_ssh" git -C "$work" push -q "$url" "$1" | |
| 57 | } | |
| 58 | ||
| 59 | if as_smoke repo show "$repo" --json > /dev/null 2>&1; then | |
| 60 | echo "$repo exists" | |
| 61 | GIT_SSH_COMMAND="$smoke_ssh" git clone -q "$url" "$work" | |
| 62 | else | |
| 63 | as_smoke repo create "$repo" | |
| 64 | git -C "$work" init -q -b main | |
| 65 | cat > "$work/README.md" <<'EOF' | |
| 50 | 66 | # ui-smoke-edit |
| 51 | 67 | |
| 52 | 68 | A fixture repository for the gitbay-ios live UI smoke suite. `notes.txt` |
| 53 | is edited by `testBlameAndEditFlows` on every run. | |
| 69 | is edited by `testBlameAndEditFlows` on every run, and the `ui-smoke` | |
| 70 | branch is what the merge-request flows open merge requests from. | |
| 54 | 71 | |
| 55 | 72 | Nothing in the app or the JSON API can seed a repository's first commit |
| 56 | 73 | (that is a push), so the suite only checks that this repo exists and |
| 57 | 74 | skips when it does not. `gitbayUITests/smoke-account.sh` in krz/gitbay-ios |
| 58 | 75 | is what created it. |
| 59 | 76 | EOF |
| 60 | cat > "$work/notes.txt" <<'EOF' | |
| 77 | cat > "$work/notes.txt" <<'EOF' | |
| 61 | 78 | Scratch content for the live UI smoke suite. |
| 62 | 79 | |
| 63 | 80 | testBlameAndEditFlows opens this file and saves an edit through the app, |
| 64 | 81 | which is how the file-editing path is verified end to end. The text does |
| 65 | 82 | not matter; the file existing does. |
| 66 | 83 | EOF |
| 67 | git -C "$work" add -A | |
| 68 | git -C "$work" -c user.name="$user" -c user.email="$user@gitbay.org" -c commit.gpgsign=false \ | |
| 69 | commit -q -m 'fixture repo for the live smoke suite' | |
| 70 | GIT_SSH_COMMAND="$smoke_ssh" \ | |
| 71 | git -C "$work" push -q "ssh://git@gitbay.org/$user/ui-smoke-edit.git" main | |
| 72 | rm -rf "$work" | |
| 84 | commit_in_work 'fixture repo for the live smoke suite' | |
| 85 | push_work main | |
| 86 | fi | |
| 87 | ||
| 88 | # The MR flows open one merge request each way between ui-smoke and | |
| 89 | # main, so each side needs a commit the other lacks. Only a push can | |
| 90 | # make a branch, which is why this lives here and not in the suite. | |
| 91 | if ! as_smoke repo refs "$repo" --json | grep -q '"ui-smoke"'; then | |
| 92 | git -C "$work" checkout -q -b ui-smoke | |
| 93 | cat > "$work/ui-smoke.md" <<'EOF' | |
| 94 | This branch exists so the live UI smoke suite has a merge request to | |
| 95 | open. It is never merged: the suite opens merge requests both ways | |
| 96 | between it and main, and closes them again. | |
| 97 | EOF | |
| 98 | commit_in_work 'ui-smoke: a commit main does not have' | |
| 99 | push_work ui-smoke | |
| 100 | ||
| 101 | git -C "$work" checkout -q main | |
| 102 | cat > "$work/main.md" <<'EOF' | |
| 103 | The counterpart of ui-smoke.md: a commit on main that the ui-smoke | |
| 104 | branch does not have, so a merge request from main to ui-smoke has | |
| 105 | something to carry. | |
| 106 | EOF | |
| 107 | commit_in_work 'main: a commit ui-smoke does not have' | |
| 108 | push_work main | |
| 109 | fi | |
| 73 | 110 | |
| 74 | 111 | echo "Mint the suite's token with:" |
| 75 | 112 | echo " $smoke_ssh git@gitbay.org token create --name ui-smoke --scope full --ttl 30d" |