Skip to content

Commit 9d7b898

Browse files
niklasberglundbuggmagnet
authored andcommitted
Add WireGuard tests for iOS app
1 parent 8bc5c13 commit 9d7b898

20 files changed

+429
-18
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
@@ -618,6 +618,7 @@
618618
8529693A2B4F0238007EAD4C /* TermsOfServicePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852969392B4F0238007EAD4C /* TermsOfServicePage.swift */; };
619619
8529693C2B4F0257007EAD4C /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8529693B2B4F0257007EAD4C /* Alert.swift */; };
620620
8532E6872B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8532E6862B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift */; };
621+
8542CE242B95F7B9006FCA14 /* VPNSettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */; };
621622
85557B0E2B591B2600795FE1 /* FirewallAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B0D2B591B2600795FE1 /* FirewallAPIClient.swift */; };
622623
85557B102B59215F00795FE1 /* FirewallRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B0F2B59215F00795FE1 /* FirewallRule.swift */; };
623624
85557B122B594FC900795FE1 /* ConnectivityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B112B594FC900795FE1 /* ConnectivityTests.swift */; };
@@ -1861,6 +1862,7 @@
18611862
852969392B4F0238007EAD4C /* TermsOfServicePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsOfServicePage.swift; sourceTree = "<group>"; };
18621863
8529693B2B4F0257007EAD4C /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = "<group>"; };
18631864
8532E6862B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportSubmittedPage.swift; sourceTree = "<group>"; };
1865+
8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNSettingsPage.swift; sourceTree = "<group>"; };
18641866
85557B0D2B591B2600795FE1 /* FirewallAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirewallAPIClient.swift; sourceTree = "<group>"; };
18651867
85557B0F2B59215F00795FE1 /* FirewallRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirewallRule.swift; sourceTree = "<group>"; };
18661868
85557B112B594FC900795FE1 /* ConnectivityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityTests.swift; sourceTree = "<group>"; };
@@ -3609,6 +3611,7 @@
36093611
852969392B4F0238007EAD4C /* TermsOfServicePage.swift */,
36103612
850201DE2B5040A500EF8C96 /* TunnelControlPage.swift */,
36113613
85FB5A0B2B6903990015DCED /* WelcomePage.swift */,
3614+
8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */,
36123615
);
36133616
path = Pages;
36143617
sourceTree = "<group>";
@@ -5598,6 +5601,7 @@
55985601
8532E6872B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift in Sources */,
55995602
85FB5A0C2B6903990015DCED /* WelcomePage.swift in Sources */,
56005603
850201DF2B5040A500EF8C96 /* TunnelControlPage.swift in Sources */,
5604+
8542CE242B95F7B9006FCA14 /* VPNSettingsPage.swift in Sources */,
56015605
85557B1E2B5FB8C700795FE1 /* HeaderBar.swift in Sources */,
56025606
85557B122B594FC900795FE1 /* ConnectivityTests.swift in Sources */,
56035607
852969332B4E9232007EAD4C /* Page.swift in Sources */,

ios/MullvadVPN/Classes/AccessbilityIdentifier.swift

+10
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public enum AccessibilityIdentifier: String {
3535
case startUsingTheAppButton
3636
case problemReportAppLogsButton
3737
case problemReportSendButton
38+
case relayStatusCollapseButton
39+
case settingsDoneButton
3840

3941
// Cells
4042
case vpnSettingsCell
@@ -44,11 +46,16 @@ public enum AccessibilityIdentifier: String {
4446
case apiAccessCell
4547
case relayFilterOwnershipCell
4648
case relayFilterProviderCell
49+
case wireGuardPortsCell
50+
case wireGuardObfuscationCell
51+
case udpOverTCPPortCell
52+
case quantumResistantTunnelCell
4753

4854
// Labels
4955
case headerDeviceNameLabel
5056
case connectionStatusLabel
5157
case welcomeAccountNumberLabel
58+
case connectionPanelDetailLabel
5259

5360
// Views
5461
case accountView
@@ -62,17 +69,20 @@ public enum AccessibilityIdentifier: String {
6269
case selectLocationView
6370
case selectLocationTableView
6471
case settingsTableView
72+
case vpnSettingsTableView
6573
case tunnelControlView
6674
case problemReportView
6775
case problemReportSubmittedView
6876
case revokedDeviceView
6977
case welcomeView
7078
case deleteAccountView
79+
case settingsContainerView
7180

7281
// Other UI elements
7382
case connectionPanelInAddressRow
7483
case connectionPanelOutAddressRow
7584
case customSwitch
85+
case customWireGuardPortTextField
7686
case dnsContentBlockersHeaderView
7787
case loginTextField
7888
case selectLocationSearchTextField

ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift

+1
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
777777
)
778778

779779
let navigationController = CustomNavigationController()
780+
navigationController.view.accessibilityIdentifier = .settingsContainerView
780781

781782
let configurationTester = ProxyConfigurationTester(transportProvider: configuredTransportProvider)
782783

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/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
9797
comment: ""
9898
)
9999

100+
cell.textField.accessibilityIdentifier = .customWireGuardPortTextField
100101
cell.accessibilityIdentifier = item.accessibilityIdentifier
101102
cell.applySubCellStyling()
102103

ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift

+4
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
446446
comment: ""
447447
)
448448

449+
header.accessibilityIdentifier = .wireGuardPortsCell
449450
header.titleLabel.text = title
450451
header.accessibilityCustomActionName = title
451452
header.infoButtonHandler = { [weak self] in
@@ -488,6 +489,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
488489
comment: ""
489490
)
490491

492+
header.accessibilityIdentifier = .wireGuardObfuscationCell
491493
header.titleLabel.text = title
492494
header.accessibilityCustomActionName = title
493495
header.didCollapseHandler = { [weak self] header in
@@ -516,6 +518,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
516518
comment: ""
517519
)
518520

521+
header.accessibilityIdentifier = .udpOverTCPPortCell
519522
header.titleLabel.text = title
520523
header.accessibilityCustomActionName = title
521524
header.didCollapseHandler = { [weak self] header in
@@ -545,6 +548,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
545548
comment: ""
546549
)
547550

551+
header.accessibilityIdentifier = .quantumResistantTunnelCell
548552
header.titleLabel.text = title
549553
header.accessibilityCustomActionName = title
550554
header.didCollapseHandler = { [weak self] header in

ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsViewController.swift

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class VPNSettingsViewController: UITableViewController, VPNSettingsDataSourceDel
3838
override func viewDidLoad() {
3939
super.viewDidLoad()
4040

41+
tableView.accessibilityIdentifier = .vpnSettingsTableView
4142
tableView.backgroundColor = .secondaryColor
4243
tableView.separatorColor = .secondaryColor
4344
tableView.rowHeight = UITableView.automaticDimension

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

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

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

74-
return adServingDomainURL
73+
return adServingDomain
7574
}
7675

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

83-
return adServingDomain
83+
return shouldBeReachableDomain
8484
}
8585

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

137+
/// Verify that the device has Internet connectivity
138+
public static func verifyCanAccessInternet() throws {
139+
XCTAssertTrue(try canConnectSocket(host: getAlwaysReachableDomain(), port: "80"))
140+
}
141+
142+
/// Verify that the device does not have Internet connectivity
143+
public static func verifyCannotAccessInternet() throws {
144+
XCTAssertFalse(try canConnectSocket(host: getAlwaysReachableDomain(), port: "80"))
145+
}
146+
137147
/// Verify that an ad serving domain is reachable by making sure a connection can be established on port 80
138148
public static func verifyCanReachAdServingDomain() throws {
139149
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/SelectLocationPage.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class SelectLocationPage: Page {
2222
return self
2323
}
2424

25-
@discardableResult func tapLocationCellExpandButton(withName name: String) -> Self {
25+
@discardableResult func tapLocationCellExpandCollapseButton(withName name: String) -> Self {
2626
let table = app.tables[AccessibilityIdentifier.selectLocationTableView]
2727
let matchingCells = table.cells.containing(.any, identifier: name)
2828
let buttons = matchingCells.buttons

ios/MullvadVPNUITests/Pages/SettingsPage.swift

+8-1
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,17 @@ class SettingsPage: Page {
1313
@discardableResult override init(_ app: XCUIApplication) {
1414
super.init(app)
1515

16-
self.pageAccessibilityIdentifier = .settingsTableView
16+
self.pageAccessibilityIdentifier = .settingsContainerView
1717
waitForPageToBeShown()
1818
}
1919

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

0 commit comments

Comments
 (0)