diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index aeb406512813..56aa4423a6a7 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -633,6 +633,7 @@ 7A9F29392CABFAFC005F2089 /* InfoHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9F29382CABFAEC005F2089 /* InfoHeaderView.swift */; }; 7A9F293B2CAC4443005F2089 /* InfoHeaderConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9F293A2CAC4420005F2089 /* InfoHeaderConfig.swift */; }; 7A9F293D2CAD2FD5005F2089 /* InfoModalConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9F293C2CAD2FCF005F2089 /* InfoModalConfig.swift */; }; + 7A9F29352CAA8829005F2089 /* AccessMethodsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9F29342CAA8823005F2089 /* AccessMethodsTests.swift */; }; 7A9FA1422A2E3306000B728D /* CheckboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9FA1412A2E3306000B728D /* CheckboxView.swift */; }; 7A9FA1442A2E3FE5000B728D /* CheckableSettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9FA1432A2E3FE5000B728D /* CheckableSettingsCell.swift */; }; 7AA130992CFF365D00640DF9 /* ConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA130982CFF365A00640DF9 /* ConnectionView.swift */; }; @@ -2148,6 +2149,7 @@ 7A9F29382CABFAEC005F2089 /* InfoHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoHeaderView.swift; sourceTree = ""; }; 7A9F293A2CAC4420005F2089 /* InfoHeaderConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoHeaderConfig.swift; sourceTree = ""; }; 7A9F293C2CAD2FCF005F2089 /* InfoModalConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoModalConfig.swift; sourceTree = ""; }; + 7A9F29342CAA8823005F2089 /* AccessMethodsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessMethodsTests.swift; sourceTree = ""; }; 7A9FA1412A2E3306000B728D /* CheckboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxView.swift; sourceTree = ""; }; 7A9FA1432A2E3FE5000B728D /* CheckableSettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckableSettingsCell.swift; sourceTree = ""; }; 7AA130982CFF365A00640DF9 /* ConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionView.swift; sourceTree = ""; }; @@ -4355,6 +4357,7 @@ 8556EB532B9A1D7100D26DD4 /* BridgingHeader.h */, 85B267602B849ADB0098E3CD /* mullvad-api.h */, 852969372B4ED20E007EAD4C /* Info.plist */, + 7A9F29342CAA8823005F2089 /* AccessMethodsTests.swift */, 852969272B4D9C1F007EAD4C /* AccountTests.swift */, 85557B112B594FC900795FE1 /* ConnectivityTests.swift */, A9BFAFFE2BD004ED00F2BCA1 /* CustomListsTests.swift */, @@ -6683,6 +6686,7 @@ 85021CAE2BDBC4290098B400 /* AppLogsPage.swift in Sources */, 4495ECD12D0B170700A7358B /* UDPOverTCPObfuscationSettingsPage.swift in Sources */, 850201DB2B503D7700EF8C96 /* RelayTests.swift in Sources */, + 7A9F29352CAA8829005F2089 /* AccessMethodsTests.swift in Sources */, 7A45CFC62C05FF6A00D80B21 /* ScreenshotTests.swift in Sources */, 852D054D2BC3DE3A008578D2 /* APIAccessPage.swift in Sources */, 85139B2D2B84B4A700734217 /* OutOfTimePage.swift in Sources */, diff --git a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift index 820c2cabdb52..ea585cebcf46 100644 --- a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift +++ b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift @@ -12,6 +12,7 @@ public enum AccessibilityIdentifier: Equatable { // Buttons case addAccessMethodButton case accessMethodAddButton + case accessMethodTestButton case accountButton case accessMethodUnreachableBackButton case accessMethodUnreachableSaveButton @@ -64,6 +65,9 @@ public enum AccessibilityIdentifier: Equatable { case acceptLocalNetworkSharingButton // Cells case deviceCell + case accessMethodDirectCell + case accessMethodBridgesCell + case accessMethodEncryptedDNSCell case accessMethodProtocolSelectionCell case vpnSettingsCell case dnsSettingsAddServerCell diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/ButtonCellContentConfiguration.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/ButtonCellContentConfiguration.swift index c5c58ef77af2..b4b0558352f2 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/ButtonCellContentConfiguration.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/ButtonCellContentConfiguration.swift @@ -25,6 +25,9 @@ struct ButtonCellContentConfiguration: UIContentConfiguration, Equatable { /// The button content edge insets. var directionalContentEdgeInsets: NSDirectionalEdgeInsets = UIMetrics.SettingsCell.insetLayoutMargins + // Accessibility identifier. + var accessibilityIdentifier: AccessibilityIdentifier? + func makeContentView() -> UIView & UIContentView { return ButtonCellContentView(configuration: self) } diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/ButtonCellContentView.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/ButtonCellContentView.swift index a766b9cfd8a5..533fa7663100 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/ButtonCellContentView.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/ButtonCellContentView.swift @@ -67,6 +67,7 @@ class ButtonCellContentView: UIView, UIContentView { button.isEnabled = actualConfiguration.isEnabled button.style = actualConfiguration.style button.configuration?.contentInsets = actualConfiguration.directionalContentEdgeInsets + button.setAccessibilityIdentifier(actualConfiguration.accessibilityIdentifier) } private func addSubviews() { diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodViewController.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodViewController.swift index ec27f17c9b4c..9b36dcc530f7 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodViewController.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodViewController.swift @@ -199,6 +199,7 @@ extension EditAccessMethodViewController: UITableViewDelegate { private func configureTestMethod(_ cell: UITableViewCell, itemIdentifier: EditAccessMethodItemIdentifier) { var contentConfiguration = ButtonCellContentConfiguration() + contentConfiguration.accessibilityIdentifier = .accessMethodTestButton contentConfiguration.text = itemIdentifier.text contentConfiguration.isEnabled = subject.value.testingStatus != .inProgress contentConfiguration.primaryAction = UIAction { [weak self] _ in @@ -209,6 +210,7 @@ extension EditAccessMethodViewController: UITableViewDelegate { private func configureCancelTest(_ cell: UITableViewCell, itemIdentifier: EditAccessMethodItemIdentifier) { var contentConfiguration = ButtonCellContentConfiguration() + contentConfiguration.accessibilityIdentifier = .accessMethodTestButton contentConfiguration.text = itemIdentifier.text contentConfiguration.isEnabled = subject.value.testingStatus == .inProgress contentConfiguration.primaryAction = UIAction { [weak self] _ in diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodItem.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodItem.swift index 063546ce8361..0319ba9b66c6 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodItem.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodItem.swift @@ -10,9 +10,10 @@ import Foundation /// A concrete implementation of an API access list item. struct ListAccessMethodItem: Hashable, Identifiable, Equatable { + /// The unique ID. let id: UUID - /// The localized name of an API method. + /// The localized name. let name: String /// The detailed information displayed alongside. diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodViewController.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodViewController.swift index 9e4b91f39ce9..d04a532f1c3c 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodViewController.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodViewController.swift @@ -16,7 +16,7 @@ enum ListAccessMethodSectionIdentifier: Hashable { } struct ListAccessMethodItemIdentifier: Hashable { - var id: UUID + let id: UUID } /// View controller presenting a list of API access methods. @@ -220,6 +220,18 @@ class ListAccessMethodViewController: UIViewController, UITableViewDelegate { cell.disclosureType = .chevron } + let accessibilityId: AccessibilityIdentifier? = switch item.id.uuidString { + case AccessMethodRepository.directId.uuidString: + AccessibilityIdentifier.accessMethodDirectCell + case AccessMethodRepository.bridgeId.uuidString: + AccessibilityIdentifier.accessMethodBridgesCell + case AccessMethodRepository.encryptedDNSId.uuidString: + AccessibilityIdentifier.accessMethodEncryptedDNSCell + default: + nil + } + cell.setAccessibilityIdentifier(accessibilityId) + return cell } diff --git a/ios/MullvadVPNUITests/AccessMethodsTests.swift b/ios/MullvadVPNUITests/AccessMethodsTests.swift new file mode 100644 index 000000000000..37ec9e832d99 --- /dev/null +++ b/ios/MullvadVPNUITests/AccessMethodsTests.swift @@ -0,0 +1,59 @@ +// +// AccessMethodsTests.swift +// MullvadVPN +// +// Created by Jon Petersson on 2024-09-30. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import XCTest + +class AccessMethodsTests: LoggedOutUITestCase { + func testDirect() throws { + HeaderBar(app) + .tapSettingsButton() + + SettingsPage(app) + .tapAPIAccessCell() + + APIAccessPage(app) + .getAccessMethodCell(accessibilityId: AccessibilityIdentifier.accessMethodDirectCell) + .tap() + + EditAccessMethodPage(app) + .tapTestMethodButton() + .verifyTestStatus(.reachable) + } + + func testBridges() throws { + HeaderBar(app) + .tapSettingsButton() + + SettingsPage(app) + .tapAPIAccessCell() + + APIAccessPage(app) + .getAccessMethodCell(accessibilityId: AccessibilityIdentifier.accessMethodBridgesCell) + .tap() + + EditAccessMethodPage(app) + .tapTestMethodButton() + .verifyTestStatus(.reachable) + } + + func testEncryptedDNS() throws { + HeaderBar(app) + .tapSettingsButton() + + SettingsPage(app) + .tapAPIAccessCell() + + APIAccessPage(app) + .getAccessMethodCell(accessibilityId: AccessibilityIdentifier.accessMethodEncryptedDNSCell) + .tap() + + EditAccessMethodPage(app) + .tapTestMethodButton() + .verifyTestStatus(.reachable) + } +} diff --git a/ios/MullvadVPNUITests/Pages/APIAccessPage.swift b/ios/MullvadVPNUITests/Pages/APIAccessPage.swift index feea7a2bb84d..1ea3a1978431 100644 --- a/ios/MullvadVPNUITests/Pages/APIAccessPage.swift +++ b/ios/MullvadVPNUITests/Pages/APIAccessPage.swift @@ -23,6 +23,10 @@ class APIAccessPage: Page { } func getAccessMethodCells() -> [XCUIElement] { - return app.otherElements[AccessibilityIdentifier.apiAccessView].cells.allElementsBoundByIndex + app.otherElements[AccessibilityIdentifier.apiAccessView].cells.allElementsBoundByIndex + } + + func getAccessMethodCell(accessibilityId: AccessibilityIdentifier) -> XCUIElement { + app.otherElements[AccessibilityIdentifier.apiAccessView].cells[accessibilityId] } } diff --git a/ios/MullvadVPNUITests/Pages/EditAccessMethodPage.swift b/ios/MullvadVPNUITests/Pages/EditAccessMethodPage.swift index 9363e602fb1e..0941f8edabae 100644 --- a/ios/MullvadVPNUITests/Pages/EditAccessMethodPage.swift +++ b/ios/MullvadVPNUITests/Pages/EditAccessMethodPage.swift @@ -10,6 +10,10 @@ import Foundation import XCTest class EditAccessMethodPage: Page { + enum TestStatus { + case reachable, unreachable, testing + } + override init(_ app: XCUIApplication) { super.init(app) self.pageElement = app.tables[.editAccessMethodView] @@ -31,6 +35,24 @@ class EditAccessMethodPage: Page { return self } + @discardableResult func verifyTestStatus(_ status: TestStatus) -> Self { + switch status { + case .reachable: + XCTAssertTrue(app.staticTexts["API reachable"].waitForExistence(timeout: BaseUITestCase.longTimeout)) + case .unreachable: + XCTAssertTrue(app.staticTexts["API unreachable"].waitForExistence(timeout: BaseUITestCase.longTimeout)) + case .testing: + XCTAssertTrue(app.staticTexts["Testing..."].waitForExistence(timeout: BaseUITestCase.longTimeout)) + } + + return self + } + + @discardableResult func tapTestMethodButton() -> Self { + app.buttons[AccessibilityIdentifier.accessMethodTestButton].tap() + 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