Skip to content

Commit 42183bb

Browse files
committed
Merge branch 'test-connection-retry-logic-ios-435'
2 parents ede40a2 + 23136e9 commit 42183bb

File tree

8 files changed

+129
-28
lines changed

8 files changed

+129
-28
lines changed

ios/MullvadVPN/Classes/AccessbilityIdentifier.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public enum AccessibilityIdentifier: String {
1717
case cancelButton
1818
case connectionPanelButton
1919
case collapseButton
20+
case expandButton
2021
case createAccountButton
2122
case deleteButton
2223
case disconnectButton
@@ -57,7 +58,8 @@ public enum AccessibilityIdentifier: String {
5758

5859
// Labels
5960
case headerDeviceNameLabel
60-
case connectionStatusLabel
61+
case connectionStatusConnectedLabel
62+
case connectionStatusNotConnectedLabel
6163
case welcomeAccountNumberLabel
6264
case connectionPanelDetailLabel
6365

ios/MullvadVPN/View controllers/SelectLocation/LocationCell.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ class LocationCell: UITableViewCell {
4949

5050
private let collapseButton: UIButton = {
5151
let button = UIButton(type: .custom)
52-
button.accessibilityIdentifier = .collapseButton
5352
button.isAccessibilityElement = false
5453
button.tintColor = .white
5554
return button
@@ -267,6 +266,7 @@ class LocationCell: UITableViewCell {
267266
private func updateCollapseImage() {
268267
let image = isExpanded ? chevronUp : chevronDown
269268

269+
collapseButton.accessibilityIdentifier = isExpanded ? .collapseButton : .expandButton
270270
collapseButton.setImage(image, for: .normal)
271271
}
272272

ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ final class LocationViewController: UIViewController {
5555
override func viewDidLoad() {
5656
super.viewDidLoad()
5757

58+
view.accessibilityIdentifier = .selectLocationView
5859
view.backgroundColor = .secondaryColor
5960

6061
navigationItem.title = NSLocalizedString(

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

+7-2
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,6 @@ final class TunnelControlView: UIView {
121121
accessibilityContainerType = .semanticGroup
122122
accessibilityIdentifier = .tunnelControlView
123123

124-
secureLabel.accessibilityIdentifier = .connectionStatusLabel
125-
126124
addSubviews()
127125
addButtonHandlers()
128126
}
@@ -187,6 +185,13 @@ final class TunnelControlView: UIView {
187185
private func updateSecureLabel(tunnelState: TunnelState) {
188186
secureLabel.text = tunnelState.localizedTitleForSecureLabel.uppercased()
189187
secureLabel.textColor = tunnelState.textColorForSecureLabel
188+
189+
switch tunnelState {
190+
case .connected:
191+
secureLabel.accessibilityIdentifier = .connectionStatusConnectedLabel
192+
default:
193+
secureLabel.accessibilityIdentifier = .connectionStatusNotConnectedLabel
194+
}
190195
}
191196

192197
private func updateButtonTitles(tunnelState: TunnelState) {

ios/MullvadVPNUITests/Networking/FirewallRule.swift

+10
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ struct FirewallRule {
4545
)
4646
}
4747

48+
public static func makeBlockAllTrafficRule(toIPAddress: String) throws -> FirewallRule {
49+
let deviceIPAddress = try Networking.getIPAddress()
50+
51+
return FirewallRule(
52+
fromIPAddress: deviceIPAddress,
53+
toIPAddress: toIPAddress,
54+
protocols: [.ICMP, .TCP, .UDP]
55+
)
56+
}
57+
4858
public static func makeBlockUDPTrafficRule(toIPAddress: String) throws -> FirewallRule {
4959
let deviceIPAddress = try Networking.getIPAddress()
5060

ios/MullvadVPNUITests/Pages/SelectLocationPage.swift

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

25-
@discardableResult func tapLocationCellExpandCollapseButton(withName name: String) -> Self {
25+
@discardableResult func tapLocationCellExpandButton(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
29-
let expandButton = buttons[AccessibilityIdentifier.collapseButton]
29+
let expandButton = buttons[AccessibilityIdentifier.expandButton]
3030

3131
expandButton.tap()
3232

3333
return self
3434
}
35+
36+
func locationCellIsExpanded(_ name: String) -> Bool {
37+
let matchingCells = app.cells.containing(.any, identifier: name)
38+
return matchingCells.buttons[AccessibilityIdentifier.expandButton].exists ? false : true
39+
}
3540
}

ios/MullvadVPNUITests/Pages/TunnelControlPage.swift

+47-3
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ class TunnelControlPage: Page {
1919
/// Poll the "in address row" label for its updated values and output an array of ConnectionAttempt objects representing the connection attempts that have been communicated through the UI.
2020
/// - Parameters:
2121
/// - attemptsCount: number of connection attempts to look for
22-
/// - timeout: timeout after this many seconds if attemptsCount haven't been reached yet
22+
/// - timeout: return the attemps found so far after this many seconds if `attemptsCount` haven't been reached yet
2323
private func waitForConnectionAttempts(_ attemptsCount: Int, timeout: TimeInterval) -> [ConnectionAttempt] {
2424
var connectionAttempts: [ConnectionAttempt] = []
25+
var lastConnectionAttempt: ConnectionAttempt?
2526
let startTime = Date()
2627
let pollingInterval = TimeInterval(0.5) // How often to check for changes
2728

@@ -52,8 +53,9 @@ class TunnelControlPage: Page {
5253
protocolName: protocolName
5354
)
5455

55-
if connectionAttempts.contains(connectionAttempt) == false {
56+
if connectionAttempt != lastConnectionAttempt {
5657
connectionAttempts.append(connectionAttempt)
58+
lastConnectionAttempt = connectionAttempt
5759

5860
if connectionAttempts.count == attemptsCount {
5961
break
@@ -87,8 +89,13 @@ class TunnelControlPage: Page {
8789
return self
8890
}
8991

92+
@discardableResult func tapCancelButton() -> Self {
93+
app.buttons[AccessibilityIdentifier.cancelButton].tap()
94+
return self
95+
}
96+
9097
@discardableResult func waitForSecureConnectionLabel() -> Self {
91-
_ = app.staticTexts[AccessibilityIdentifier.connectionStatusLabel]
98+
_ = app.staticTexts[AccessibilityIdentifier.connectionStatusConnectedLabel]
9299
.waitForExistence(timeout: BaseUITestCase.defaultTimeout)
93100
return self
94101
}
@@ -130,6 +137,43 @@ class TunnelControlPage: Page {
130137
return self
131138
}
132139

140+
/// Verify that connection attempts are made in the correct order
141+
@discardableResult func verifyConnectionAttemptsOrder() -> Self {
142+
let connectionAttempts = waitForConnectionAttempts(4, timeout: 50)
143+
XCTAssertEqual(connectionAttempts.count, 4)
144+
145+
if connectionAttempts.last?.protocolName == "UDP" {
146+
// If last attempt is over UDP it means we have encountered the UI bug where only one UDP attempt is shown and then the two TCP attempts
147+
for (attemptIndex, attempt) in connectionAttempts.enumerated() {
148+
if attemptIndex == 0 {
149+
XCTAssertEqual(attempt.protocolName, "UDP")
150+
} else if attemptIndex == 1 {
151+
XCTAssertEqual(attempt.protocolName, "TCP")
152+
XCTAssertEqual(attempt.port, "80")
153+
} else if attemptIndex == 2 {
154+
XCTAssertEqual(attempt.protocolName, "TCP")
155+
XCTAssertEqual(attempt.port, "5001")
156+
} // Ignore the 4th attempt which is the first attempt of new attempt cycle
157+
}
158+
} else {
159+
for (attemptIndex, attempt) in connectionAttempts.enumerated() {
160+
if attemptIndex == 0 {
161+
XCTAssertEqual(attempt.protocolName, "UDP")
162+
} else if attemptIndex == 1 {
163+
XCTAssertEqual(attempt.protocolName, "UDP")
164+
} else if attemptIndex == 2 {
165+
XCTAssertEqual(attempt.protocolName, "TCP")
166+
XCTAssertEqual(attempt.port, "80")
167+
} else if attemptIndex == 3 {
168+
XCTAssertEqual(attempt.protocolName, "TCP")
169+
XCTAssertEqual(attempt.port, "5001")
170+
}
171+
}
172+
}
173+
174+
return self
175+
}
176+
133177
@discardableResult func verifyConnectingToPort(_ port: String) -> Self {
134178
let connectionAttempts = waitForConnectionAttempts(1, timeout: 10)
135179
XCTAssertEqual(connectionAttempts.count, 1)

ios/MullvadVPNUITests/RelayTests.swift

+53-19
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,27 @@ class RelayTests: LoggedInWithTimeUITestCase {
5555
.tapDisconnectButton()
5656
}
5757

58+
func testConnectionRetryLogic() throws {
59+
FirewallAPIClient().removeRules()
60+
removeFirewallRulesInTearDown = true
61+
62+
// First get relay IP address
63+
let relayIPAddress = getGot001WireGuardRelayIPAddress()
64+
65+
// Run actual test
66+
try FirewallAPIClient().createRule(
67+
FirewallRule.makeBlockAllTrafficRule(toIPAddress: relayIPAddress)
68+
)
69+
70+
TunnelControlPage(app)
71+
.tapSecureConnectionButton()
72+
73+
// Should be two UDP connection attempts but sometimes only one is shown in the UI
74+
TunnelControlPage(app)
75+
.verifyConnectionAttemptsOrder()
76+
.tapCancelButton()
77+
}
78+
5879
func testWireGuardOverTCPManually() throws {
5980
HeaderBar(app)
6081
.tapSettingsButton()
@@ -86,29 +107,11 @@ class RelayTests: LoggedInWithTimeUITestCase {
86107

87108
/// Test automatic switching to TCP is functioning when UDP traffic to relay is blocked. This test first connects to a realy to get the IP address of it, in order to block UDP traffic to this relay.
88109
func testWireGuardOverTCPAutomatically() throws {
89-
let wireGuardGot001RelayName = "se-got-wg-001"
90-
91110
FirewallAPIClient().removeRules()
92111
removeFirewallRulesInTearDown = true
93112

94113
// First get relay IP address
95-
TunnelControlPage(app)
96-
.tapSelectLocationButton()
97-
98-
SelectLocationPage(app)
99-
.tapLocationCellExpandCollapseButton(withName: "Sweden")
100-
.tapLocationCellExpandCollapseButton(withName: "Gothenburg")
101-
.tapLocationCell(withName: wireGuardGot001RelayName)
102-
103-
allowAddVPNConfigurationsIfAsked()
104-
105-
let relayIPAddress = TunnelControlPage(app)
106-
.waitForSecureConnectionLabel()
107-
.tapRelayStatusExpandCollapseButton()
108-
.getInIPAddressFromConnectionStatus()
109-
110-
TunnelControlPage(app)
111-
.tapDisconnectButton()
114+
let relayIPAddress = getGot001WireGuardRelayIPAddress()
112115

113116
// Run actual test
114117
try FirewallAPIClient().createRule(
@@ -163,4 +166,35 @@ class RelayTests: LoggedInWithTimeUITestCase {
163166
.verifyConnectingToPort("4001")
164167
.tapDisconnectButton()
165168
}
169+
170+
/// Get got001 WireGuard relay IP address by connecting to it and checking which IP address the app connects to. Assumes user is logged on and at tunnel control page.
171+
private func getGot001WireGuardRelayIPAddress() -> String {
172+
let wireGuardGot001RelayName = "se-got-wg-001"
173+
174+
TunnelControlPage(app)
175+
.tapSelectLocationButton()
176+
177+
if SelectLocationPage(app).locationCellIsExpanded("Sweden") {
178+
// Already expanded - just make sure correct relay is selected
179+
SelectLocationPage(app)
180+
.tapLocationCell(withName: wireGuardGot001RelayName)
181+
} else {
182+
SelectLocationPage(app)
183+
.tapLocationCellExpandButton(withName: "Sweden")
184+
.tapLocationCellExpandButton(withName: "Gothenburg")
185+
.tapLocationCell(withName: wireGuardGot001RelayName)
186+
}
187+
188+
allowAddVPNConfigurationsIfAsked()
189+
190+
let relayIPAddress = TunnelControlPage(app)
191+
.waitForSecureConnectionLabel()
192+
.tapRelayStatusExpandCollapseButton()
193+
.getInIPAddressFromConnectionStatus()
194+
195+
TunnelControlPage(app)
196+
.tapDisconnectButton()
197+
198+
return relayIPAddress
199+
}
166200
}

0 commit comments

Comments
 (0)