Commit 530c076c61
Verified · cmc
gitbay.xcodeproj/project.pbxproj +110
| @@ -18,11 +18,19 @@ | ||
| 18 | 18 | remoteGlobalIDString = 8B289811303FF3E5001BF44A; |
| 19 | 19 | remoteInfo = gitbay; |
| 20 | 20 | }; |
| 21 | 8B2898C7303FF3E5001BF44A /* PBXContainerItemProxy */ = { | |
| 22 | isa = PBXContainerItemProxy; | |
| 23 | containerPortal = 8B28980A303FF3E5001BF44A /* Project object */; | |
| 24 | proxyType = 1; | |
| 25 | remoteGlobalIDString = 8B289811303FF3E5001BF44A; | |
| 26 | remoteInfo = gitbay; | |
| 27 | }; | |
| 21 | 28 | /* End PBXContainerItemProxy section */ |
| 22 | 29 | |
| 23 | 30 | /* Begin PBXFileReference section */ |
| 24 | 31 | 8B289812303FF3E5001BF44A /* gitbay.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = gitbay.app; sourceTree = BUILT_PRODUCTS_DIR; }; |
| 25 | 32 | 8B2898A2303FF3E5001BF44A /* gitbayTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = gitbayTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; |
| 33 | 8B2898C2303FF3E5001BF44A /* gitbayUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = gitbayUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; | |
| 26 | 34 | /* End PBXFileReference section */ |
| 27 | 35 | |
| 28 | 36 | /* Begin PBXFileSystemSynchronizedRootGroup section */ |
| @@ -36,6 +44,11 @@ | ||
| 36 | 44 | path = gitbayTests; |
| 37 | 45 | sourceTree = "<group>"; |
| 38 | 46 | }; |
| 47 | 8B2898C0303FF3E5001BF44A /* gitbayUITests */ = { | |
| 48 | isa = PBXFileSystemSynchronizedRootGroup; | |
| 49 | path = gitbayUITests; | |
| 50 | sourceTree = "<group>"; | |
| 51 | }; | |
| 39 | 52 | /* End PBXFileSystemSynchronizedRootGroup section */ |
| 40 | 53 | |
| 41 | 54 | /* Begin PBXFrameworksBuildPhase section */ |
| @@ -54,6 +67,13 @@ | ||
| 54 | 67 | ); |
| 55 | 68 | runOnlyForDeploymentPostprocessing = 0; |
| 56 | 69 | }; |
| 70 | 8B2898C4303FF3E5001BF44A /* Frameworks */ = { | |
| 71 | isa = PBXFrameworksBuildPhase; | |
| 72 | buildActionMask = 2147483647; | |
| 73 | files = ( | |
| 74 | ); | |
| 75 | runOnlyForDeploymentPostprocessing = 0; | |
| 76 | }; | |
| 57 | 77 | /* End PBXFrameworksBuildPhase section */ |
| 58 | 78 | |
| 59 | 79 | /* Begin PBXGroup section */ |
| @@ -62,6 +82,7 @@ | ||
| 62 | 82 | children = ( |
| 63 | 83 | 8B289814303FF3E5001BF44A /* gitbay */, |
| 64 | 84 | 8B2898A0303FF3E5001BF44A /* gitbayTests */, |
| 85 | 8B2898C0303FF3E5001BF44A /* gitbayUITests */, | |
| 65 | 86 | 8B289813303FF3E5001BF44A /* Products */, |
| 66 | 87 | ); |
| 67 | 88 | sourceTree = "<group>"; |
| @@ -71,6 +92,7 @@ | ||
| 71 | 92 | children = ( |
| 72 | 93 | 8B289812303FF3E5001BF44A /* gitbay.app */, |
| 73 | 94 | 8B2898A2303FF3E5001BF44A /* gitbayTests.xctest */, |
| 95 | 8B2898C2303FF3E5001BF44A /* gitbayUITests.xctest */, | |
| 74 | 96 | ); |
| 75 | 97 | name = Products; |
| 76 | 98 | sourceTree = "<group>"; |
| @@ -124,6 +146,29 @@ | ||
| 124 | 146 | productReference = 8B2898A2303FF3E5001BF44A /* gitbayTests.xctest */; |
| 125 | 147 | productType = "com.apple.product-type.bundle.unit-test"; |
| 126 | 148 | }; |
| 149 | 8B2898C1303FF3E5001BF44A /* gitbayUITests */ = { | |
| 150 | isa = PBXNativeTarget; | |
| 151 | buildConfigurationList = 8B2898CA303FF3E5001BF44A /* Build configuration list for PBXNativeTarget "gitbayUITests" */; | |
| 152 | buildPhases = ( | |
| 153 | 8B2898C3303FF3E5001BF44A /* Sources */, | |
| 154 | 8B2898C4303FF3E5001BF44A /* Frameworks */, | |
| 155 | 8B2898C5303FF3E5001BF44A /* Resources */, | |
| 156 | ); | |
| 157 | buildRules = ( | |
| 158 | ); | |
| 159 | dependencies = ( | |
| 160 | 8B2898C6303FF3E5001BF44A /* PBXTargetDependency */, | |
| 161 | ); | |
| 162 | fileSystemSynchronizedGroups = ( | |
| 163 | 8B2898C0303FF3E5001BF44A /* gitbayUITests */, | |
| 164 | ); | |
| 165 | name = gitbayUITests; | |
| 166 | packageProductDependencies = ( | |
| 167 | ); | |
| 168 | productName = gitbayUITests; | |
| 169 | productReference = 8B2898C2303FF3E5001BF44A /* gitbayUITests.xctest */; | |
| 170 | productType = "com.apple.product-type.bundle.ui-testing"; | |
| 171 | }; | |
| 127 | 172 | /* End PBXNativeTarget section */ |
| 128 | 173 | |
| 129 | 174 | /* Begin PBXProject section */ |
| @@ -141,6 +186,10 @@ | ||
| 141 | 186 | CreatedOnToolsVersion = 26.6; |
| 142 | 187 | TestTargetID = 8B289811303FF3E5001BF44A; |
| 143 | 188 | }; |
| 189 | 8B2898C1303FF3E5001BF44A = { | |
| 190 | CreatedOnToolsVersion = 26.6; | |
| 191 | TestTargetID = 8B289811303FF3E5001BF44A; | |
| 192 | }; | |
| 144 | 193 | }; |
| 145 | 194 | }; |
| 146 | 195 | buildConfigurationList = 8B28980D303FF3E5001BF44A /* Build configuration list for PBXProject "gitbay" */; |
| @@ -162,6 +211,7 @@ | ||
| 162 | 211 | targets = ( |
| 163 | 212 | 8B289811303FF3E5001BF44A /* gitbay */, |
| 164 | 213 | 8B2898A1303FF3E5001BF44A /* gitbayTests */, |
| 214 | 8B2898C1303FF3E5001BF44A /* gitbayUITests */, | |
| 165 | 215 | ); |
| 166 | 216 | }; |
| 167 | 217 | /* End PBXProject section */ |
| @@ -181,6 +231,13 @@ | ||
| 181 | 231 | ); |
| 182 | 232 | runOnlyForDeploymentPostprocessing = 0; |
| 183 | 233 | }; |
| 234 | 8B2898C5303FF3E5001BF44A /* Resources */ = { | |
| 235 | isa = PBXResourcesBuildPhase; | |
| 236 | buildActionMask = 2147483647; | |
| 237 | files = ( | |
| 238 | ); | |
| 239 | runOnlyForDeploymentPostprocessing = 0; | |
| 240 | }; | |
| 184 | 241 | /* End PBXResourcesBuildPhase section */ |
| 185 | 242 | |
| 186 | 243 | /* Begin PBXSourcesBuildPhase section */ |
| @@ -198,6 +255,13 @@ | ||
| 198 | 255 | ); |
| 199 | 256 | runOnlyForDeploymentPostprocessing = 0; |
| 200 | 257 | }; |
| 258 | 8B2898C3303FF3E5001BF44A /* Sources */ = { | |
| 259 | isa = PBXSourcesBuildPhase; | |
| 260 | buildActionMask = 2147483647; | |
| 261 | files = ( | |
| 262 | ); | |
| 263 | runOnlyForDeploymentPostprocessing = 0; | |
| 264 | }; | |
| 201 | 265 | /* End PBXSourcesBuildPhase section */ |
| 202 | 266 | |
| 203 | 267 | /* Begin PBXTargetDependency section */ |
| @@ -206,6 +270,11 @@ | ||
| 206 | 270 | target = 8B289811303FF3E5001BF44A /* gitbay */; |
| 207 | 271 | targetProxy = 8B2898A7303FF3E5001BF44A /* PBXContainerItemProxy */; |
| 208 | 272 | }; |
| 273 | 8B2898C6303FF3E5001BF44A /* PBXTargetDependency */ = { | |
| 274 | isa = PBXTargetDependency; | |
| 275 | target = 8B289811303FF3E5001BF44A /* gitbay */; | |
| 276 | targetProxy = 8B2898C7303FF3E5001BF44A /* PBXContainerItemProxy */; | |
| 277 | }; | |
| 209 | 278 | /* End PBXTargetDependency section */ |
| 210 | 279 | |
| 211 | 280 | /* Begin XCBuildConfiguration section */ |
| @@ -434,6 +503,38 @@ | ||
| 434 | 503 | }; |
| 435 | 504 | name = Release; |
| 436 | 505 | }; |
| 506 | 8B2898C8303FF3E5001BF44A /* Debug */ = { | |
| 507 | isa = XCBuildConfiguration; | |
| 508 | buildSettings = { | |
| 509 | CURRENT_PROJECT_VERSION = 1; | |
| 510 | DEVELOPMENT_TEAM = ZCNAX3VL9D; | |
| 511 | GENERATE_INFOPLIST_FILE = YES; | |
| 512 | MARKETING_VERSION = 1.0; | |
| 513 | PRODUCT_BUNDLE_IDENTIFIER = org.gitbay.gitbayUITests; | |
| 514 | PRODUCT_NAME = "$(TARGET_NAME)"; | |
| 515 | SWIFT_EMIT_LOC_STRINGS = NO; | |
| 516 | SWIFT_VERSION = 6.0; | |
| 517 | TARGETED_DEVICE_FAMILY = "1,2"; | |
| 518 | TEST_TARGET_NAME = gitbay; | |
| 519 | }; | |
| 520 | name = Debug; | |
| 521 | }; | |
| 522 | 8B2898C9303FF3E5001BF44A /* Release */ = { | |
| 523 | isa = XCBuildConfiguration; | |
| 524 | buildSettings = { | |
| 525 | CURRENT_PROJECT_VERSION = 1; | |
| 526 | DEVELOPMENT_TEAM = ZCNAX3VL9D; | |
| 527 | GENERATE_INFOPLIST_FILE = YES; | |
| 528 | MARKETING_VERSION = 1.0; | |
| 529 | PRODUCT_BUNDLE_IDENTIFIER = org.gitbay.gitbayUITests; | |
| 530 | PRODUCT_NAME = "$(TARGET_NAME)"; | |
| 531 | SWIFT_EMIT_LOC_STRINGS = NO; | |
| 532 | SWIFT_VERSION = 6.0; | |
| 533 | TARGETED_DEVICE_FAMILY = "1,2"; | |
| 534 | TEST_TARGET_NAME = gitbay; | |
| 535 | }; | |
| 536 | name = Release; | |
| 537 | }; | |
| 437 | 538 | /* End XCBuildConfiguration section */ |
| 438 | 539 | |
| 439 | 540 | /* Begin XCConfigurationList section */ |
| @@ -464,6 +565,15 @@ | ||
| 464 | 565 | defaultConfigurationIsVisible = 0; |
| 465 | 566 | defaultConfigurationName = Release; |
| 466 | 567 | }; |
| 568 | 8B2898CA303FF3E5001BF44A /* Build configuration list for PBXNativeTarget "gitbayUITests" */ = { | |
| 569 | isa = XCConfigurationList; | |
| 570 | buildConfigurations = ( | |
| 571 | 8B2898C8303FF3E5001BF44A /* Debug */, | |
| 572 | 8B2898C9303FF3E5001BF44A /* Release */, | |
| 573 | ); | |
| 574 | defaultConfigurationIsVisible = 0; | |
| 575 | defaultConfigurationName = Release; | |
| 576 | }; | |
| 467 | 577 | /* End XCConfigurationList section */ |
| 468 | 578 | |
| 469 | 579 | /* Begin XCRemoteSwiftPackageReference section */ |
gitbay/Views/Issues/IssueListView.swift +1
| @@ -41,6 +41,7 @@ struct IssueListView: View { | ||
| 41 | 41 | } label: { |
| 42 | 42 | Image(systemName: "plus") |
| 43 | 43 | } |
| 44 | .accessibilityIdentifier("issue-create-button") | |
| 44 | 45 | } |
| 45 | 46 | } |
| 46 | 47 | .sheet(isPresented: $composing) { |
gitbay/Views/Issues/IssueView.swift +2
| @@ -147,6 +147,7 @@ struct IssueView: View { | ||
| 147 | 147 | } |
| 148 | 148 | .disabled(model.working) |
| 149 | 149 | .task { await model.loadMilestones() } |
| 150 | .accessibilityIdentifier("milestone-menu") | |
| 150 | 151 | } |
| 151 | 152 | } |
| 152 | 153 | |
| @@ -232,6 +233,7 @@ struct IssueView: View { | ||
| 232 | 233 | } |
| 233 | 234 | } |
| 234 | 235 | .disabled(model.working) |
| 236 | .accessibilityIdentifier("issue-actions-menu") | |
| 235 | 237 | } |
| 236 | 238 | } |
| 237 | 239 | } |
gitbay/Views/MRs/MRListView.swift +8
| @@ -39,6 +39,7 @@ struct MRListView: View { | ||
| 39 | 39 | } label: { |
| 40 | 40 | Image(systemName: "plus") |
| 41 | 41 | } |
| 42 | .accessibilityIdentifier("mr-create-button") | |
| 42 | 43 | } |
| 43 | 44 | } |
| 44 | 45 | .sheet(isPresented: $composing) { |
| @@ -72,17 +73,23 @@ private struct MRCreateSheet: View { | ||
| 72 | 73 | TextField("Source branch", text: $source) |
| 73 | 74 | .autocorrectionDisabled() |
| 74 | 75 | .textInputAutocapitalization(.never) |
| 76 | .accessibilityIdentifier("mr-source") | |
| 75 | 77 | TextField("Target branch", text: $target) |
| 76 | 78 | .autocorrectionDisabled() |
| 77 | 79 | .textInputAutocapitalization(.never) |
| 80 | .accessibilityIdentifier("mr-target") | |
| 78 | 81 | } |
| 79 | 82 | Section("Title") { |
| 80 | 83 | TextField("Title", text: $title, axis: .vertical) |
| 81 | 84 | .lineLimit(1...3) |
| 85 | .autocorrectionDisabled() | |
| 86 | .accessibilityIdentifier("mr-title") | |
| 82 | 87 | } |
| 83 | 88 | Section("Body") { |
| 84 | 89 | TextEditor(text: $bodyText) |
| 85 | 90 | .frame(minHeight: 120) |
| 91 | .autocorrectionDisabled() | |
| 92 | .accessibilityIdentifier("mr-body") | |
| 86 | 93 | } |
| 87 | 94 | if let error = model.errorMessage { |
| 88 | 95 | Section { |
| @@ -117,6 +124,7 @@ private struct MRCreateSheet: View { | ||
| 117 | 124 | || target.trimmingCharacters(in: .whitespaces).isEmpty |
| 118 | 125 | || title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty |
| 119 | 126 | ) |
| 127 | .accessibilityIdentifier("mr-submit") | |
| 120 | 128 | } |
| 121 | 129 | } |
| 122 | 130 | } |
gitbay/Views/MRs/MRView.swift +1
| @@ -274,6 +274,7 @@ struct MRView: View { | ||
| 274 | 274 | } |
| 275 | 275 | } |
| 276 | 276 | .disabled(model.working) |
| 277 | .accessibilityIdentifier("mr-actions-menu") | |
| 277 | 278 | } |
| 278 | 279 | } |
| 279 | 280 | } |
gitbay/Views/Shared/ComposeSheet.swift +7
| @@ -19,11 +19,17 @@ struct ComposeSheet: View { | ||
| 19 | 19 | Section("Title") { |
| 20 | 20 | TextField("Title", text: $title, axis: .vertical) |
| 21 | 21 | .lineLimit(1...3) |
| 22 | // Titles name code: paths, commands, branch names. | |
| 23 | // Autocorrection rewrites them. | |
| 24 | .autocorrectionDisabled() | |
| 25 | .accessibilityIdentifier("compose-title") | |
| 22 | 26 | } |
| 23 | 27 | Section("Body") { |
| 24 | 28 | TextEditor(text: $bodyText) |
| 25 | 29 | .frame(minHeight: 160) |
| 26 | 30 | .font(.body) |
| 31 | .autocorrectionDisabled() | |
| 32 | .accessibilityIdentifier("compose-body") | |
| 27 | 33 | } |
| 28 | 34 | if let errorMessage { |
| 29 | 35 | Section { |
| @@ -45,6 +51,7 @@ struct ComposeSheet: View { | ||
| 45 | 51 | } else { |
| 46 | 52 | Button(submitLabel, action: onSubmit) |
| 47 | 53 | .disabled(title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) |
| 54 | .accessibilityIdentifier("compose-submit") | |
| 48 | 55 | } |
| 49 | 56 | } |
| 50 | 57 | } |
gitbayUITests/LiveSmokeUITests.swift added +142
| @@ -0,0 +1,142 @@ | ||
| 1 | import XCTest | |
| 2 | ||
| 3 | /// Live smoke flows against gitbay.org, driven by accessibility — the | |
| 4 | /// coordinate-free verification the simulator's input pipeline cannot be | |
| 5 | /// trusted to do (krz/gitbay-ios#5). | |
| 6 | /// | |
| 7 | /// These WRITE to the live instance (an MR is opened and closed, a | |
| 8 | /// milestone assigned), so they only run when explicitly asked: | |
| 9 | /// | |
| 10 | /// TEST_RUNNER_GITBAY_UITEST_LIVE=1 xcodebuild test \ | |
| 11 | /// -only-testing:gitbayUITests ... | |
| 12 | /// | |
| 13 | /// They assume the simulator is already signed in as an account that can | |
| 14 | /// write to krz/gitbay-ios, and that branch `ui-smoke` exists. | |
| 15 | final class LiveSmokeUITests: XCTestCase { | |
| 16 | ||
| 17 | private var app: XCUIApplication! | |
| 18 | ||
| 19 | override func setUpWithError() throws { | |
| 20 | try XCTSkipUnless( | |
| 21 | ProcessInfo.processInfo.environment["GITBAY_UITEST_LIVE"] == "1", | |
| 22 | "live UI smoke is opt-in; set TEST_RUNNER_GITBAY_UITEST_LIVE=1" | |
| 23 | ) | |
| 24 | continueAfterFailure = false | |
| 25 | app = XCUIApplication() | |
| 26 | app.launch() | |
| 27 | } | |
| 28 | ||
| 29 | /// Everything in one ordered pass: milestone assign, edit-save, | |
| 30 | /// MR create, MR close. One test so the flows share navigation and | |
| 31 | /// the writes happen exactly once. | |
| 32 | func testCreationAndEditingFlows() throws { | |
| 33 | openRepo("krz/gitbay-ios") | |
| 34 | ||
| 35 | // --- Issue #4: milestone picker assigns v1.0.0 --- | |
| 36 | app.staticTexts["Issues"].firstMatch.tap() | |
| 37 | let issueRow = app.staticTexts | |
| 38 | .containing(NSPredicate(format: "label CONTAINS 'README.org'")).firstMatch | |
| 39 | XCTAssertTrue(issueRow.waitForExistence(timeout: 10), "issue #4 not in the list") | |
| 40 | issueRow.tap() | |
| 41 | ||
| 42 | let milestoneMenu = app.descendants(matching: .any) | |
| 43 | .matching(identifier: "milestone-menu").firstMatch | |
| 44 | XCTAssertTrue(milestoneMenu.waitForExistence(timeout: 10)) | |
| 45 | milestoneMenu.tap() | |
| 46 | let milestoneChoice = app.buttons | |
| 47 | .containing(NSPredicate(format: "label BEGINSWITH 'v1.0.0'")).firstMatch | |
| 48 | XCTAssertTrue(milestoneChoice.waitForExistence(timeout: 5), "milestone menu did not open") | |
| 49 | milestoneChoice.tap() | |
| 50 | // The reload renders the assigned milestone in the header row. | |
| 51 | XCTAssertTrue(app.staticTexts["v1.0.0"].firstMatch | |
| 52 | .waitForExistence(timeout: 10), "milestone not shown after assign") | |
| 53 | ||
| 54 | // --- Issue #4: edit sheet saves (content unchanged) --- | |
| 55 | app.descendants(matching: .any).matching(identifier: "issue-actions-menu") | |
| 56 | .firstMatch.tap() | |
| 57 | let edit = app.buttons["Edit"].firstMatch | |
| 58 | XCTAssertTrue(edit.waitForExistence(timeout: 5)) | |
| 59 | edit.tap() | |
| 60 | let title = app.descendants(matching: .any) | |
| 61 | .matching(identifier: "compose-title").firstMatch | |
| 62 | XCTAssertTrue(title.waitForExistence(timeout: 5), "edit sheet did not open") | |
| 63 | XCTAssertTrue((title.value as? String)?.contains("README.org") == true, | |
| 64 | "edit sheet did not prefill") | |
| 65 | app.descendants(matching: .any).matching(identifier: "compose-submit") | |
| 66 | .firstMatch.tap() | |
| 67 | // Sheet dismissal proves the save round-tripped without error. | |
| 68 | XCTAssertTrue(waitForDisappearance(title, timeout: 15), "edit save did not dismiss") | |
| 69 | ||
| 70 | back() // to issues list | |
| 71 | back() // to repo screen | |
| 72 | ||
| 73 | // --- MR: create from ui-smoke, then close it --- | |
| 74 | app.staticTexts["Merge Requests"].firstMatch.tap() | |
| 75 | app.descendants(matching: .any).matching(identifier: "mr-create-button") | |
| 76 | .firstMatch.tap() | |
| 77 | ||
| 78 | let source = app.descendants(matching: .any) | |
| 79 | .matching(identifier: "mr-source").firstMatch | |
| 80 | XCTAssertTrue(source.waitForExistence(timeout: 5), "MR create sheet did not open") | |
| 81 | source.tap() | |
| 82 | source.typeText("ui-smoke") | |
| 83 | ||
| 84 | let target = app.descendants(matching: .any) | |
| 85 | .matching(identifier: "mr-target").firstMatch | |
| 86 | // The default branch prefilled while the sheet loaded. | |
| 87 | XCTAssertEqual(target.value as? String, "main", "target did not prefill") | |
| 88 | ||
| 89 | let mrTitle = app.descendants(matching: .any) | |
| 90 | .matching(identifier: "mr-title").firstMatch | |
| 91 | mrTitle.tap() | |
| 92 | mrTitle.typeText("UI smoke: mr create from the app") | |
| 93 | ||
| 94 | app.descendants(matching: .any).matching(identifier: "mr-submit") | |
| 95 | .firstMatch.tap() | |
| 96 | ||
| 97 | let createdRow = app.staticTexts | |
| 98 | .containing(NSPredicate(format: "label CONTAINS 'UI smoke'")).firstMatch | |
| 99 | XCTAssertTrue(createdRow.waitForExistence(timeout: 15), "created MR not in the list") | |
| 100 | createdRow.tap() | |
| 101 | ||
| 102 | app.descendants(matching: .any).matching(identifier: "mr-actions-menu") | |
| 103 | .firstMatch.tap() | |
| 104 | let close = app.buttons["Close"].firstMatch | |
| 105 | XCTAssertTrue(close.waitForExistence(timeout: 5)) | |
| 106 | close.tap() | |
| 107 | // The confirmation dialog's destructive Close. | |
| 108 | let confirm = app.buttons["Close"].firstMatch | |
| 109 | XCTAssertTrue(confirm.waitForExistence(timeout: 5), "close confirmation missing") | |
| 110 | confirm.tap() | |
| 111 | ||
| 112 | XCTAssertTrue(app.staticTexts["closed"].firstMatch | |
| 113 | .waitForExistence(timeout: 15), "MR did not show closed after close") | |
| 114 | } | |
| 115 | ||
| 116 | // MARK: - Helpers | |
| 117 | ||
| 118 | private func openRepo(_ path: String) { | |
| 119 | let tab = app.buttons["Repositories"].firstMatch | |
| 120 | XCTAssertTrue(tab.waitForExistence(timeout: 10)) | |
| 121 | tab.tap() | |
| 122 | let search = app.searchFields.firstMatch | |
| 123 | XCTAssertTrue(search.waitForExistence(timeout: 10)) | |
| 124 | search.tap() | |
| 125 | search.typeText(path) | |
| 126 | let row = app.staticTexts[path].firstMatch | |
| 127 | XCTAssertTrue(row.waitForExistence(timeout: 15), "\(path) not in the repo list") | |
| 128 | row.tap() | |
| 129 | // Repo screen is loaded once its links render. | |
| 130 | XCTAssertTrue(app.staticTexts["Files"].firstMatch.waitForExistence(timeout: 10)) | |
| 131 | } | |
| 132 | ||
| 133 | private func back() { | |
| 134 | app.navigationBars.buttons.firstMatch.tap() | |
| 135 | } | |
| 136 | ||
| 137 | private func waitForDisappearance(_ element: XCUIElement, timeout: TimeInterval) -> Bool { | |
| 138 | let predicate = NSPredicate(format: "exists == false") | |
| 139 | let expectation = XCTNSPredicateExpectation(predicate: predicate, object: element) | |
| 140 | return XCTWaiter().wait(for: [expectation], timeout: timeout) == .completed | |
| 141 | } | |
| 142 | } | |