diff --git a/ios/Configurations/UITests.xcconfig.template b/ios/Configurations/UITests.xcconfig.template index c8727dd3e142..2ed40f1624e7 100644 --- a/ios/Configurations/UITests.xcconfig.template +++ b/ios/Configurations/UITests.xcconfig.template @@ -6,8 +6,14 @@ // Copyright © 2024 Mullvad VPN AB. All rights reserved. // +// Pin code of the iOS device under test +MULLVAD_IOS_DEVICE_PIN_CODE = + +// Ad serving domain used when testing ad blocking. Not that we are assuming there's an HTTP server running on the host. +MULLVAD_AD_SERVING_DOMAIN = + // Mullvad accounts used by UI tests MULLVAD_NO_TIME_ACCOUNT_NUMBER = MULLVAD_HAS_TIME_ACCOUNT_NUMBER = -MULLVAD_FIVE_WIREGUARD_KEYS_ACCOUNT_NUMBER = +MULLVAD_FIVE_WIREGUARD_KEYS_ACCOUNT_NUMBER = diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 6a43be8c0d0d..0e6a5225cb8b 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -568,13 +568,18 @@ 7AF9BE902A39F26000DBFEDB /* Collection+Sorting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE8F2A39F26000DBFEDB /* Collection+Sorting.swift */; }; 7AF9BE952A40461100DBFEDB /* RelayFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE942A40461100DBFEDB /* RelayFilterView.swift */; }; 7AF9BE972A41C71F00DBFEDB /* RelayFilterChipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE962A41C71F00DBFEDB /* RelayFilterChipView.swift */; }; + 850201DB2B503D7700EF8C96 /* RelayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850201DA2B503D7700EF8C96 /* RelayTests.swift */; }; + 850201DD2B503D8C00EF8C96 /* SelectLocationPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850201DC2B503D8C00EF8C96 /* SelectLocationPage.swift */; }; + 850201DF2B5040A500EF8C96 /* TunnelControlPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850201DE2B5040A500EF8C96 /* TunnelControlPage.swift */; }; 850201E12B51389500EF8C96 /* BaseUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850201E02B51389500EF8C96 /* BaseUITestCase.swift */; }; + 850201E32B51A93C00EF8C96 /* SettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850201E22B51A93C00EF8C96 /* SettingsPage.swift */; }; 852969282B4D9C1F007EAD4C /* AccountTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852969272B4D9C1F007EAD4C /* AccountTests.swift */; }; 852969332B4E9232007EAD4C /* Page.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852969322B4E9232007EAD4C /* Page.swift */; }; 852969352B4E9270007EAD4C /* LoginPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852969342B4E9270007EAD4C /* LoginPage.swift */; }; 852969362B4E9724007EAD4C /* AccessbilityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0B311D2B303A0D004B12E0 /* AccessbilityIdentifier.swift */; }; 8529693A2B4F0238007EAD4C /* TermsOfServicePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852969392B4F0238007EAD4C /* TermsOfServicePage.swift */; }; 8529693C2B4F0257007EAD4C /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8529693B2B4F0257007EAD4C /* Alert.swift */; }; + 85557B162B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B152B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift */; }; A900E9B82ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A900E9B72ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift */; }; A900E9BA2ACC5D0600C95F67 /* RESTRequestExecutor+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A900E9B92ACC5D0600C95F67 /* RESTRequestExecutor+Stubs.swift */; }; A900E9BC2ACC609200C95F67 /* DevicesProxy+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A900E9BB2ACC609200C95F67 /* DevicesProxy+Stubs.swift */; }; @@ -1720,7 +1725,11 @@ 7AF9BE8F2A39F26000DBFEDB /* Collection+Sorting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+Sorting.swift"; sourceTree = ""; }; 7AF9BE942A40461100DBFEDB /* RelayFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterView.swift; sourceTree = ""; }; 7AF9BE962A41C71F00DBFEDB /* RelayFilterChipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterChipView.swift; sourceTree = ""; }; + 850201DA2B503D7700EF8C96 /* RelayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayTests.swift; sourceTree = ""; }; + 850201DC2B503D8C00EF8C96 /* SelectLocationPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationPage.swift; sourceTree = ""; }; + 850201DE2B5040A500EF8C96 /* TunnelControlPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelControlPage.swift; sourceTree = ""; }; 850201E02B51389500EF8C96 /* BaseUITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseUITestCase.swift; sourceTree = ""; }; + 850201E22B51A93C00EF8C96 /* SettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPage.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 /* MullvadVPNUITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MullvadVPNUITests.xctestplan; sourceTree = ""; }; @@ -1730,6 +1739,7 @@ 852969382B4ED818007EAD4C /* UITests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UITests.xcconfig; sourceTree = ""; }; 852969392B4F0238007EAD4C /* TermsOfServicePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsOfServicePage.swift; sourceTree = ""; }; 8529693B2B4F0257007EAD4C /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = ""; }; + 85557B152B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElementQuery+Extensions.swift"; sourceTree = ""; }; A900E9B72ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountsProxy+Stubs.swift"; sourceTree = ""; }; A900E9B92ACC5D0600C95F67 /* RESTRequestExecutor+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RESTRequestExecutor+Stubs.swift"; sourceTree = ""; }; A900E9BB2ACC609200C95F67 /* DevicesProxy+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DevicesProxy+Stubs.swift"; sourceTree = ""; }; @@ -2937,6 +2947,7 @@ 58CE5E62224146200008646E /* MullvadVPN */, 58D0C79423F1CE7000FE9BA7 /* MullvadVPNScreenshots */, 58B0A2A1238EE67E00BC001D /* MullvadVPNTests */, + 852969262B4D9C1F007EAD4C /* MullvadVPNUITests */, 58D223F4294C8FF00029F5F8 /* MullvadLogging */, 581943F228F8014500B0CB5E /* MullvadTypes */, 06799ABD28F98E1D00ACD94E /* MullvadREST */, @@ -2952,7 +2963,6 @@ 58C7A4432A863F490060C66F /* PacketTunnelCoreTests */, 7A88DCCF2A8FABBE00D2FF0E /* Routing */, 7A88DCDD2A8FABBE00D2FF0E /* RoutingTests */, - 852969262B4D9C1F007EAD4C /* MullvadVPNUITests */, 58CE5E61224146200008646E /* Products */, 584F991F2902CBDD001F858D /* Frameworks */, ); @@ -3333,6 +3343,8 @@ 852969312B4E9220007EAD4C /* Pages */, 852969272B4D9C1F007EAD4C /* AccountTests.swift */, 850201E02B51389500EF8C96 /* BaseUITestCase.swift */, + 850201DA2B503D7700EF8C96 /* RelayTests.swift */, + 85557B152B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift */, ); path = MullvadVPNUITests; sourceTree = ""; @@ -3340,10 +3352,13 @@ 852969312B4E9220007EAD4C /* Pages */ = { isa = PBXGroup; children = ( - 852969322B4E9232007EAD4C /* Page.swift */, + 8529693B2B4F0257007EAD4C /* Alert.swift */, 852969342B4E9270007EAD4C /* LoginPage.swift */, + 852969322B4E9232007EAD4C /* Page.swift */, + 850201DC2B503D8C00EF8C96 /* SelectLocationPage.swift */, + 850201E22B51A93C00EF8C96 /* SettingsPage.swift */, 852969392B4F0238007EAD4C /* TermsOfServicePage.swift */, - 8529693B2B4F0257007EAD4C /* Alert.swift */, + 850201DE2B5040A500EF8C96 /* TunnelControlPage.swift */, ); path = Pages; sourceTree = ""; @@ -5202,11 +5217,16 @@ buildActionMask = 2147483647; files = ( 8529693C2B4F0257007EAD4C /* Alert.swift in Sources */, + 850201DD2B503D8C00EF8C96 /* SelectLocationPage.swift in Sources */, + 850201DB2B503D7700EF8C96 /* RelayTests.swift in Sources */, 852969362B4E9724007EAD4C /* AccessbilityIdentifier.swift in Sources */, 852969352B4E9270007EAD4C /* LoginPage.swift in Sources */, 850201E12B51389500EF8C96 /* BaseUITestCase.swift in Sources */, + 850201E32B51A93C00EF8C96 /* SettingsPage.swift in Sources */, 852969282B4D9C1F007EAD4C /* AccountTests.swift in Sources */, + 85557B162B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift in Sources */, 8529693A2B4F0238007EAD4C /* TermsOfServicePage.swift in Sources */, + 850201DF2B5040A500EF8C96 /* TunnelControlPage.swift in Sources */, 852969332B4E9232007EAD4C /* Page.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift index 7cb2e0e607b1..dd0fe98a3547 100644 --- a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift +++ b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift @@ -15,6 +15,7 @@ public enum AccessibilityIdentifier: String { case alertOkButton case applyButton case cancelButton + case connectionPanelButton case collapseButton case deleteButton case disconnectButton @@ -47,9 +48,18 @@ public enum AccessibilityIdentifier: String { case alertTitle case loginView case termsOfServiceView + case selectLocationView + case selectLocationTableView + case settingsTableView + case tunnelControlView // Other UI elements + case connectionPanelInAddressRow + case connectionPanelOutAddressRow + case customSwitch + case dnsContentBlockersHeaderView case loginTextField + case selectLocationSearchTextField // DNS settings case dnsSettings diff --git a/ios/MullvadVPN/View controllers/Preferences/CustomDNSDataSource.swift b/ios/MullvadVPN/View controllers/Preferences/CustomDNSDataSource.swift index ea7217b1eacc..afed9c584597 100644 --- a/ios/MullvadVPN/View controllers/Preferences/CustomDNSDataSource.swift +++ b/ios/MullvadVPN/View controllers/Preferences/CustomDNSDataSource.swift @@ -589,6 +589,7 @@ final class CustomDNSDataSource: UITableViewDiffableDataSource< header.titleLabel.text = title header.accessibilityCustomActionName = title + header.accessibilityIdentifier = .dnsContentBlockersHeaderView header.infoButtonHandler = { [weak self] in self?.delegate?.showInfo(for: .contentBlockers) diff --git a/ios/MullvadVPN/View controllers/SelectLocation/SelectLocationViewController.swift b/ios/MullvadVPN/View controllers/SelectLocation/SelectLocationViewController.swift index b19fef0df605..47e9a4309f38 100644 --- a/ios/MullvadVPN/View controllers/SelectLocation/SelectLocationViewController.swift +++ b/ios/MullvadVPN/View controllers/SelectLocation/SelectLocationViewController.swift @@ -133,6 +133,7 @@ final class SelectLocationViewController: UIViewController { tableView.estimatedRowHeight = 53 tableView.indicatorStyle = .white tableView.keyboardDismissMode = .onDrag + tableView.accessibilityIdentifier = .selectLocationTableView } private func setUpTopContent() { @@ -167,6 +168,7 @@ final class SelectLocationViewController: UIViewController { value: "Search for...", comment: "" ) + searchBar.searchTextField.accessibilityIdentifier = .selectLocationSearchTextField UITextField.SearchTextFieldAppearance.inactive.apply(to: searchBar) } diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift b/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift index 7ac716784061..13f6f6d1e2a0 100644 --- a/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift +++ b/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift @@ -54,6 +54,7 @@ class SettingsViewController: UITableViewController, SettingsDataSourceDelegate }) ) + tableView.accessibilityIdentifier = .settingsTableView tableView.backgroundColor = .secondaryColor tableView.separatorColor = .secondaryColor tableView.rowHeight = UITableView.automaticDimension diff --git a/ios/MullvadVPN/View controllers/Tunnel/ConnectionPanelView.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionPanelView.swift index 7908906d3b66..2e975c9c3899 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/ConnectionPanelView.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionPanelView.swift @@ -75,6 +75,9 @@ class ConnectionPanelView: UIView { inAddressRow.translatesAutoresizingMaskIntoConstraints = false outAddressRow.translatesAutoresizingMaskIntoConstraints = false + inAddressRow.accessibilityIdentifier = .connectionPanelInAddressRow + outAddressRow.accessibilityIdentifier = .connectionPanelOutAddressRow + inAddressRow.title = NSLocalizedString( "IN_ADDRESS_LABEL", tableName: "ConnectionPanel", @@ -285,6 +288,8 @@ class ConnectionPanelCollapseButton: CustomButton { imageAlignment = .trailing inlineImageSpacing = 0 + accessibilityIdentifier = .connectionPanelButton + updateButtonImage() } diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift index 75d2b012c8d6..e1f393b1b78a 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift @@ -118,6 +118,7 @@ final class TunnelControlView: UIView { backgroundColor = .clear directionalLayoutMargins = UIMetrics.contentLayoutMargins accessibilityContainerType = .semanticGroup + accessibilityIdentifier = .tunnelControlView addSubviews() addButtonHandlers() diff --git a/ios/MullvadVPN/Views/CustomSwitch.swift b/ios/MullvadVPN/Views/CustomSwitch.swift index 4596cb56b6d8..a696d52142b4 100644 --- a/ios/MullvadVPN/Views/CustomSwitch.swift +++ b/ios/MullvadVPN/Views/CustomSwitch.swift @@ -31,6 +31,8 @@ class CustomSwitch: UISwitch { onTintColor = .clear overrideUserInterfaceStyle = .light + accessibilityIdentifier = .customSwitch + updateThumbColor(isOn: isOn, animated: false) addTarget(self, action: #selector(valueChanged(_:)), for: .valueChanged) diff --git a/ios/MullvadVPNUITests/BaseUITestCase.swift b/ios/MullvadVPNUITests/BaseUITestCase.swift index 7ca4356e0345..83df4591cff3 100644 --- a/ios/MullvadVPNUITests/BaseUITestCase.swift +++ b/ios/MullvadVPNUITests/BaseUITestCase.swift @@ -10,10 +10,31 @@ import Foundation import XCTest class BaseUITestCase: XCTestCase { + public static let defaultTimeout = 10.0 + // swiftlint:disable force_cast - let noTimeAccountNumber = Bundle(for: AccountTests.self).infoDictionary?["MullvadNoTimeAccountNumber"] as! String - let hasTimeAccountNumber = Bundle(for: AccountTests.self).infoDictionary?["MullvadHasTimeAccountNumber"] as! String - let fiveWireGuardKeysAccountNumber = Bundle(for: AccountTests.self) + let noTimeAccountNumber = Bundle(for: BaseUITestCase.self) + .infoDictionary?["MullvadNoTimeAccountNumber"] as! String + let hasTimeAccountNumber = Bundle(for: BaseUITestCase.self) + .infoDictionary?["MullvadHasTimeAccountNumber"] as! String + let fiveWireGuardKeysAccountNumber = Bundle(for: BaseUITestCase.self) .infoDictionary?["MullvadFiveWireGuardKeysAccountNumber"] as! String + let iOSDevicePinCode = Bundle(for: BaseUITestCase.self) + .infoDictionary?["MullvadIOSDevicePinCode"] as! String + let adServingDomain = Bundle(for: BaseUITestCase.self) + .infoDictionary?["MullvadAdServingDomain"] as! String // swiftlint:enable force_cast + + /// Handle iOS add VPN configuration permission alert - allow and enter device PIN code + func allowAddVPNConfigurations() { + let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") + + let alertAllowButton = springboard.buttons.element(boundBy: 0) + if alertAllowButton.waitForExistence(timeout: Self.defaultTimeout) { + alertAllowButton.tap() + } + + _ = springboard.buttons["1"].waitForExistence(timeout: Self.defaultTimeout) + springboard.typeText(iOSDevicePinCode) + } } diff --git a/ios/MullvadVPNUITests/Info.plist b/ios/MullvadVPNUITests/Info.plist index f0e3228b4b32..568e195a8f44 100644 --- a/ios/MullvadVPNUITests/Info.plist +++ b/ios/MullvadVPNUITests/Info.plist @@ -2,10 +2,14 @@ + MullvadAdServingDomain + $(MULLVAD_AD_SERVING_DOMAIN) MullvadFiveWireGuardKeysAccountNumber $(MULLVAD_FIVE_WIREGUARD_KEYS_ACCOUNT_NUMBER) MullvadHasTimeAccountNumber $(MULLVAD_HAS_TIME_ACCOUNT_NUMBER) + MullvadIOSDevicePinCode + $(MULLVAD_IOS_DEVICE_PIN_CODE) MullvadNoTimeAccountNumber $(MULLVAD_NO_TIME_ACCOUNT_NUMBER) diff --git a/ios/MullvadVPNUITests/Pages/Alert.swift b/ios/MullvadVPNUITests/Pages/Alert.swift index 7ecfb59e259a..dce7c3bf11c1 100644 --- a/ios/MullvadVPNUITests/Pages/Alert.swift +++ b/ios/MullvadVPNUITests/Pages/Alert.swift @@ -19,7 +19,7 @@ class Alert: Page { } @discardableResult func tapOkay() -> Self { - app.buttons[AccessibilityIdentifier.alertOkButton.rawValue].tap() + app.buttons[AccessibilityIdentifier.alertOkButton].tap() return self } } diff --git a/ios/MullvadVPNUITests/Pages/LoginPage.swift b/ios/MullvadVPNUITests/Pages/LoginPage.swift index 7d13d880884d..18a44b5e74f7 100644 --- a/ios/MullvadVPNUITests/Pages/LoginPage.swift +++ b/ios/MullvadVPNUITests/Pages/LoginPage.swift @@ -18,19 +18,19 @@ class LoginPage: Page { } @discardableResult public func tapAccountNumberTextField() -> Self { - app.textFields[AccessibilityIdentifier.loginTextField.rawValue].tap() + app.textFields[AccessibilityIdentifier.loginTextField].tap() return self } @discardableResult public func tapAccountNumberSubmitButton() -> Self { - app.buttons[AccessibilityIdentifier.loginTextFieldButton.rawValue].tap() + app.buttons[AccessibilityIdentifier.loginTextFieldButton].tap() return self } @discardableResult public func verifyDeviceLabelShown() -> Self { XCTAssertTrue( - app.staticTexts[AccessibilityIdentifier.headerDeviceNameLabel.rawValue] - .waitForExistence(timeout: defaultTimeout) + app.staticTexts[AccessibilityIdentifier.headerDeviceNameLabel] + .waitForExistence(timeout: BaseUITestCase.defaultTimeout) ) return self diff --git a/ios/MullvadVPNUITests/Pages/Page.swift b/ios/MullvadVPNUITests/Pages/Page.swift index 775edf0beb70..6d39ba680501 100644 --- a/ios/MullvadVPNUITests/Pages/Page.swift +++ b/ios/MullvadVPNUITests/Pages/Page.swift @@ -12,23 +12,22 @@ import XCTest class Page { let app: XCUIApplication var pageAccessibilityIdentifier: AccessibilityIdentifier? - let defaultTimeout = 10.0 - init(_ app: XCUIApplication) { + @discardableResult init(_ app: XCUIApplication) { self.app = app } - public func enterText(_ text: String) -> Self { - app.typeText(text) - return self - } - public func waitForPageToBeShown() { if let pageAccessibilityIdentifier = self.pageAccessibilityIdentifier { XCTAssert( - self.app.otherElements[pageAccessibilityIdentifier.rawValue] - .waitForExistence(timeout: defaultTimeout) + self.app.otherElements[pageAccessibilityIdentifier] + .waitForExistence(timeout: BaseUITestCase.defaultTimeout) ) } } + + @discardableResult public func enterText(_ text: String) -> Self { + app.typeText(text) + return self + } } diff --git a/ios/MullvadVPNUITests/Pages/SelectLocationPage.swift b/ios/MullvadVPNUITests/Pages/SelectLocationPage.swift new file mode 100644 index 000000000000..f841101ff630 --- /dev/null +++ b/ios/MullvadVPNUITests/Pages/SelectLocationPage.swift @@ -0,0 +1,34 @@ +// +// SelectLocationPage.swift +// MullvadVPNUITests +// +// Created by Niklas Berglund on 2024-01-11. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import XCTest + +class SelectLocationPage: Page { + @discardableResult override init(_ app: XCUIApplication) { + super.init(app) + + self.pageAccessibilityIdentifier = .selectLocationView + } + + @discardableResult func tapLocationCell(withName name: String) -> Self { + app.tables[AccessibilityIdentifier.selectLocationTableView].cells.staticTexts[name].tap() + return self + } + + @discardableResult func tapLocationCellExpandButton(withName name: String) -> Self { + let table = app.tables[AccessibilityIdentifier.selectLocationTableView] + let matchingCells = table.cells.containing(.any, identifier: name) + let buttons = matchingCells.buttons + let expandButton = buttons[AccessibilityIdentifier.collapseButton] + + expandButton.tap() + + return self + } +} diff --git a/ios/MullvadVPNUITests/Pages/SettingsPage.swift b/ios/MullvadVPNUITests/Pages/SettingsPage.swift new file mode 100644 index 000000000000..c57fd3ff0421 --- /dev/null +++ b/ios/MullvadVPNUITests/Pages/SettingsPage.swift @@ -0,0 +1,50 @@ +// +// SettingsPage.swift +// MullvadVPNUITests +// +// Created by Niklas Berglund on 2024-01-12. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import XCTest + +class SettingsPage: Page { + @discardableResult override init(_ app: XCUIApplication) { + super.init(app) + + self.pageAccessibilityIdentifier = .settingsTableView + } + + @discardableResult func tapVPNSettingsCell() -> Self { + app.tables[AccessibilityIdentifier.settingsTableView] + .cells[AccessibilityIdentifier.preferencesCell] + .tap() + + return self + } + + @discardableResult func tapDNSSettingsCell() -> Self { + app.tables + .cells[AccessibilityIdentifier.dnsSettings] + .tap() + + return self + } + + @discardableResult func tapDNSContentBlockingHeaderExpandButton() -> Self { + let headerView = app.otherElements[AccessibilityIdentifier.dnsContentBlockersHeaderView] + let expandButton = headerView.buttons[AccessibilityIdentifier.collapseButton] + expandButton.tap() + + return self + } + + @discardableResult func tapBlockAdsSwitch() -> Self { + app.cells[AccessibilityIdentifier.blockAdvertising] + .switches[AccessibilityIdentifier.customSwitch] + .tap() + + return self + } +} diff --git a/ios/MullvadVPNUITests/Pages/TermsOfServicePage.swift b/ios/MullvadVPNUITests/Pages/TermsOfServicePage.swift index fb64b3237d43..b1a569290db6 100644 --- a/ios/MullvadVPNUITests/Pages/TermsOfServicePage.swift +++ b/ios/MullvadVPNUITests/Pages/TermsOfServicePage.swift @@ -17,7 +17,7 @@ class TermsOfServicePage: Page { } @discardableResult func tapAgreeButton() -> Self { - app.buttons[AccessibilityIdentifier.agreeButton.rawValue].tap() + app.buttons[AccessibilityIdentifier.agreeButton].tap() return self } } diff --git a/ios/MullvadVPNUITests/Pages/TunnelControlPage.swift b/ios/MullvadVPNUITests/Pages/TunnelControlPage.swift new file mode 100644 index 000000000000..8fbee875bfee --- /dev/null +++ b/ios/MullvadVPNUITests/Pages/TunnelControlPage.swift @@ -0,0 +1,28 @@ +// +// TunnelControlPage.swift +// MullvadVPNUITests +// +// Created by Niklas Berglund on 2024-01-11. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import XCTest + +class TunnelControlPage: Page { + @discardableResult override init(_ app: XCUIApplication) { + super.init(app) + + self.pageAccessibilityIdentifier = .tunnelControlView + } + + @discardableResult func tapSelectLocationButton() -> Self { + app.buttons[AccessibilityIdentifier.selectLocationButton].tap() + return self + } + + @discardableResult func tapSettingsButton() -> Self { + app.buttons[AccessibilityIdentifier.settingsButton].tap() + return self + } +} diff --git a/ios/MullvadVPNUITests/RelayTests.swift b/ios/MullvadVPNUITests/RelayTests.swift new file mode 100644 index 000000000000..45d3477e2cb3 --- /dev/null +++ b/ios/MullvadVPNUITests/RelayTests.swift @@ -0,0 +1,96 @@ +// +// RelayTests.swift +// MullvadVPNUITests +// +// Created by Niklas Berglund on 2024-01-11. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import XCTest + +class RelayTests: BaseUITestCase { + func testAdBlockingViaDNS() throws { + let app = XCUIApplication() + app.launch() + + TermsOfServicePage(app) + .tapAgreeButton() + + Alert(app) + .tapOkay() + + LoginPage(app) + .tapAccountNumberTextField() + .enterText(self.hasTimeAccountNumber) + .tapAccountNumberSubmitButton() + .verifySuccessIconShown() + .verifyDeviceLabelShown() + + TunnelControlPage(app) + .tapSelectLocationButton() + + SelectLocationPage(app) + .tapLocationCellExpandButton(withName: "Sweden") + .tapLocationCellExpandButton(withName: "Gothenburg") + .tapLocationCell(withName: "se-got-wg-001") + + allowAddVPNConfigurations() // Allow adding VPN configurations iOS permission + + TunnelControlPage(app) // Make sure we're taken back to tunnel control page again + + verifyCanReachAdServingDomain() + + TunnelControlPage(app) + .tapSettingsButton() + + SettingsPage(app) + .tapVPNSettingsCell() + .tapDNSSettingsCell() + .tapDNSContentBlockingHeaderExpandButton() + .tapBlockAdsSwitch() + + verifyCannotReachAdServingDomain() + } + + /// Verify that an ad serving domain is reachable by making sure the host can be found when sending HTTP request to it + func verifyCanReachAdServingDomain() { + XCTAssertTrue(canReachAdServingDomain()) + } + + /// Verify that an ad serving domain is NOT reachable by making sure the host cannot be found when sending HTTP request to it + func verifyCannotReachAdServingDomain() { + XCTAssertFalse(canReachAdServingDomain()) + } + + /// Attempt to reach HTTP server on an ad serving domain + /// - Returns: `true` if host can be resolved, otherwise `false` + private func canReachAdServingDomain() -> Bool { + guard let url = URL(string: "http://\(adServingDomain)") else { return false } + + var requestError: Error? + var requestResponse: URLResponse? + + let completionHandlerInvokedExpectation = expectation( + description: "Completion handler for the request is invoked" + ) + + let task = URLSession.shared.dataTask(with: url) { _, response, error in + requestError = error + requestResponse = response + completionHandlerInvokedExpectation.fulfill() + } + + task.resume() + + wait(for: [completionHandlerInvokedExpectation], timeout: 30) + + if let urlError = requestError as? URLError { + if urlError.code == .cannotFindHost && requestResponse == nil { + return false + } + } + + return true + } +} diff --git a/ios/MullvadVPNUITests/XCUIElementQuery+Extensions.swift b/ios/MullvadVPNUITests/XCUIElementQuery+Extensions.swift new file mode 100644 index 000000000000..8500d7c08c2c --- /dev/null +++ b/ios/MullvadVPNUITests/XCUIElementQuery+Extensions.swift @@ -0,0 +1,16 @@ +// +// XCUIElementQuery+Extensions.swift +// MullvadVPNUITests +// +// Created by Niklas Berglund on 2024-01-19. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import XCTest + +extension XCUIElementQuery { + subscript(key: any RawRepresentable) -> XCUIElement { + self[key.rawValue] + } +}