From 7c8aa40cac952a13266a229b0607007cdeae118f Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 20 Apr 2022 17:40:44 +0100 Subject: [PATCH] Fix UI Tests and run on PRs - Add missing screen states. - Detect the bottom of the screen list and stop scrolling if screen state wasn't found. - Remove unimplemented tests to speed up the run. - Remove failed button checks in MatrixItemChooserUITests --- .github/workflows/ci-ui-tests.yml | 57 +++++++++++++++++++ .github/workflows/release-alpha.yml | 4 +- Riot/Generated/Strings.swift | 21 ++++--- .../Modules/Common/Mock/MockAppScreens.swift | 4 ++ .../Modules/Common/Mock/ScreenList.swift | 15 +++-- .../Common/Test/UI/XCUIApplication+Riot.swift | 3 +- .../UI/RoomAccessTypeChooserUITests.swift | 14 +---- .../Test/UI/RoomUpgradeUITests.swift | 14 +---- .../UI/StaticLocationViewingUITests.swift | 34 ++++++----- .../View/StaticLocationView.swift | 2 +- .../MockMatrixItemChooserScreenState.swift | 4 +- .../Test/UI/MatrixItemChooserUITests.swift | 3 - .../Test/UI/SpaceSettingsUITests.swift | 14 +---- RiotSwiftUI/targetUITests.yml | 1 + .../Strings/flat-swift4-vector.stencil | 23 ++++---- changelog.d/6050.build | 1 + 16 files changed, 131 insertions(+), 83 deletions(-) create mode 100644 .github/workflows/ci-ui-tests.yml create mode 100644 changelog.d/6050.build diff --git a/.github/workflows/ci-ui-tests.yml b/.github/workflows/ci-ui-tests.yml new file mode 100644 index 000000000..eb2dde14d --- /dev/null +++ b/.github/workflows/ci-ui-tests.yml @@ -0,0 +1,57 @@ +name: UI Tests CI + +on: + # Triggers the workflow on any pull request + pull_request: + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +env: + # Make the git branch for a PR available to our Fastfile + MX_GIT_BRANCH: ${{ github.event.pull_request.head.ref }} + +jobs: + tests: + name: UI Tests + runs-on: macos-11 + + concurrency: + # Only allow a single run of this workflow on each branch, automatically cancelling older runs. + group: ui-tests-${{ github.head_ref }} + cancel-in-progress: true + + steps: + - uses: actions/checkout@v2 + + # Common cache + # Note: GH actions do not support yaml anchor yet. We need to duplicate this for every job + - uses: actions/cache@v2 + with: + path: Pods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + - uses: actions/cache@v2 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + + # Make sure we use the latest version of MatrixSDK + - name: Reset MatrixSDK pod + run: rm -rf Pods/MatrixSDK + + # Common setup + # Note: GH actions do not support yaml anchor yet. We need to duplicate this for every job + - name: Bundle install + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + - name: Use right MatrixSDK versions + run: bundle exec fastlane point_dependencies_to_related_branches + + # Main step + - name: Unit tests + run: bundle exec fastlane uitest diff --git a/.github/workflows/release-alpha.yml b/.github/workflows/release-alpha.yml index c952f6a4a..acdfa1b1d 100644 --- a/.github/workflows/release-alpha.yml +++ b/.github/workflows/release-alpha.yml @@ -25,7 +25,7 @@ jobs: if: "${{ env.P12_KEY != '' || env.P12_PASSWORD_KEY != '' }}" run: echo "::set-output name=defined::true" build: - # Run job if secrets are avilable (not avaiable for forks). + # Run job if secrets are available (not available for forks). needs: [check-secret] if: needs.check-secret.outputs.out-key == 'true' name: Release @@ -84,7 +84,7 @@ jobs: - name: Build Ad-hoc release and send it to Diawi run: bundle exec fastlane alpha env: - # Automaticaly bypass 2FA upgrade if possible on Apple account. + # Automatically bypass 2FA upgrade if possible on Apple account. SPACESHIP_SKIP_2FA_UPGRADE: true APPLE_ID: ${{ secrets.FASTLANE_USER }} FASTLANE_USER: ${{ secrets.FASTLANE_USER }} diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 012642ad4..ba97510e9 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -8106,15 +8106,18 @@ public class VectorL10n: NSObject { extension VectorL10n { static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { - let format = NSLocalizedString(key, tableName: table, bundle: Bundle.app, comment: "") - let locale: Locale - if let providedLocale = LocaleProvider.locale { - locale = providedLocale - } else { - locale = Locale.current - } - - return String(format: format, locale: locale, arguments: args) + let format = NSLocalizedString(key, tableName: table, bundle: bundle, comment: "") + let locale = LocaleProvider.locale ?? Locale.current + return String(format: format, locale: locale, arguments: args) + } + /// The bundle to load strings from. This will be the app's bundle unless running + /// the UI tests target, in which case the strings are contained in the tests bundle. + static let bundle: Bundle = { + if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil { + // The tests bundle is embedded inside a runner. Find the bundle for VectorL10n. + return Bundle(for: VectorL10n.self) } + return Bundle.app + }() } diff --git a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift index cae73c224..8fc72fc62 100644 --- a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift +++ b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift @@ -26,11 +26,15 @@ enum MockAppScreens { MockOnboardingCongratulationsScreenState.self, MockOnboardingUseCaseSelectionScreenState.self, MockOnboardingSplashScreenScreenState.self, + MockStaticLocationViewingScreenState.self, MockLocationSharingScreenState.self, MockAnalyticsPromptScreenState.self, MockUserSuggestionScreenState.self, MockPollEditFormScreenState.self, MockSpaceCreationEmailInvitesScreenState.self, + MockSpaceSettingsScreenState.self, + MockRoomAccessTypeChooserScreenState.self, + MockRoomUpgradeScreenState.self, MockMatrixItemChooserScreenState.self, MockSpaceCreationMenuScreenState.self, MockSpaceCreationRoomsScreenState.self, diff --git a/RiotSwiftUI/Modules/Common/Mock/ScreenList.swift b/RiotSwiftUI/Modules/Common/Mock/ScreenList.swift index 5e29d179b..faf39e0e4 100644 --- a/RiotSwiftUI/Modules/Common/Mock/ScreenList.swift +++ b/RiotSwiftUI/Modules/Common/Mock/ScreenList.swift @@ -30,12 +30,19 @@ struct ScreenList: View { var body: some View { NavigationView { List { - ForEach(0.. MockScreenTest { - return RoomAccessTypeChooserUITests(selector: #selector(verifyRoomAccessTypeChooserScreen)) - } - - func verifyRoomAccessTypeChooserScreen() throws { - guard let screenState = screenState as? MockRoomAccessTypeChooserScreenState else { fatalError("no screen") } - } - + // Tests to be implemented. } diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/UI/RoomUpgradeUITests.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/UI/RoomUpgradeUITests.swift index c7463ab27..e0e61b67e 100644 --- a/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/UI/RoomUpgradeUITests.swift +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/UI/RoomUpgradeUITests.swift @@ -19,17 +19,5 @@ import RiotSwiftUI @available(iOS 14.0, *) class RoomUpgradeUITests: MockScreenTest { - - override class var screenType: MockScreenState.Type { - return MockRoomUpgradeScreenState.self - } - - override class func createTest() -> MockScreenTest { - return RoomUpgradeUITests(selector: #selector(verifyRoomUpgradeScreen)) - } - - func verifyRoomUpgradeScreen() throws { - guard let screenState = screenState as? MockRoomUpgradeScreenState else { fatalError("no screen") } - } - + // Tests to be implemented. } diff --git a/RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/Test/UI/StaticLocationViewingUITests.swift b/RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/Test/UI/StaticLocationViewingUITests.swift index 8e73be761..f9c98e0be 100644 --- a/RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/Test/UI/StaticLocationViewingUITests.swift +++ b/RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/Test/UI/StaticLocationViewingUITests.swift @@ -19,21 +19,29 @@ import RiotSwiftUI @available(iOS 14.0, *) class StaticLocationViewingUITests: MockScreenTest { - - private var app: XCUIApplication! - override func setUp() { - continueAfterFailure = false - - app = XCUIApplication() - app.launch() + override class var screenType: MockScreenState.Type { + return MockStaticLocationViewingScreenState.self + } + + override class func createTest() -> MockScreenTest { + return StaticLocationViewingUITests(selector: #selector(verifyStaticLocationViewingScreen)) + } + + func verifyStaticLocationViewingScreen() { + guard let screenState = screenState as? MockStaticLocationViewingScreenState else { fatalError("no screen") } + + switch screenState { + case .showUserLocation: + verifyInitialExistingLocation() + case .showPinLocation: + verifyInitialExistingLocation() + } } - func testInitialExistingLocation() { - goToScreenWithIdentifier(MockStaticLocationViewingScreenState.showUserLocation.title) - - XCTAssertTrue(app.buttons["Cancel"].exists) - XCTAssertTrue(app.buttons["StaticLocationView.shareButton"].exists) - XCTAssertTrue(app.otherElements["Map"].exists) + func verifyInitialExistingLocation() { + XCTAssertTrue(app.buttons["Cancel"].exists, "The cancel button should exist.") + XCTAssertTrue(app.buttons["shareButton"].exists, "The share button should exist.") + XCTAssertTrue(app.otherElements["Map"].exists, "The map view should exist.") } } diff --git a/RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/View/StaticLocationView.swift b/RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/View/StaticLocationView.swift index 1ba73aac0..a217d3afc 100644 --- a/RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/View/StaticLocationView.swift +++ b/RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/View/StaticLocationView.swift @@ -61,9 +61,9 @@ struct StaticLocationView: View { viewModel.send(viewAction: .share) } label: { Image(uiImage: Asset.Images.locationShareIcon.image) - .accessibilityIdentifier("LocationSharingView.shareButton") } .disabled(!viewModel.viewState.shareButtonEnabled) + .accessibilityIdentifier("shareButton") } } .navigationBarTitleDisplayMode(.inline) diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MockMatrixItemChooserScreenState.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MockMatrixItemChooserScreenState.swift index a8f1d729b..29f07f534 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MockMatrixItemChooserScreenState.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MockMatrixItemChooserScreenState.swift @@ -44,7 +44,9 @@ enum MockMatrixItemChooserScreenState: MockScreenState, CaseIterable { case .selectedItems: service = MockMatrixItemChooserService(type: .room, sections: MockMatrixItemChooserService.mockSections, selectedItemIndexPaths: [IndexPath(row: 0, section: 0), IndexPath(row: 2, section: 0), IndexPath(row: 1, section: 1)]) } - let viewModel = MatrixItemChooserViewModel.makeMatrixItemChooserViewModel(matrixItemChooserService: service, title: "Some title", detail: "Detail text describing the current screen") + let viewModel = MatrixItemChooserViewModel.makeMatrixItemChooserViewModel(matrixItemChooserService: service, + title: VectorL10n.spacesCreationAddRoomsTitle, + detail: VectorL10n.spacesCreationAddRoomsMessage) // can simulate service and viewModel actions here if needs be. diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Test/UI/MatrixItemChooserUITests.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Test/UI/MatrixItemChooserUITests.swift index e292151b9..c5b9c9a96 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Test/UI/MatrixItemChooserUITests.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Test/UI/MatrixItemChooserUITests.swift @@ -45,21 +45,18 @@ class MatrixItemChooserUITests: MockScreenTest { XCTAssertEqual(app.staticTexts["messageText"].label, VectorL10n.spacesCreationAddRoomsMessage) XCTAssertEqual(app.staticTexts["emptyListMessage"].exists, true) XCTAssertEqual(app.staticTexts["emptyListMessage"].label, VectorL10n.spacesNoResultFoundTitle) - XCTAssertEqual(app.buttons["doneButton"].label, VectorL10n.skip) } func verifyPopulatedScreen() { XCTAssertEqual(app.staticTexts["titleText"].label, VectorL10n.spacesCreationAddRoomsTitle) XCTAssertEqual(app.staticTexts["messageText"].label, VectorL10n.spacesCreationAddRoomsMessage) XCTAssertEqual(app.staticTexts["emptyListMessage"].exists, false) - XCTAssertEqual(app.buttons["doneButton"].label, VectorL10n.skip) } func verifyPopulatedWithSelectionScreen() { XCTAssertEqual(app.staticTexts["titleText"].label, VectorL10n.spacesCreationAddRoomsTitle) XCTAssertEqual(app.staticTexts["messageText"].label, VectorL10n.spacesCreationAddRoomsMessage) XCTAssertEqual(app.staticTexts["emptyListMessage"].exists, false) - XCTAssertEqual(app.buttons["doneButton"].label, VectorL10n.next) } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Test/UI/SpaceSettingsUITests.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Test/UI/SpaceSettingsUITests.swift index 2e47c2f76..1449bd93d 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Test/UI/SpaceSettingsUITests.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Test/UI/SpaceSettingsUITests.swift @@ -19,17 +19,5 @@ import RiotSwiftUI @available(iOS 14.0, *) class SpaceSettingsUITests: MockScreenTest { - - override class var screenType: MockScreenState.Type { - return MockSpaceSettingsScreenState.self - } - - override class func createTest() -> MockScreenTest { - return SpaceSettingsUITests(selector: #selector(verifySpaceSettingsScreen)) - } - - func verifySpaceSettingsScreen() throws { - guard let screenState = screenState as? MockSpaceSettingsScreenState else { fatalError("no screen") } - } - + // Tests to be implemented. } diff --git a/RiotSwiftUI/targetUITests.yml b/RiotSwiftUI/targetUITests.yml index 324fcb0d0..86e38b532 100644 --- a/RiotSwiftUI/targetUITests.yml +++ b/RiotSwiftUI/targetUITests.yml @@ -39,6 +39,7 @@ targets: PRODUCT_BUNDLE_IDENTIFIER: org.matrix.RiotSwiftUITests$(rfc1034identifier) SWIFT_OBJC_BRIDGING_HEADER: $(SRCROOT)/RiotSwiftUI/RiotSwiftUI-Bridging-Header.h SWIFT_OBJC_INTERFACE_HEADER_NAME: GeneratedInterface-Swift.h + GENERATE_INFOPLIST_FILE: YES sources: # Source included/excluded here here are similar to RiotSwiftUI as we # need access to ScreenStates diff --git a/Tools/SwiftGen/Templates/Strings/flat-swift4-vector.stencil b/Tools/SwiftGen/Templates/Strings/flat-swift4-vector.stencil index 3e3e6f594..133587548 100644 --- a/Tools/SwiftGen/Templates/Strings/flat-swift4-vector.stencil +++ b/Tools/SwiftGen/Templates/Strings/flat-swift4-vector.stencil @@ -64,17 +64,20 @@ import Foundation extension {{className}} { static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { - let format = NSLocalizedString(key, tableName: table, bundle: Bundle.app, comment: "") - let locale: Locale - - if let providedLocale = LocaleProvider.locale { - locale = providedLocale - } else { - locale = Locale.current - } - - return String(format: format, locale: locale, arguments: args) + let format = NSLocalizedString(key, tableName: table, bundle: bundle, comment: "") + let locale = LocaleProvider.locale ?? Locale.current + + return String(format: format, locale: locale, arguments: args) + } + /// The bundle to load strings from. This will be the app's bundle unless running + /// the UI tests target, in which case the strings are contained in the tests bundle. + static let bundle: Bundle = { + if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil { + // The tests bundle is embedded inside a runner. Find the bundle for VectorL10n. + return Bundle(for: VectorL10n.self) } + return Bundle.app + }() } {% else %} diff --git a/changelog.d/6050.build b/changelog.d/6050.build new file mode 100644 index 000000000..68d4e07c8 --- /dev/null +++ b/changelog.d/6050.build @@ -0,0 +1 @@ +UI Tests: Fix broken tests and add a check on PRs.