Skip to content

Commit fddf8d5

Browse files
Add WireGuard tests for iOS app
1 parent 75c0537 commit fddf8d5

19 files changed

+407
-5
lines changed

ios/Configurations/UITests.xcconfig.template

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ FIVE_WIREGUARD_KEYS_ACCOUNT_NUMBER =
1515

1616
// Ad serving domain used when testing ad blocking. Note that we are assuming there's an HTTP server running on the host.
1717
AD_SERVING_DOMAIN = vpnlist.to
18+
19+
// A domain which should be reachable. Used to verify Internet connectivity. Must be running a server on port 80.
20+
SHOULD_BE_REACHABLE_DOMAIN = mullvad.net
1821

1922
// 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
2023
FIREWALL_API_BASE_URL = http:/${}/8.8.8.8

ios/MullvadVPN.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,7 @@
603603
8529693A2B4F0238007EAD4C /* TermsOfServicePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852969392B4F0238007EAD4C /* TermsOfServicePage.swift */; };
604604
8529693C2B4F0257007EAD4C /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8529693B2B4F0257007EAD4C /* Alert.swift */; };
605605
8532E6872B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8532E6862B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift */; };
606+
8542CE242B95F7B9006FCA14 /* VPNSettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */; };
606607
85557B0E2B591B2600795FE1 /* FirewallAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B0D2B591B2600795FE1 /* FirewallAPIClient.swift */; };
607608
85557B102B59215F00795FE1 /* FirewallRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B0F2B59215F00795FE1 /* FirewallRule.swift */; };
608609
85557B122B594FC900795FE1 /* ConnectivityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B112B594FC900795FE1 /* ConnectivityTests.swift */; };
@@ -1828,6 +1829,7 @@
18281829
852969392B4F0238007EAD4C /* TermsOfServicePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsOfServicePage.swift; sourceTree = "<group>"; };
18291830
8529693B2B4F0257007EAD4C /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = "<group>"; };
18301831
8532E6862B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportSubmittedPage.swift; sourceTree = "<group>"; };
1832+
8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNSettingsPage.swift; sourceTree = "<group>"; };
18311833
85557B0D2B591B2600795FE1 /* FirewallAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirewallAPIClient.swift; sourceTree = "<group>"; };
18321834
85557B0F2B59215F00795FE1 /* FirewallRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirewallRule.swift; sourceTree = "<group>"; };
18331835
85557B112B594FC900795FE1 /* ConnectivityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityTests.swift; sourceTree = "<group>"; };
@@ -3536,6 +3538,7 @@
35363538
850201E22B51A93C00EF8C96 /* SettingsPage.swift */,
35373539
852969392B4F0238007EAD4C /* TermsOfServicePage.swift */,
35383540
850201DE2B5040A500EF8C96 /* TunnelControlPage.swift */,
3541+
8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */,
35393542
);
35403543
path = Pages;
35413544
sourceTree = "<group>";
@@ -5500,6 +5503,7 @@
55005503
8529693A2B4F0238007EAD4C /* TermsOfServicePage.swift in Sources */,
55015504
8532E6872B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift in Sources */,
55025505
850201DF2B5040A500EF8C96 /* TunnelControlPage.swift in Sources */,
5506+
8542CE242B95F7B9006FCA14 /* VPNSettingsPage.swift in Sources */,
55035507
85557B1E2B5FB8C700795FE1 /* HeaderBar.swift in Sources */,
55045508
85557B122B594FC900795FE1 /* ConnectivityTests.swift in Sources */,
55055509
852969332B4E9232007EAD4C /* Page.swift in Sources */,

ios/MullvadVPN/Classes/AccessbilityIdentifier.swift

+10
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public enum AccessibilityIdentifier: String {
3333
case startUsingTheAppButton
3434
case problemReportAppLogsButton
3535
case problemReportSendButton
36+
case relayStatusCollapseButton
37+
case settingsDoneButton
3638

3739
// Cells
3840
case preferencesCell
@@ -43,10 +45,15 @@ public enum AccessibilityIdentifier: String {
4345
case ipOverrideCell
4446
case relayFilterOwnershipCell
4547
case relayFilterProviderCell
48+
case wireGuardPortsCell
49+
case wireGuardObfuscationCell
50+
case udpOverTCPPortCell
51+
case quantumResistantTunnelCell
4652

4753
// Labels
4854
case headerDeviceNameLabel
4955
case connectionStatusLabel
56+
case connectionPanelDetailLabel
5057

5158
// Views
5259
case accountView
@@ -58,14 +65,17 @@ public enum AccessibilityIdentifier: String {
5865
case selectLocationView
5966
case selectLocationTableView
6067
case settingsTableView
68+
case vpnSettingsTableView
6169
case tunnelControlView
6270
case problemReportView
6371
case problemReportSubmittedView
72+
case settingsContainerView
6473

6574
// Other UI elements
6675
case connectionPanelInAddressRow
6776
case connectionPanelOutAddressRow
6877
case customSwitch
78+
case customWireGuardPortTextField
6979
case dnsContentBlockersHeaderView
7080
case loginTextField
7181
case selectLocationSearchTextField

ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift

+1
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
767767
)
768768

769769
let navigationController = CustomNavigationController()
770+
navigationController.view.accessibilityIdentifier = .settingsContainerView
770771

771772
let configurationTester = ProxyConfigurationTester(transportProvider: configuredTransportProvider)
772773

ios/MullvadVPN/View controllers/Preferences/PreferencesCellFactory.swift

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ final class PreferencesCellFactory: CellFactoryProtocol {
8484
comment: ""
8585
)
8686

87+
cell.textField.accessibilityIdentifier = .customWireGuardPortTextField
8788
cell.accessibilityIdentifier = item.accessibilityIdentifier
8889
cell.applySubCellStyling()
8990

ios/MullvadVPN/View controllers/Preferences/PreferencesDataSource.swift

+4
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ final class PreferencesDataSource: UITableViewDiffableDataSource<
432432
comment: ""
433433
)
434434

435+
header.accessibilityIdentifier = .wireGuardPortsCell
435436
header.titleLabel.text = title
436437
header.accessibilityCustomActionName = title
437438
header.infoButtonHandler = { [weak self] in
@@ -474,6 +475,7 @@ final class PreferencesDataSource: UITableViewDiffableDataSource<
474475
comment: ""
475476
)
476477

478+
header.accessibilityIdentifier = .wireGuardObfuscationCell
477479
header.titleLabel.text = title
478480
header.accessibilityCustomActionName = title
479481
header.didCollapseHandler = { [weak self] header in
@@ -502,6 +504,7 @@ final class PreferencesDataSource: UITableViewDiffableDataSource<
502504
comment: ""
503505
)
504506

507+
header.accessibilityIdentifier = .udpOverTCPPortCell
505508
header.titleLabel.text = title
506509
header.accessibilityCustomActionName = title
507510
header.didCollapseHandler = { [weak self] header in
@@ -531,6 +534,7 @@ final class PreferencesDataSource: UITableViewDiffableDataSource<
531534
comment: ""
532535
)
533536

537+
header.accessibilityIdentifier = .quantumResistantTunnelCell
534538
header.titleLabel.text = title
535539
header.accessibilityCustomActionName = title
536540
header.didCollapseHandler = { [weak self] header in

ios/MullvadVPN/View controllers/Preferences/PreferencesViewController.swift

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class PreferencesViewController: UITableViewController, PreferencesDataSourceDel
3232
override func viewDidLoad() {
3333
super.viewDidLoad()
3434

35+
tableView.accessibilityIdentifier = .vpnSettingsTableView
3536
tableView.backgroundColor = .secondaryColor
3637
tableView.separatorColor = .secondaryColor
3738
tableView.rowHeight = UITableView.automaticDimension

ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,17 @@ class SettingsViewController: UITableViewController, SettingsDataSourceDelegate
4545
value: "Settings",
4646
comment: ""
4747
)
48-
navigationItem.rightBarButtonItem = UIBarButtonItem(
48+
49+
let doneButton = UIBarButtonItem(
4950
systemItem: .done,
5051
primaryAction: UIAction(handler: { [weak self] _ in
5152
guard let self else { return }
5253

5354
delegate?.settingsViewControllerDidFinish(self)
5455
})
5556
)
57+
doneButton.accessibilityIdentifier = .settingsDoneButton
58+
navigationItem.rightBarButtonItem = doneButton
5659

5760
tableView.accessibilityIdentifier = .settingsTableView
5861
tableView.backgroundColor = .secondaryColor

ios/MullvadVPN/View controllers/Tunnel/ConnectionPanelView.swift

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class ConnectionPanelView: UIView {
2929

3030
var connectedRelayName = "" {
3131
didSet {
32+
collapseButton.accessibilityIdentifier = .relayStatusCollapseButton
3233
collapseButton.setTitle(connectedRelayName, for: .normal)
3334
collapseButton.accessibilityLabel = NSLocalizedString(
3435
"RELAY_ACCESSIBILITY_LABEL",
@@ -185,6 +186,7 @@ class ConnectionPanelAddressRow: UIView {
185186

186187
private let detailTextLabel: UILabel = {
187188
let detailTextLabel = UILabel()
189+
detailTextLabel.accessibilityIdentifier = .connectionPanelDetailLabel
188190
detailTextLabel.font = .systemFont(ofSize: 17)
189191
detailTextLabel.textColor = .white
190192
detailTextLabel.translatesAutoresizingMaskIntoConstraints = false

ios/MullvadVPNUITests/Info.plist

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
<string>$(IOS_DEVICE_PIN_CODE)</string>
2121
<key>NoTimeAccountNumber</key>
2222
<string>$(NO_TIME_ACCOUNT_NUMBER)</string>
23+
<key>ShouldBeReachableDomain</key>
24+
<string>$(SHOULD_BE_REACHABLE_DOMAIN)</string>
2325
<key>TestDeviceIdentifier</key>
2426
<string>$(TEST_DEVICE_IDENTIFIER_UUID</string>
2527
</dict>

ios/MullvadVPNUITests/Networking/FirewallAPIClient.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class FirewallAPIClient {
3434
"label": sessionIdentifier,
3535
"from": firewallRule.fromIPAddress,
3636
"to": firewallRule.toIPAddress,
37+
"protocols": firewallRule.protocolsAsStringArray(),
3738
]
3839

3940
var requestError: Error?
@@ -80,7 +81,6 @@ class FirewallAPIClient {
8081

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

8585
var requestResponse: URLResponse?
8686
var requestError: Error?

ios/MullvadVPNUITests/Networking/FirewallRule.swift

+15-1
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,28 @@ struct FirewallRule {
3030
self.protocols = protocols
3131
}
3232

33+
public func protocolsAsStringArray() -> [String] {
34+
return protocols.map { $0.rawValue }
35+
}
36+
3337
/// Make a firewall rule blocking API access for the current device under test
3438
public static func makeBlockAPIAccessFirewallRule() throws -> FirewallRule {
3539
let deviceIPAddress = try Networking.getIPAddress()
3640
let apiIPAddress = try MullvadAPIWrapper.getAPIIPAddress()
3741
return FirewallRule(
3842
fromIPAddress: deviceIPAddress,
3943
toIPAddress: apiIPAddress,
40-
protocols: [NetworkingProtocol.TCP]
44+
protocols: [.TCP]
45+
)
46+
}
47+
48+
public static func makeBlockUDPTrafficRule(toIPAddress: String) throws -> FirewallRule {
49+
let deviceIPAddress = try Networking.getIPAddress()
50+
51+
return FirewallRule(
52+
fromIPAddress: deviceIPAddress,
53+
toIPAddress: toIPAddress,
54+
protocols: [.UDP]
4155
)
4256
}
4357
}

ios/MullvadVPNUITests/Networking/Networking.swift

+23-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class Networking {
6363
throw NetworkingError.internalError(reason: "Failed to determine device's IP address")
6464
}
6565

66+
/// Get configured ad serving domain as URL object
6667
private static func getAdServingDomainURL() -> URL? {
6768
guard let adServingDomain = Bundle(for: BaseUITestCase.self)
6869
.infoDictionary?["AdServingDomain"] as? String,
@@ -74,15 +75,26 @@ class Networking {
7475
return adServingDomainURL
7576
}
7677

78+
/// Get configured ad serving domain
7779
private static func getAdServingDomain() throws -> String {
78-
guard let adServingDomain = Bundle(for: BaseUITestCase.self)
80+
guard let adServingDomain = Bundle(for: Networking.self)
7981
.infoDictionary?["AdServingDomain"] as? String else {
8082
throw NetworkingError.notConfiguredError
8183
}
8284

8385
return adServingDomain
8486
}
8587

88+
/// Get configured domain to use for Internet connectivity checks
89+
private static func getShouldBeReachableDomain() throws -> String {
90+
guard let shouldBeReachableDomain = Bundle(for: Networking.self)
91+
.infoDictionary?["ShouldBeReachableDomain"] as? String else {
92+
throw NetworkingError.notConfiguredError
93+
}
94+
95+
return shouldBeReachableDomain
96+
}
97+
8698
/// Check whether host and port is reachable by attempting to connect a socket
8799
private static func canConnectSocket(host: String, port: String) throws -> Bool {
88100
let socketHost = NWEndpoint.Host(host)
@@ -134,6 +146,16 @@ class Networking {
134146
XCTAssertFalse(try canConnectSocket(host: apiIPAddress, port: apiPort))
135147
}
136148

149+
/// Verify that the device has Internet connectivity
150+
public static func verifyCanAccessInternet() throws {
151+
XCTAssertTrue(try canConnectSocket(host: getShouldBeReachableDomain(), port: "80"))
152+
}
153+
154+
/// Verify that the device do not have Internet connectivity
155+
public static func verifyCannotAccessInternet() throws {
156+
XCTAssertFalse(try canConnectSocket(host: getShouldBeReachableDomain(), port: "80"))
157+
}
158+
137159
/// Verify that an ad serving domain is reachable by making sure a connection can be established on port 80
138160
public static func verifyCanReachAdServingDomain() throws {
139161
XCTAssertTrue(try Self.canConnectSocket(host: try Self.getAdServingDomain(), port: "80"))

ios/MullvadVPNUITests/Pages/Page.swift

+5
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ class Page {
3131
return self
3232
}
3333

34+
@discardableResult func dismissKeyboard() -> Self {
35+
self.enterText("\n")
36+
return self
37+
}
38+
3439
/// Fast swipe down action to dismiss a modal view. Will swipe on the middle of the screen.
3540
@discardableResult func swipeDownToDismissModal() -> Self {
3641
app.swipeDown(velocity: .fast)

ios/MullvadVPNUITests/Pages/SettingsPage.swift

+7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ class SettingsPage: Page {
1616
self.pageAccessibilityIdentifier = .settingsTableView
1717
}
1818

19+
@discardableResult func tapDoneButton() -> Self {
20+
app.buttons[AccessibilityIdentifier.settingsDoneButton]
21+
.tap()
22+
23+
return self
24+
}
25+
1926
@discardableResult func tapVPNSettingsCell() -> Self {
2027
app.tables[AccessibilityIdentifier.settingsTableView]
2128
.cells[AccessibilityIdentifier.preferencesCell]

0 commit comments

Comments
 (0)