diff options
| author | Niklas Berglund <niklas.berglund@gmail.com> | 2024-04-04 18:03:11 +0200 |
|---|---|---|
| committer | Niklas Berglund <niklas.berglund@gmail.com> | 2024-05-13 11:00:00 +0200 |
| commit | abed8379bc59aff40aa70439d10d6528dad758e2 (patch) | |
| tree | 4a1358aecc82f29d6719247a09bee426063b5554 /ios | |
| parent | 4380b7a738b777e494d164f920a19ad08338c4ff (diff) | |
| download | mullvadvpn-abed8379bc59aff40aa70439d10d6528dad758e2.tar.xz mullvadvpn-abed8379bc59aff40aa70439d10d6528dad758e2.zip | |
Add iOS test making sure app functioning when API is down
Diffstat (limited to 'ios')
24 files changed, 401 insertions, 34 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index b01bed21d7..fc84c277da 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 = "<group>"; }; 850201E22B51A93C00EF8C96 /* SettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPage.swift; sourceTree = "<group>"; }; 85139B2C2B84B4A700734217 /* OutOfTimePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutOfTimePage.swift; sourceTree = "<group>"; }; - 8518F6372B60157E009EB113 /* LoggedInWithoutTimeUITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedInWithoutTimeUITestCase.swift; sourceTree = "<group>"; }; 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 = "<group>"; }; 852969302B4D9E70007EAD4C /* MullvadVPNUITestsAll.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MullvadVPNUITestsAll.xctestplan; sourceTree = "<group>"; }; @@ -1898,6 +1900,8 @@ 852BC66D2BAB44F500A47558 /* MullvadVPNUITestsChangeSettings.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNUITestsChangeSettings.xctestplan; sourceTree = "<group>"; }; 852BC66E2BAB44F500A47558 /* MullvadVPNUITestsVerifyDNSSettingsChanged.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNUITestsVerifyDNSSettingsChanged.xctestplan; sourceTree = "<group>"; }; 852BC6722BAB450B00A47558 /* MullvadVPNUITestsChangeDNSSettings.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNUITestsChangeDNSSettings.xctestplan; sourceTree = "<group>"; }; + 852D054C2BC3DE3A008578D2 /* APIAccessPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIAccessPage.swift; sourceTree = "<group>"; }; + 852D054E2BC43DF7008578D2 /* AddAccessMethodPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccessMethodPage.swift; sourceTree = "<group>"; }; 8532E6862B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportSubmittedPage.swift; sourceTree = "<group>"; }; 8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNSettingsPage.swift; sourceTree = "<group>"; }; 8542F7522BCFBD050035C042 /* SelectLocationFilterPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationFilterPage.swift; sourceTree = "<group>"; }; @@ -1912,11 +1916,12 @@ 8556EB532B9A1D7100D26DD4 /* BridgingHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BridgingHeader.h; sourceTree = "<group>"; }; 8556EB552B9B0AC500D26DD4 /* RevokedDevicePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RevokedDevicePage.swift; sourceTree = "<group>"; }; 855D9F5A2B63E56B00D7C64D /* ProblemReportPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportPage.swift; sourceTree = "<group>"; }; + 856952E12BD6B04C008C1F84 /* XCUIElement+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extensions.swift"; sourceTree = "<group>"; }; + 8585CBE22BC684180015B6A4 /* EditAccessMethodPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccessMethodPage.swift; sourceTree = "<group>"; }; 8587A05C2B84D43100152938 /* ChangeLogAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeLogAlert.swift; sourceTree = "<group>"; }; 859089692B61763B003AF5F5 /* LoggedInWithTimeUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedInWithTimeUITestCase.swift; sourceTree = "<group>"; }; 8590896A2B61763B003AF5F5 /* BaseUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseUITestCase.swift; sourceTree = "<group>"; }; 8590896B2B61763B003AF5F5 /* LoggedOutUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedOutUITestCase.swift; sourceTree = "<group>"; }; - 85A42B852BB1D627007BABF7 /* XCUIElement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extensions.swift"; sourceTree = "<group>"; }; 85A42B872BB44D31007BABF7 /* DeviceManagementPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceManagementPage.swift; sourceTree = "<group>"; }; 85B267602B849ADB0098E3CD /* mullvad-api.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "mullvad-api.h"; path = "../../mullvad-api/include/mullvad-api.h"; sourceTree = "<group>"; }; 85C7A2E82B89024B00035D5A /* SettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = "<group>"; }; @@ -3766,7 +3771,6 @@ 8590896A2B61763B003AF5F5 /* BaseUITestCase.swift */, 859089692B61763B003AF5F5 /* LoggedInWithTimeUITestCase.swift */, 8590896B2B61763B003AF5F5 /* LoggedOutUITestCase.swift */, - 8518F6372B60157E009EB113 /* LoggedInWithoutTimeUITestCase.swift */, ); path = "Test base classes"; sourceTree = "<group>"; @@ -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 = "<group>"; @@ -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 9fab241c13..f0d8c26ec0 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 51da8b43b9..839126f4fc 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 55d9c01533..a4dc973eb3 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 a0a0d66eec..fe8817f523 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 201ec4e04c..e2dd45f9d6 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 55a907677a..710ea4a403 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 ac712ef6fa..979a0ad9c3 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 78fc98bc73..44b18807ba 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 b8c9d5f205..425dd0798d 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 f4340e7877..2f32ddf1a9 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 3d7cecbe60..8a852719fe 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 a2879b098a..64d4ad01dc 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 ae1a290f6e..ed0d9d3b22 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 3caa77dd84..64438308f9 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 0000000000..33d8be3393 --- /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 811f3ef68d..83082f3395 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 0000000000..698397bbbc --- /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 0000000000..68f21d35cc --- /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 96c6ebea89..f7b204b344 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 9ac69f8ce0..09a6b5b2ae 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 6ed383afa9..db28fb33c9 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 82feccc89e..3551f482fe 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 9f0aa87188..271d5a767c 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 } } |
