From abed8379bc59aff40aa70439d10d6528dad758e2 Mon Sep 17 00:00:00 2001 From: Niklas Berglund Date: Thu, 4 Apr 2024 18:03:11 +0200 Subject: [PATCH] Add iOS test making sure app functioning when API is down --- ios/MullvadVPN.xcodeproj/project.pbxproj | 24 +++- .../Classes/AccessbilityIdentifier.swift | 25 +++- .../Coordinators/AccountCoordinator.swift | 1 + .../SwitchCellContentConfiguration.swift | 2 + .../Cells/SwitchCellContentView.swift | 1 + .../Common/SocksSectionHandler.swift | 3 + .../Edit/EditAccessMethodViewController.swift | 2 + .../MethodSettingsCellConfiguration.swift | 2 + .../MethodSettingsViewController.swift | 8 +- .../MethodTestingStatusCellContentView.swift | 10 ++ .../List/ListAccessMethodViewController.swift | 3 + .../Pickers/AccessMethodProtocolPicker.swift | 1 + .../Login/LoginViewController.swift | 11 ++ .../Views/CustomSwitchContainer.swift | 6 + ios/MullvadVPNUITests/ConnectivityTests.swift | 121 ++++++++++++++++-- .../Pages/APIAccessPage.swift | 28 ++++ ios/MullvadVPNUITests/Pages/AccountPage.swift | 5 + .../Pages/AddAccessMethodPage.swift | 92 +++++++++++++ .../Pages/EditAccessMethodPage.swift | 41 ++++++ ios/MullvadVPNUITests/Pages/LoginPage.swift | 4 +- ios/MullvadVPNUITests/Pages/Page.swift | 3 +- .../Pages/SettingsPage.swift | 8 ++ .../Test base classes/BaseUITestCase.swift | 31 +++-- .../XCUIElement+Extensions.swift | 3 +- 24 files changed, 401 insertions(+), 34 deletions(-) create mode 100644 ios/MullvadVPNUITests/Pages/APIAccessPage.swift create mode 100644 ios/MullvadVPNUITests/Pages/AddAccessMethodPage.swift create mode 100644 ios/MullvadVPNUITests/Pages/EditAccessMethodPage.swift diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index b01bed21d732..fc84c277dad7 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -631,6 +631,8 @@ 852BC6702BAB44F500A47558 /* MullvadVPNUITestsChangeSettings.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 852BC66D2BAB44F500A47558 /* MullvadVPNUITestsChangeSettings.xctestplan */; }; 852BC6712BAB44F500A47558 /* MullvadVPNUITestsVerifyDNSSettingsChanged.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 852BC66E2BAB44F500A47558 /* MullvadVPNUITestsVerifyDNSSettingsChanged.xctestplan */; }; 852BC6732BAB450B00A47558 /* MullvadVPNUITestsChangeDNSSettings.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 852BC6722BAB450B00A47558 /* MullvadVPNUITestsChangeDNSSettings.xctestplan */; }; + 852D054D2BC3DE3A008578D2 /* APIAccessPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852D054C2BC3DE3A008578D2 /* APIAccessPage.swift */; }; + 852D054F2BC43DF7008578D2 /* AddAccessMethodPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852D054E2BC43DF7008578D2 /* AddAccessMethodPage.swift */; }; 8532E6872B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8532E6862B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift */; }; 8542CE242B95F7B9006FCA14 /* VPNSettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */; }; 8542F7532BCFBD050035C042 /* SelectLocationFilterPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8542F7522BCFBD050035C042 /* SelectLocationFilterPage.swift */; }; @@ -644,9 +646,10 @@ 8556EB542B9A1D7100D26DD4 /* BridgingHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = 8556EB532B9A1D7100D26DD4 /* BridgingHeader.h */; }; 8556EB562B9B0AC500D26DD4 /* RevokedDevicePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8556EB552B9B0AC500D26DD4 /* RevokedDevicePage.swift */; }; 855D9F5B2B63E56B00D7C64D /* ProblemReportPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 855D9F5A2B63E56B00D7C64D /* ProblemReportPage.swift */; }; + 856952E22BD6B04C008C1F84 /* XCUIElement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 856952E12BD6B04C008C1F84 /* XCUIElement+Extensions.swift */; }; + 8585CBE32BC684180015B6A4 /* EditAccessMethodPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8585CBE22BC684180015B6A4 /* EditAccessMethodPage.swift */; }; 8587A05D2B84D43100152938 /* ChangeLogAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8587A05C2B84D43100152938 /* ChangeLogAlert.swift */; }; 8590896F2B61763B003AF5F5 /* LoggedOutUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590896B2B61763B003AF5F5 /* LoggedOutUITestCase.swift */; }; - 85A42B862BB1D627007BABF7 /* XCUIElement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A42B852BB1D627007BABF7 /* XCUIElement+Extensions.swift */; }; 85A42B882BB44D31007BABF7 /* DeviceManagementPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A42B872BB44D31007BABF7 /* DeviceManagementPage.swift */; }; 85B267612B849ADB0098E3CD /* mullvad-api.h in Headers */ = {isa = PBXBuildFile; fileRef = 85B267602B849ADB0098E3CD /* mullvad-api.h */; }; 85C7A2E92B89024B00035D5A /* SettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C7A2E82B89024B00035D5A /* SettingsTests.swift */; }; @@ -1883,7 +1886,6 @@ 850201DE2B5040A500EF8C96 /* TunnelControlPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelControlPage.swift; sourceTree = ""; }; 850201E22B51A93C00EF8C96 /* SettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPage.swift; sourceTree = ""; }; 85139B2C2B84B4A700734217 /* OutOfTimePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutOfTimePage.swift; sourceTree = ""; }; - 8518F6372B60157E009EB113 /* LoggedInWithoutTimeUITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedInWithoutTimeUITestCase.swift; sourceTree = ""; }; 852969252B4D9C1F007EAD4C /* MullvadVPNUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MullvadVPNUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 852969272B4D9C1F007EAD4C /* AccountTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTests.swift; sourceTree = ""; }; 852969302B4D9E70007EAD4C /* MullvadVPNUITestsAll.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MullvadVPNUITestsAll.xctestplan; sourceTree = ""; }; @@ -1898,6 +1900,8 @@ 852BC66D2BAB44F500A47558 /* MullvadVPNUITestsChangeSettings.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNUITestsChangeSettings.xctestplan; sourceTree = ""; }; 852BC66E2BAB44F500A47558 /* MullvadVPNUITestsVerifyDNSSettingsChanged.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNUITestsVerifyDNSSettingsChanged.xctestplan; sourceTree = ""; }; 852BC6722BAB450B00A47558 /* MullvadVPNUITestsChangeDNSSettings.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNUITestsChangeDNSSettings.xctestplan; sourceTree = ""; }; + 852D054C2BC3DE3A008578D2 /* APIAccessPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIAccessPage.swift; sourceTree = ""; }; + 852D054E2BC43DF7008578D2 /* AddAccessMethodPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccessMethodPage.swift; sourceTree = ""; }; 8532E6862B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportSubmittedPage.swift; sourceTree = ""; }; 8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNSettingsPage.swift; sourceTree = ""; }; 8542F7522BCFBD050035C042 /* SelectLocationFilterPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationFilterPage.swift; sourceTree = ""; }; @@ -1912,11 +1916,12 @@ 8556EB532B9A1D7100D26DD4 /* BridgingHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BridgingHeader.h; sourceTree = ""; }; 8556EB552B9B0AC500D26DD4 /* RevokedDevicePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RevokedDevicePage.swift; sourceTree = ""; }; 855D9F5A2B63E56B00D7C64D /* ProblemReportPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportPage.swift; sourceTree = ""; }; + 856952E12BD6B04C008C1F84 /* XCUIElement+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extensions.swift"; sourceTree = ""; }; + 8585CBE22BC684180015B6A4 /* EditAccessMethodPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccessMethodPage.swift; sourceTree = ""; }; 8587A05C2B84D43100152938 /* ChangeLogAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeLogAlert.swift; sourceTree = ""; }; 859089692B61763B003AF5F5 /* LoggedInWithTimeUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedInWithTimeUITestCase.swift; sourceTree = ""; }; 8590896A2B61763B003AF5F5 /* BaseUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseUITestCase.swift; sourceTree = ""; }; 8590896B2B61763B003AF5F5 /* LoggedOutUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedOutUITestCase.swift; sourceTree = ""; }; - 85A42B852BB1D627007BABF7 /* XCUIElement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extensions.swift"; sourceTree = ""; }; 85A42B872BB44D31007BABF7 /* DeviceManagementPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceManagementPage.swift; sourceTree = ""; }; 85B267602B849ADB0098E3CD /* mullvad-api.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "mullvad-api.h"; path = "../../mullvad-api/include/mullvad-api.h"; sourceTree = ""; }; 85C7A2E82B89024B00035D5A /* SettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = ""; }; @@ -3766,7 +3771,6 @@ 8590896A2B61763B003AF5F5 /* BaseUITestCase.swift */, 859089692B61763B003AF5F5 /* LoggedInWithTimeUITestCase.swift */, 8590896B2B61763B003AF5F5 /* LoggedOutUITestCase.swift */, - 8518F6372B60157E009EB113 /* LoggedInWithoutTimeUITestCase.swift */, ); path = "Test base classes"; sourceTree = ""; @@ -3783,6 +3787,10 @@ 85557B0C2B591B0F00795FE1 /* Networking */, 852969312B4E9220007EAD4C /* Pages */, 850201DA2B503D7700EF8C96 /* RelayTests.swift */, + 8518F6392B601910009EB113 /* Test base classes */, + 856952E12BD6B04C008C1F84 /* XCUIElement+Extensions.swift */, + 85557B152B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift */, + 85C7A2E82B89024B00035D5A /* SettingsTests.swift */, 85D039972BA4711800940E7F /* SettingsMigrationTests.swift */, 85C7A2E82B89024B00035D5A /* SettingsTests.swift */, 8518F6392B601910009EB113 /* Test base classes */, @@ -3818,6 +3826,9 @@ 8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */, 85FB5A0B2B6903990015DCED /* WelcomePage.swift */, 8542F7522BCFBD050035C042 /* SelectLocationFilterPage.swift */, + 852D054C2BC3DE3A008578D2 /* APIAccessPage.swift */, + 852D054E2BC43DF7008578D2 /* AddAccessMethodPage.swift */, + 8585CBE22BC684180015B6A4 /* EditAccessMethodPage.swift */, ); path = Pages; sourceTree = ""; @@ -5799,6 +5810,7 @@ buildActionMask = 2147483647; files = ( A9BFB0012BD00B7F00F2BCA1 /* CustomListPage.swift in Sources */, + 8585CBE32BC684180015B6A4 /* EditAccessMethodPage.swift in Sources */, 8556EB522B9A1C6900D26DD4 /* MullvadApi.swift in Sources */, 85EC620C2B838D10005AFFB5 /* MullvadAPIWrapper.swift in Sources */, A9DF789D2B7D1E8B0094E4AD /* LoggedInWithTimeUITestCase.swift in Sources */, @@ -5808,10 +5820,11 @@ 850201DD2B503D8C00EF8C96 /* SelectLocationPage.swift in Sources */, 85D039982BA4711800940E7F /* SettingsMigrationTests.swift in Sources */, 850201DB2B503D7700EF8C96 /* RelayTests.swift in Sources */, + 852D054D2BC3DE3A008578D2 /* APIAccessPage.swift in Sources */, 85139B2D2B84B4A700734217 /* OutOfTimePage.swift in Sources */, - 85A42B862BB1D627007BABF7 /* XCUIElement+Extensions.swift in Sources */, 852969362B4E9724007EAD4C /* AccessbilityIdentifier.swift in Sources */, 85E3BDE52B70E18C00FA71FD /* Networking.swift in Sources */, + 856952E22BD6B04C008C1F84 /* XCUIElement+Extensions.swift in Sources */, 85C7A2E92B89024B00035D5A /* SettingsTests.swift in Sources */, 8590896F2B61763B003AF5F5 /* LoggedOutUITestCase.swift in Sources */, 85557B202B5FBBD700795FE1 /* AccountPage.swift in Sources */, @@ -5838,6 +5851,7 @@ 85557B122B594FC900795FE1 /* ConnectivityTests.swift in Sources */, 852969332B4E9232007EAD4C /* Page.swift in Sources */, A9A557F32B7E19B10017ADA8 /* SettingsPage.swift in Sources */, + 852D054F2BC43DF7008578D2 /* AddAccessMethodPage.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift index 9fab241c13b2..f0d8c26ec0d8 100644 --- a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift +++ b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift @@ -10,7 +10,11 @@ import UIKit public enum AccessibilityIdentifier: String { // Buttons + case addAccessMethodButton + case accessMethodAddButton case accountButton + case accessMethodUnreachableBackButton + case accessMethodUnreachableSaveButton case agreeButton case alertOkButton case applyButton @@ -56,6 +60,7 @@ public enum AccessibilityIdentifier: String { // Cells case deviceCell + case accessMethodProtocolSelectionCell case vpnSettingsCell case dnsSettingsAddServerCell case dnsSettingsUseCustomDNSCell @@ -83,8 +88,13 @@ public enum AccessibilityIdentifier: String { case customListLocationCell // Labels - case accountPagePaidUntilLabel case accountPageDeviceNameLabel + case socks5ServerCell + case socks5PortCell + case accountPagePaidUntilLabel + case addAccessMethodTestStatusReachableLabel + case addAccessMethodTestStatusTestingLabel + case addAccessMethodTestStatusUnreachableLabel case headerDeviceNameLabel case connectionStatusConnectedLabel case connectionStatusNotConnectedLabel @@ -92,11 +102,17 @@ public enum AccessibilityIdentifier: String { case connectionPanelDetailLabel // Views + case accessMethodProtocolPickerView + case accessMethodUnreachableAlert case accountView + case addLocationsView + case addAccessMethodTableView + case apiAccessView case alertContainerView case alertTitle case changeLogAlert case deviceManagementView + case editAccessMethodView case headerBarView case loginView case outOfTimeView @@ -121,17 +137,24 @@ public enum AccessibilityIdentifier: String { case editCustomListEditLocationsTableView // Other UI elements + case accessMethodEnableSwitch + case accessMethodNameTextField + case logOutSpinnerAlertView case connectionPanelInAddressRow case connectionPanelOutAddressRow case customSwitch case customWireGuardPortTextField case dnsContentBlockersHeaderView case dnsSettingsEnterIPAddressTextField + case loginStatusIconAuthenticating + case loginStatusIconFailure + case loginStatusIconSuccess case loginTextField case selectLocationSearchTextField case problemReportEmailTextField case problemReportMessageTextView case deleteAccountTextField + case socks5AuthenticationSwitch // DNS settings case dnsSettings diff --git a/ios/MullvadVPN/Coordinators/AccountCoordinator.swift b/ios/MullvadVPN/Coordinators/AccountCoordinator.swift index 51da8b43b9cb..839126f4fc47 100644 --- a/ios/MullvadVPN/Coordinators/AccountCoordinator.swift +++ b/ios/MullvadVPN/Coordinators/AccountCoordinator.swift @@ -135,6 +135,7 @@ final class AccountCoordinator: Coordinator, Presentable, Presenting { private func logOut() { let presentation = AlertPresentation( id: "account-logout-alert", + accessibilityIdentifier: .logOutSpinnerAlertView, icon: .spinner, message: nil, buttons: [] diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/SwitchCellContentConfiguration.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/SwitchCellContentConfiguration.swift index 55d9c01533d4..a4dc973eb39b 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/SwitchCellContentConfiguration.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/SwitchCellContentConfiguration.swift @@ -15,6 +15,8 @@ struct SwitchCellContentConfiguration: UIContentConfiguration, Equatable { var color = UIColor.Cell.titleTextColor } + var accessibilityIdentifier: AccessibilityIdentifier? + /// Text label. var text: String? diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/SwitchCellContentView.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/SwitchCellContentView.swift index a0a0d66eece6..fe8817f523c4 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/SwitchCellContentView.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/SwitchCellContentView.swift @@ -76,6 +76,7 @@ class SwitchCellContentView: UIView, UIContentView, UITextFieldDelegate { private func configureSwitch() { switchContainer.control.isOn = actualConfiguration.isOn switchContainer.transform = CGAffineTransform(scaleX: 0.85, y: 0.85) + switchContainer.accessibilityIdentifier = accessibilityIdentifier } private func addSubviews() { diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/SocksSectionHandler.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/SocksSectionHandler.swift index 201ec4e04c50..e2dd45f9d6b9 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/SocksSectionHandler.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/SocksSectionHandler.swift @@ -38,6 +38,7 @@ struct SocksSectionHandler { contentConfiguration.inputText = subject.value.socks.server contentConfiguration.textFieldProperties = .withSmartFeaturesDisabled() contentConfiguration.editingEvents.onChange = subject.bindTextAction(to: \.socks.server) + cell.accessibilityIdentifier = .socks5ServerCell cell.contentConfiguration = contentConfiguration } @@ -52,6 +53,7 @@ struct SocksSectionHandler { if case .phone = cell.traitCollection.userInterfaceIdiom { contentConfiguration.textFieldProperties.keyboardType = .numberPad } + cell.accessibilityIdentifier = .socks5PortCell cell.contentConfiguration = contentConfiguration } @@ -60,6 +62,7 @@ struct SocksSectionHandler { contentConfiguration.text = itemIdentifier.text contentConfiguration.isOn = subject.value.socks.authenticate contentConfiguration.onChange = subject.bindSwitchAction(to: \.socks.authenticate) + contentConfiguration.accessibilityIdentifier = .socks5AuthenticationSwitch cell.contentConfiguration = contentConfiguration } diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodViewController.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodViewController.swift index 55a907677aa8..710ea4a40315 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodViewController.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodViewController.swift @@ -43,6 +43,7 @@ class EditAccessMethodViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() + view.accessibilityIdentifier = .editAccessMethodView view.backgroundColor = .secondaryColor tableView.backgroundColor = .secondaryColor navigationItem.largeTitleDisplayMode = .never @@ -197,6 +198,7 @@ class EditAccessMethodViewController: UITableViewController { private func configureEnableMethod(_ cell: UITableViewCell, itemIdentifier: EditAccessMethodItemIdentifier) { var contentConfiguration = SwitchCellContentConfiguration() + contentConfiguration.accessibilityIdentifier = .accessMethodEnableSwitch contentConfiguration.text = itemIdentifier.text contentConfiguration.isOn = subject.value.isEnabled contentConfiguration.onChange = UIAction { [weak self] action in diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsCellConfiguration.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsCellConfiguration.swift index ac712ef6fac0..979a0ad9c3a7 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsCellConfiguration.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsCellConfiguration.swift @@ -111,6 +111,7 @@ class MethodSettingsCellConfiguration { contentConfiguration.inputText = subject.value.name contentConfiguration.editingEvents.onChange = subject.bindTextAction(to: \.name) + cell.accessibilityIdentifier = .accessMethodNameTextField cell.setDisabled(isTesting) cell.contentConfiguration = contentConfiguration } @@ -153,6 +154,7 @@ class MethodSettingsCellConfiguration { cell.disclosureType = .chevron } + cell.accessibilityIdentifier = .accessMethodProtocolSelectionCell cell.setDisabled(isTesting) } diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsViewController.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsViewController.swift index 78fc98bc73f4..44b18807bac5 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsViewController.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsViewController.swift @@ -76,10 +76,13 @@ class MethodSettingsViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() + view.accessibilityIdentifier = .addAccessMethodTableView view.directionalLayoutMargins = UIMetrics.contentLayoutMargins view.backgroundColor = .secondaryColor navigationItem.rightBarButtonItem = saveBarButton + navigationItem.rightBarButtonItem?.accessibilityIdentifier = .accessMethodAddButton + navigationItem.rightBarButtonItem?.isAccessibilityElement = true isModalInPresentation = true configureTableView() @@ -307,6 +310,7 @@ class MethodSettingsViewController: UITableViewController { case .failed: let presentation = AlertPresentation( id: "api-access-methods-testing-status-failed-alert", + accessibilityIdentifier: .accessMethodUnreachableAlert, icon: .warning, message: NSLocalizedString( "METHOD_SETTINGS_SAVE_PROMPT", @@ -323,6 +327,7 @@ class MethodSettingsViewController: UITableViewController { comment: "" ), style: .default, + accessibilityId: .accessMethodUnreachableSaveButton, handler: { [weak self] in self?.onSave() } @@ -334,7 +339,8 @@ class MethodSettingsViewController: UITableViewController { value: "Back to editing", comment: "" ), - style: .default + style: .default, + accessibilityId: .accessMethodUnreachableBackButton ), ] ) diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodTestingStatusCellContentView.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodTestingStatusCellContentView.swift index b8c9d5f205c1..425dd0798df9 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodTestingStatusCellContentView.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodTestingStatusCellContentView.swift @@ -136,6 +136,16 @@ class MethodTestingStatusCellContentView: UIView, UIContentView { progressView.startAnimating() } + // Set accessibility identifier for the text label based on the status it is indicating + switch actualConfiguration.status { + case .reachable: + textLabel.accessibilityIdentifier = .addAccessMethodTestStatusReachableLabel + case .unreachable: + textLabel.accessibilityIdentifier = .addAccessMethodTestStatusUnreachableLabel + case .testing: + textLabel.accessibilityIdentifier = .addAccessMethodTestStatusTestingLabel + } + // Text label is always the last one, so only add it into the stack if it's not there yet. if textLabel.superview == nil { horizontalStackView.addArrangedSubview(textLabel) diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodViewController.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodViewController.swift index f4340e7877c1..2f32ddf1a90d 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodViewController.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodViewController.swift @@ -67,6 +67,8 @@ class ListAccessMethodViewController: UIViewController, UITableViewDelegate { tableView.registerReusableViews(from: CellReuseIdentifier.self) + view.accessibilityIdentifier = .apiAccessView + view.addConstrainedSubviews([headerView, tableView]) { headerView.pinEdgesToSuperview(.all().excluding(.bottom)) tableView.pinEdgesToSuperview(.all().excluding(.top)) @@ -117,6 +119,7 @@ class ListAccessMethodViewController: UIViewController, UITableViewDelegate { button.addAction(UIAction { [weak self] _ in self?.sendAddNew() }, for: .touchUpInside) + button.accessibilityIdentifier = .addAccessMethodButton let fontSize = button.titleLabel?.font.pointSize ?? 0 button.titleLabel?.font = UIFont.systemFont(ofSize: fontSize, weight: .regular) diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/AccessMethodProtocolPicker.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/AccessMethodProtocolPicker.swift index 3d7cecbe60c6..8a852719fec4 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/AccessMethodProtocolPicker.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/AccessMethodProtocolPicker.swift @@ -23,6 +23,7 @@ struct AccessMethodProtocolPicker { let dataSource = AccessMethodProtocolPickerDataSource() let controller = ListItemPickerViewController(dataSource: dataSource, selectedItemID: currentValue) + controller.view.accessibilityIdentifier = .accessMethodProtocolPickerView controller.navigationItem.title = NSLocalizedString( "SELECT_PROTOCOL_NAV_TITLE", diff --git a/ios/MullvadVPN/View controllers/Login/LoginViewController.swift b/ios/MullvadVPN/View controllers/Login/LoginViewController.swift index a2879b098a44..64d4ad01dcb8 100644 --- a/ios/MullvadVPN/View controllers/Login/LoginViewController.swift +++ b/ios/MullvadVPN/View controllers/Login/LoginViewController.swift @@ -267,6 +267,17 @@ class LoginViewController: UIViewController, RootContainment { private func updateStatusIcon() { contentView.statusActivityView.state = loginState.statusActivityState + + switch loginState { + case .authenticating: + contentView.statusActivityView.accessibilityIdentifier = .loginStatusIconAuthenticating + case .failure: + contentView.statusActivityView.accessibilityIdentifier = .loginStatusIconFailure + case .success: + contentView.statusActivityView.accessibilityIdentifier = .loginStatusIconSuccess + default: + break + } } private func beginLogin(_ action: LoginAction) { diff --git a/ios/MullvadVPN/Views/CustomSwitchContainer.swift b/ios/MullvadVPN/Views/CustomSwitchContainer.swift index ae1a290f6e01..ed0d9d3b2248 100644 --- a/ios/MullvadVPN/Views/CustomSwitchContainer.swift +++ b/ios/MullvadVPN/Views/CustomSwitchContainer.swift @@ -31,6 +31,12 @@ class CustomSwitchContainer: UIView { } } + override var accessibilityIdentifier: String? { + didSet { + control.accessibilityIdentifier = accessibilityIdentifier + } + } + override var intrinsicContentSize: CGSize { controlSize() } diff --git a/ios/MullvadVPNUITests/ConnectivityTests.swift b/ios/MullvadVPNUITests/ConnectivityTests.swift index 3caa77dd84ce..64438308f940 100644 --- a/ios/MullvadVPNUITests/ConnectivityTests.swift +++ b/ios/MullvadVPNUITests/ConnectivityTests.swift @@ -13,10 +13,6 @@ import XCTest class ConnectivityTests: LoggedOutUITestCase { let firewallAPIClient = FirewallAPIClient() - override func tearDownWithError() throws { - super.tearDown() - } - /// Verifies that the app still functions when API has been blocked func testAPIConnectionViaBridges() throws { addTeardownBlock { @@ -97,16 +93,117 @@ class ConnectivityTests: LoggedOutUITestCase { verifyDeviceHasBeenRemoved(deviceName: deviceName, accountNumber: hasTimeAccountNumber) } -} -private func verifyDeviceHasBeenRemoved(deviceName: String, accountNumber: String) { - do { - let devices = try MullvadAPIWrapper().getDevices(accountNumber) + // swiftlint:disable function_body_length + /// Test that the app is functioning when API is down. To simulate API being down we create a dummy access method + func testAppStillFunctioningWhenAPIDown() throws { + addTeardownBlock { + HeaderBar(self.app) + .tapSettingsButton() + + SettingsPage(self.app) + .tapAPIAccessCell() + + self.toggleAllAccessMethodsEnabledSwitchesIfOff() + } + + // Setup. Create a dummy access method to simulate API being down(unreachable) + LoginPage(app) + .tapAccountNumberTextField() + .enterText(self.hasTimeAccountNumber) + .tapAccountNumberSubmitButton() + + TunnelControlPage(app) + + HeaderBar(app) + .tapSettingsButton() + + SettingsPage(app) + .tapAPIAccessCell() + + toggleAllAccessMethodsEnabledSwitches() + + APIAccessPage(app) + .tapAddButton() + + allowLocalNetworkAccessIfAsked() + + AddAccessMethodPage(app) + .tapNameCell() + .enterText("Disable-access-dummy") + .tapTypeCell() + .tapSOCKS5TypeValueCell() + .tapServerCell() + .enterText("123.123.123.123") + .dismissKeyboard() + .tapPortCell() + .enterText("123") + .dismissKeyboard() + .tapAddButton() + .waitForAPIUnreachableLabel() + + AddAccessMethodAPIUnreachableAlert(app) + .tapSaveButton() + + SettingsPage(app) + .swipeDownToDismissModal() + + // Actual test. Make sure it is possible to connect to a relay + TunnelControlPage(app) + .tapSecureConnectionButton() + + allowAddVPNConfigurationsIfAsked() + + TunnelControlPage(app) + .waitForSecureConnectionLabel() + + HeaderBar(app) + .tapAccountButton() + + // Log out will take long because API cannot be reached + AccountPage(app) + .tapLogOutButton() + .waitForSpinnerNoLongerShown() + + // Verify API cannot be reached by doing a login attempt which should fail + LoginPage(app) + .tapAccountNumberTextField() + .enterText(self.hasTimeAccountNumber) + .tapAccountNumberSubmitButton() + .verifyFailIconShown() + } + + private func verifyDeviceHasBeenRemoved(deviceName: String, accountNumber: String) { + do { + let devices = try MullvadAPIWrapper().getDevices(accountNumber) + + for device in devices where device.name == deviceName { + XCTFail("Device has not been removed which tells us that the logout was not successful") + } + } catch { + XCTFail("Failed to get devices from app API") + } + } + + // swiftlint:enable function_body_length + + /// Toggle enabled switch for all existing access methods. It is a precondition that the app is currently showing API access view. + private func toggleAllAccessMethodsEnabledSwitches() { + for cell in APIAccessPage(app).getAccessMethodCells() { + cell.tap() + EditAccessMethodPage(app) + .tapEnableMethodSwitch() + .tapBackButton() + } + } - for device in devices where device.name == deviceName { - XCTFail("Device has not been removed which tells us that the logout was not successful") + /// Toggle enabled switch for all existing access methods if the switch is in off state. It is a precondition that the app is currently showing API access view. + private func toggleAllAccessMethodsEnabledSwitchesIfOff() { + for cell in APIAccessPage(app).getAccessMethodCells() { + cell.tap() + EditAccessMethodPage(app) + .tapEnableMethodSwitchIfOff() + .tapBackButton() } - } catch { - XCTFail("Failed to get devices from app API") } } diff --git a/ios/MullvadVPNUITests/Pages/APIAccessPage.swift b/ios/MullvadVPNUITests/Pages/APIAccessPage.swift new file mode 100644 index 000000000000..33d8be3393fa --- /dev/null +++ b/ios/MullvadVPNUITests/Pages/APIAccessPage.swift @@ -0,0 +1,28 @@ +// +// APIAccessPage.swift +// MullvadVPNUITests +// +// Created by Niklas Berglund on 2024-04-08. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import XCTest + +class APIAccessPage: Page { + override init(_ app: XCUIApplication) { + super.init(app) + self.pageAccessibilityIdentifier = .apiAccessView + waitForPageToBeShown() + } + + @discardableResult func tapAddButton() -> Self { + app.buttons[AccessibilityIdentifier.addAccessMethodButton] + .tap() + return self + } + + func getAccessMethodCells() -> [XCUIElement] { + return app.otherElements[AccessibilityIdentifier.apiAccessView].cells.allElementsBoundByIndex + } +} diff --git a/ios/MullvadVPNUITests/Pages/AccountPage.swift b/ios/MullvadVPNUITests/Pages/AccountPage.swift index 811f3ef68dbb..83082f339577 100644 --- a/ios/MullvadVPNUITests/Pages/AccountPage.swift +++ b/ios/MullvadVPNUITests/Pages/AccountPage.swift @@ -68,7 +68,12 @@ class AccountPage: Page { } XCTAssertEqual(strippedDate, paidUntilLabelDate) + return self + } + @discardableResult func waitForSpinnerNoLongerShown() -> Self { + app.otherElements[AccessibilityIdentifier.logOutSpinnerAlertView] + .waitForNonExistence(timeout: BaseUITestCase.veryLongTimeout) return self } } diff --git a/ios/MullvadVPNUITests/Pages/AddAccessMethodPage.swift b/ios/MullvadVPNUITests/Pages/AddAccessMethodPage.swift new file mode 100644 index 000000000000..698397bbbcdd --- /dev/null +++ b/ios/MullvadVPNUITests/Pages/AddAccessMethodPage.swift @@ -0,0 +1,92 @@ +// +// AddAccessMethodPage.swift +// MullvadVPNUITests +// +// Created by Niklas Berglund on 2024-04-08. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import XCTest + +class AddAccessMethodPage: Page { + override init(_ app: XCUIApplication) { + super.init(app) + + self.pageAccessibilityIdentifier = .addAccessMethodTableView + waitForPageToBeShown() + } + + @discardableResult func tapNameCell() -> Self { + app.cells[AccessibilityIdentifier.accessMethodNameTextField] + .tap() + return self + } + + @discardableResult func tapTypeCell() -> Self { + app.cells[AccessibilityIdentifier.accessMethodProtocolSelectionCell] + .tap() + return self + } + + @discardableResult func tapShadowsocksTypeValueCell() -> Self { + app.tables[AccessibilityIdentifier.accessMethodProtocolPickerView].staticTexts["Shadowsocks"].tap() + return self + } + + @discardableResult func tapSOCKS5TypeValueCell() -> Self { + app.tables[AccessibilityIdentifier.accessMethodProtocolPickerView].staticTexts["SOCKS5"].tap() + return self + } + + @discardableResult func tapServerCell() -> Self { + app.cells[AccessibilityIdentifier.socks5ServerCell] + .tap() + return self + } + + @discardableResult func tapPortCell() -> Self { + app.cells[AccessibilityIdentifier.socks5PortCell] + .tap() + return self + } + + @discardableResult func tapAuthenticationSwitch() -> Self { + app.switches[AccessibilityIdentifier.socks5AuthenticationSwitch] + .tap() + return self + } + + @discardableResult func tapAddButton() -> Self { + app.buttons[AccessibilityIdentifier.accessMethodAddButton] + .tap() + return self + } + + @discardableResult func waitForAPIUnreachableLabel() -> Self { + XCTAssertTrue( + app.staticTexts[AccessibilityIdentifier.addAccessMethodTestStatusUnreachableLabel] + .waitForExistence(timeout: BaseUITestCase.longTimeout) + ) + return self + } +} + +class AddAccessMethodAPIUnreachableAlert: Page { + override init(_ app: XCUIApplication) { + super.init(app) + + self.pageAccessibilityIdentifier = .accessMethodUnreachableAlert + waitForPageToBeShown() + } + + @discardableResult func tapSaveButton() -> Self { + app.buttons[AccessibilityIdentifier.accessMethodUnreachableSaveButton].tap() + return self + } + + @discardableResult func tapBackButton() -> Self { + app.buttons[AccessibilityIdentifier.accessMethodUnreachableBackButton].tap() + return self + } +} diff --git a/ios/MullvadVPNUITests/Pages/EditAccessMethodPage.swift b/ios/MullvadVPNUITests/Pages/EditAccessMethodPage.swift new file mode 100644 index 000000000000..68f21d35cca5 --- /dev/null +++ b/ios/MullvadVPNUITests/Pages/EditAccessMethodPage.swift @@ -0,0 +1,41 @@ +// +// EditAccessMethodPage.swift +// MullvadVPNUITests +// +// Created by Niklas Berglund on 2024-04-10. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import XCTest + +class EditAccessMethodPage: Page { + override init(_ app: XCUIApplication) { + super.init(app) + + self.pageAccessibilityIdentifier = .editAccessMethodView + waitForPageToBeShown() + } + + @discardableResult func tapEnableMethodSwitch() -> Self { + app.switches[AccessibilityIdentifier.accessMethodEnableSwitch].tap() + return self + } + + @discardableResult func tapEnableMethodSwitchIfOff() -> Self { + let enableMethodSwitch = app.switches[AccessibilityIdentifier.accessMethodEnableSwitch] + + if enableMethodSwitch.value as? String == "0" { + tapEnableMethodSwitch() + } + + return self + } + + @discardableResult func tapBackButton() -> Self { + // Workaround due to the way automatically managed back buttons work. Back button needs to be nil for the automatic back button behaviour in iOS, and since its nil we cannot set accessibilityIdentifier for it + let backButton = app.navigationBars.firstMatch.buttons.firstMatch + backButton.tap() + return self + } +} diff --git a/ios/MullvadVPNUITests/Pages/LoginPage.swift b/ios/MullvadVPNUITests/Pages/LoginPage.swift index 96c6ebea8936..f7b204b344bc 100644 --- a/ios/MullvadVPNUITests/Pages/LoginPage.swift +++ b/ios/MullvadVPNUITests/Pages/LoginPage.swift @@ -43,11 +43,13 @@ class LoginPage: Page { @discardableResult public func verifySuccessIconShown() -> Self { _ = app.images.element(matching: .image, identifier: "IconSuccess") + .waitForExistence(timeout: BaseUITestCase.defaultTimeout) return self } @discardableResult public func verifyFailIconShown() -> Self { - _ = app.images.element(matching: .image, identifier: "IconFail").waitForExistence(timeout: 15) + _ = app.images.element(matching: .image, identifier: "IconFail") + .waitForExistence(timeout: BaseUITestCase.longTimeout) return self } } diff --git a/ios/MullvadVPNUITests/Pages/Page.swift b/ios/MullvadVPNUITests/Pages/Page.swift index 9ac69f8ce093..09a6b5b2ae3d 100644 --- a/ios/MullvadVPNUITests/Pages/Page.swift +++ b/ios/MullvadVPNUITests/Pages/Page.swift @@ -20,7 +20,8 @@ class Page { func waitForPageToBeShown() { if let pageAccessibilityIdentifier = self.pageAccessibilityIdentifier { XCTAssert( - self.app.otherElements[pageAccessibilityIdentifier] + self.app.descendants(matching: .any).matching(identifier: pageAccessibilityIdentifier.rawValue) + .firstMatch .waitForExistence(timeout: BaseUITestCase.defaultTimeout) ) } diff --git a/ios/MullvadVPNUITests/Pages/SettingsPage.swift b/ios/MullvadVPNUITests/Pages/SettingsPage.swift index 6ed383afa987..db28fb33c929 100644 --- a/ios/MullvadVPNUITests/Pages/SettingsPage.swift +++ b/ios/MullvadVPNUITests/Pages/SettingsPage.swift @@ -24,6 +24,14 @@ class SettingsPage: Page { return self } + @discardableResult func tapAPIAccessCell() -> Self { + app + .cells[AccessibilityIdentifier.apiAccessCell] + .tap() + + return self + } + @discardableResult func tapVPNSettingsCell() -> Self { app.tables[AccessibilityIdentifier.settingsTableView] .cells[AccessibilityIdentifier.vpnSettingsCell] diff --git a/ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift b/ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift index 82feccc89ef5..3551f482feec 100644 --- a/ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift +++ b/ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift @@ -12,6 +12,8 @@ import XCTest class BaseUITestCase: XCTestCase { let app = XCUIApplication() static let defaultTimeout = 5.0 + static let longTimeout = 15.0 + static let veryLongTimeout = 60.0 static let shortTimeout = 1.0 // swiftlint:disable force_cast @@ -25,27 +27,32 @@ class BaseUITestCase: XCTestCase { .infoDictionary?["IOSDevicePinCode"] as! String // swiftlint:enable force_cast - /// Handle iOS add VPN configuration permission alert - allow and enter device PIN code - func allowAddVPNConfigurations() { + /// Handle iOS add VPN configuration permission alert if presented, otherwise ignore + func allowAddVPNConfigurationsIfAsked() { let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") - let alertAllowButton = springboard.buttons.element(boundBy: 0) - if alertAllowButton.waitForExistence(timeout: Self.defaultTimeout) { - alertAllowButton.tap() - } + if springboard.buttons["Allow"].waitForExistence(timeout: Self.shortTimeout) { + let alertAllowButton = springboard.buttons.element(boundBy: 0) + if alertAllowButton.waitForExistence(timeout: Self.defaultTimeout) { + alertAllowButton.tap() + } - if iOSDevicePinCode.isEmpty == false { - _ = springboard.buttons["1"].waitForExistence(timeout: Self.defaultTimeout) - springboard.typeText(iOSDevicePinCode) + if iOSDevicePinCode.isEmpty == false { + _ = springboard.buttons["1"].waitForExistence(timeout: Self.defaultTimeout) + springboard.typeText(iOSDevicePinCode) + } } } - /// Handle iOS add VPN configuration permission alert if presented, otherwise ignore - func allowAddVPNConfigurationsIfAsked() { + /// Handle iOS local network access permission alert if presented, otherwise ignore + func allowLocalNetworkAccessIfAsked() { let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") if springboard.buttons["Allow"].waitForExistence(timeout: Self.shortTimeout) { - allowAddVPNConfigurations() + let alertAllowButton = springboard.buttons["Allow"] + if alertAllowButton.waitForExistence(timeout: Self.defaultTimeout) { + alertAllowButton.tap() + } } } diff --git a/ios/MullvadVPNUITests/XCUIElement+Extensions.swift b/ios/MullvadVPNUITests/XCUIElement+Extensions.swift index 9f0aa87188e7..271d5a767c68 100644 --- a/ios/MullvadVPNUITests/XCUIElement+Extensions.swift +++ b/ios/MullvadVPNUITests/XCUIElement+Extensions.swift @@ -12,7 +12,8 @@ extension XCUIElement { func waitForNonExistence(timeout: TimeInterval) -> Bool { let predicate = NSPredicate(format: "exists == FALSE") let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self) - _ = XCTWaiter.wait(for: [expectation], timeout: timeout) + + _ = XCTWaiter().wait(for: [expectation], timeout: timeout) return !exists } }