Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WireGuard tests for iOS app #5955

Merged
merged 1 commit into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ios/Configurations/UITests.xcconfig.template
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ FIVE_WIREGUARD_KEYS_ACCOUNT_NUMBER =

// Ad serving domain used when testing ad blocking. Note that we are assuming there's an HTTP server running on the host.
AD_SERVING_DOMAIN = vpnlist.to

// A domain which should be reachable. Used to verify Internet connectivity. Must be running a server on port 80.
SHOULD_BE_REACHABLE_DOMAIN = mullvad.net

// Base URL for the firewall API, Note that // will be treated as a comment, therefor you need to insert a ${} between the slashes for example http:/${}/8.8.8.8
FIREWALL_API_BASE_URL = http:/${}/8.8.8.8
4 changes: 4 additions & 0 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@
8529693A2B4F0238007EAD4C /* TermsOfServicePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852969392B4F0238007EAD4C /* TermsOfServicePage.swift */; };
8529693C2B4F0257007EAD4C /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8529693B2B4F0257007EAD4C /* Alert.swift */; };
8532E6872B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8532E6862B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift */; };
8542CE242B95F7B9006FCA14 /* VPNSettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */; };
85557B0E2B591B2600795FE1 /* FirewallAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B0D2B591B2600795FE1 /* FirewallAPIClient.swift */; };
85557B102B59215F00795FE1 /* FirewallRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B0F2B59215F00795FE1 /* FirewallRule.swift */; };
85557B122B594FC900795FE1 /* ConnectivityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B112B594FC900795FE1 /* ConnectivityTests.swift */; };
Expand Down Expand Up @@ -1861,6 +1862,7 @@
852969392B4F0238007EAD4C /* TermsOfServicePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsOfServicePage.swift; sourceTree = "<group>"; };
8529693B2B4F0257007EAD4C /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.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>"; };
85557B0D2B591B2600795FE1 /* FirewallAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirewallAPIClient.swift; sourceTree = "<group>"; };
85557B0F2B59215F00795FE1 /* FirewallRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirewallRule.swift; sourceTree = "<group>"; };
85557B112B594FC900795FE1 /* ConnectivityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3609,6 +3611,7 @@
852969392B4F0238007EAD4C /* TermsOfServicePage.swift */,
850201DE2B5040A500EF8C96 /* TunnelControlPage.swift */,
85FB5A0B2B6903990015DCED /* WelcomePage.swift */,
8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */,
);
path = Pages;
sourceTree = "<group>";
Expand Down Expand Up @@ -5598,6 +5601,7 @@
8532E6872B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift in Sources */,
85FB5A0C2B6903990015DCED /* WelcomePage.swift in Sources */,
850201DF2B5040A500EF8C96 /* TunnelControlPage.swift in Sources */,
8542CE242B95F7B9006FCA14 /* VPNSettingsPage.swift in Sources */,
85557B1E2B5FB8C700795FE1 /* HeaderBar.swift in Sources */,
85557B122B594FC900795FE1 /* ConnectivityTests.swift in Sources */,
852969332B4E9232007EAD4C /* Page.swift in Sources */,
Expand Down
10 changes: 10 additions & 0 deletions ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public enum AccessibilityIdentifier: String {
case startUsingTheAppButton
case problemReportAppLogsButton
case problemReportSendButton
case relayStatusCollapseButton
case settingsDoneButton

// Cells
case vpnSettingsCell
Expand All @@ -44,11 +46,16 @@ public enum AccessibilityIdentifier: String {
case apiAccessCell
case relayFilterOwnershipCell
case relayFilterProviderCell
case wireGuardPortsCell
case wireGuardObfuscationCell
case udpOverTCPPortCell
case quantumResistantTunnelCell

// Labels
case headerDeviceNameLabel
case connectionStatusLabel
case welcomeAccountNumberLabel
case connectionPanelDetailLabel

// Views
case accountView
Expand All @@ -62,17 +69,20 @@ public enum AccessibilityIdentifier: String {
case selectLocationView
case selectLocationTableView
case settingsTableView
case vpnSettingsTableView
case tunnelControlView
case problemReportView
case problemReportSubmittedView
case revokedDeviceView
case welcomeView
case deleteAccountView
case settingsContainerView

// Other UI elements
case connectionPanelInAddressRow
case connectionPanelOutAddressRow
case customSwitch
case customWireGuardPortTextField
case dnsContentBlockersHeaderView
case loginTextField
case selectLocationSearchTextField
Expand Down
1 change: 1 addition & 0 deletions ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
)

let navigationController = CustomNavigationController()
navigationController.view.accessibilityIdentifier = .settingsContainerView

let configurationTester = ProxyConfigurationTester(transportProvider: configuredTransportProvider)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,17 @@ class SettingsViewController: UITableViewController, SettingsDataSourceDelegate
value: "Settings",
comment: ""
)
navigationItem.rightBarButtonItem = UIBarButtonItem(

let doneButton = UIBarButtonItem(
systemItem: .done,
primaryAction: UIAction(handler: { [weak self] _ in
guard let self else { return }

delegate?.settingsViewControllerDidFinish(self)
})
)
doneButton.accessibilityIdentifier = .settingsDoneButton
navigationItem.rightBarButtonItem = doneButton

tableView.accessibilityIdentifier = .settingsTableView
tableView.backgroundColor = .secondaryColor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class ConnectionPanelView: UIView {

var connectedRelayName = "" {
didSet {
collapseButton.accessibilityIdentifier = .relayStatusCollapseButton
collapseButton.setTitle(connectedRelayName, for: .normal)
collapseButton.accessibilityLabel = NSLocalizedString(
"RELAY_ACCESSIBILITY_LABEL",
Expand Down Expand Up @@ -185,6 +186,7 @@ class ConnectionPanelAddressRow: UIView {

private let detailTextLabel: UILabel = {
let detailTextLabel = UILabel()
detailTextLabel.accessibilityIdentifier = .connectionPanelDetailLabel
detailTextLabel.font = .systemFont(ofSize: 17)
detailTextLabel.textColor = .white
detailTextLabel.translatesAutoresizingMaskIntoConstraints = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
comment: ""
)

cell.textField.accessibilityIdentifier = .customWireGuardPortTextField
cell.accessibilityIdentifier = item.accessibilityIdentifier
cell.applySubCellStyling()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
comment: ""
)

header.accessibilityIdentifier = .wireGuardPortsCell
header.titleLabel.text = title
header.accessibilityCustomActionName = title
header.infoButtonHandler = { [weak self] in
Expand Down Expand Up @@ -488,6 +489,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
comment: ""
)

header.accessibilityIdentifier = .wireGuardObfuscationCell
header.titleLabel.text = title
header.accessibilityCustomActionName = title
header.didCollapseHandler = { [weak self] header in
Expand Down Expand Up @@ -516,6 +518,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
comment: ""
)

header.accessibilityIdentifier = .udpOverTCPPortCell
header.titleLabel.text = title
header.accessibilityCustomActionName = title
header.didCollapseHandler = { [weak self] header in
Expand Down Expand Up @@ -545,6 +548,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
comment: ""
)

header.accessibilityIdentifier = .quantumResistantTunnelCell
header.titleLabel.text = title
header.accessibilityCustomActionName = title
header.didCollapseHandler = { [weak self] header in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class VPNSettingsViewController: UITableViewController, VPNSettingsDataSourceDel
override func viewDidLoad() {
super.viewDidLoad()

tableView.accessibilityIdentifier = .vpnSettingsTableView
tableView.backgroundColor = .secondaryColor
tableView.separatorColor = .secondaryColor
tableView.rowHeight = UITableView.automaticDimension
Expand Down
2 changes: 2 additions & 0 deletions ios/MullvadVPNUITests/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
<string>$(IOS_DEVICE_PIN_CODE)</string>
<key>NoTimeAccountNumber</key>
<string>$(NO_TIME_ACCOUNT_NUMBER)</string>
<key>ShouldBeReachableDomain</key>
<string>$(SHOULD_BE_REACHABLE_DOMAIN)</string>
<key>TestDeviceIdentifier</key>
<string>$(TEST_DEVICE_IDENTIFIER_UUID</string>
</dict>
Expand Down
2 changes: 1 addition & 1 deletion ios/MullvadVPNUITests/Networking/FirewallAPIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class FirewallAPIClient {
"label": sessionIdentifier,
"from": firewallRule.fromIPAddress,
"to": firewallRule.toIPAddress,
"protocols": firewallRule.protocolsAsStringArray(),
]

var requestError: Error?
Expand Down Expand Up @@ -80,7 +81,6 @@ class FirewallAPIClient {

var request = URLRequest(url: removeRulesURL)
request.httpMethod = "DELETE"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")

var requestResponse: URLResponse?
var requestError: Error?
Expand Down
16 changes: 15 additions & 1 deletion ios/MullvadVPNUITests/Networking/FirewallRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,28 @@ struct FirewallRule {
self.protocols = protocols
}

public func protocolsAsStringArray() -> [String] {
return protocols.map { $0.rawValue }
}

/// Make a firewall rule blocking API access for the current device under test
public static func makeBlockAPIAccessFirewallRule() throws -> FirewallRule {
let deviceIPAddress = try Networking.getIPAddress()
let apiIPAddress = try MullvadAPIWrapper.getAPIIPAddress()
return FirewallRule(
fromIPAddress: deviceIPAddress,
toIPAddress: apiIPAddress,
protocols: [NetworkingProtocol.TCP]
protocols: [.TCP]
)
}

public static func makeBlockUDPTrafficRule(toIPAddress: String) throws -> FirewallRule {
let deviceIPAddress = try Networking.getIPAddress()

return FirewallRule(
fromIPAddress: deviceIPAddress,
toIPAddress: toIPAddress,
protocols: [.UDP]
)
}
}
32 changes: 21 additions & 11 deletions ios/MullvadVPNUITests/Networking/Networking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,24 +63,24 @@ class Networking {
throw NetworkingError.internalError(reason: "Failed to determine device's IP address")
}

private static func getAdServingDomainURL() -> URL? {
guard let adServingDomain = Bundle(for: BaseUITestCase.self)
.infoDictionary?["AdServingDomain"] as? String,
let adServingDomainURL = URL(string: adServingDomain) else {
XCTFail("Ad serving domain not configured")
return nil
/// Get configured ad serving domain
private static func getAdServingDomain() throws -> String {
guard let adServingDomain = Bundle(for: Networking.self)
.infoDictionary?["AdServingDomain"] as? String else {
throw NetworkingError.notConfiguredError
}

return adServingDomainURL
return adServingDomain
}

private static func getAdServingDomain() throws -> String {
guard let adServingDomain = Bundle(for: BaseUITestCase.self)
.infoDictionary?["AdServingDomain"] as? String else {
/// Get configured domain to use for Internet connectivity checks
private static func getAlwaysReachableDomain() throws -> String {
guard let shouldBeReachableDomain = Bundle(for: Networking.self)
.infoDictionary?["ShouldBeReachableDomain"] as? String else {
throw NetworkingError.notConfiguredError
}

return adServingDomain
return shouldBeReachableDomain
}

/// Check whether host and port is reachable by attempting to connect a socket
Expand Down Expand Up @@ -134,6 +134,16 @@ class Networking {
XCTAssertFalse(try canConnectSocket(host: apiIPAddress, port: apiPort))
}

/// Verify that the device has Internet connectivity
public static func verifyCanAccessInternet() throws {
XCTAssertTrue(try canConnectSocket(host: getAlwaysReachableDomain(), port: "80"))
}

/// Verify that the device does not have Internet connectivity
public static func verifyCannotAccessInternet() throws {
XCTAssertFalse(try canConnectSocket(host: getAlwaysReachableDomain(), port: "80"))
}

/// Verify that an ad serving domain is reachable by making sure a connection can be established on port 80
public static func verifyCanReachAdServingDomain() throws {
XCTAssertTrue(try Self.canConnectSocket(host: try Self.getAdServingDomain(), port: "80"))
Expand Down
5 changes: 5 additions & 0 deletions ios/MullvadVPNUITests/Pages/Page.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ class Page {
return self
}

@discardableResult func dismissKeyboard() -> Self {
self.enterText("\n")
return self
}

/// Fast swipe down action to dismiss a modal view. Will swipe on the middle of the screen.
@discardableResult func swipeDownToDismissModal() -> Self {
app.swipeDown(velocity: .fast)
Expand Down
2 changes: 1 addition & 1 deletion ios/MullvadVPNUITests/Pages/SelectLocationPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class SelectLocationPage: Page {
return self
}

@discardableResult func tapLocationCellExpandButton(withName name: String) -> Self {
@discardableResult func tapLocationCellExpandCollapseButton(withName name: String) -> Self {
let table = app.tables[AccessibilityIdentifier.selectLocationTableView]
let matchingCells = table.cells.containing(.any, identifier: name)
let buttons = matchingCells.buttons
Expand Down
9 changes: 8 additions & 1 deletion ios/MullvadVPNUITests/Pages/SettingsPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,17 @@ class SettingsPage: Page {
@discardableResult override init(_ app: XCUIApplication) {
super.init(app)

self.pageAccessibilityIdentifier = .settingsTableView
self.pageAccessibilityIdentifier = .settingsContainerView
waitForPageToBeShown()
}

@discardableResult func tapDoneButton() -> Self {
app.buttons[AccessibilityIdentifier.settingsDoneButton]
.tap()

return self
}

@discardableResult func tapVPNSettingsCell() -> Self {
app.tables[AccessibilityIdentifier.settingsTableView]
.cells[AccessibilityIdentifier.vpnSettingsCell]
Expand Down
Loading
Loading