blob: e23b9b568ae55c8a0bb0c622285f63cc4cb75020 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
|
//
// ConnectivityTests.swift
// MullvadVPNUITests
//
// Created by Niklas Berglund on 2024-01-18.
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
import Foundation
import Network
import XCTest
class ConnectivityTests: LoggedOutUITestCase {
let firewallAPIClient = FirewallClient()
/// Verifies that the app still functions when API has been blocked
func testAPIConnectionViaBridges() throws {
firewallAPIClient.removeRules()
let hasTimeAccountNumber = getAccountWithTime()
addTeardownBlock {
self.deleteTemporaryAccountWithTime(accountNumber: hasTimeAccountNumber)
self.firewallAPIClient.removeRules()
}
try Networking.verifyCanAccessAPI() // Just to make sure there's no old firewall rule still active
firewallAPIClient.createRule(try FirewallRule.makeBlockAPIAccessFirewallRule())
try Networking.verifyCannotAccessAPI()
var successIconShown = false
var retryCount = 0
let maxRetryCount = 3
let loginPage = LoginPage(app)
.tapAccountNumberTextField()
.enterText(hasTimeAccountNumber)
// After creating firewall rule first login attempt might fail. More attempts are allowed since the app is cycling between three methods.
repeat {
successIconShown =
loginPage
.tapAccountNumberSubmitButton()
.getSuccessIconShown()
if successIconShown == false {
// Give it some time to show up. App might be waiting for a network connection to timeout.
loginPage.waitForAccountNumberSubmitButton()
}
retryCount += 1
} while successIconShown == false && retryCount < maxRetryCount
HeaderBar(app)
.verifyDeviceLabelShown()
}
/// Get the app into a blocked state by connecting to a relay then applying a filter which don't find this relay, then verify that app can still communicate by logging out and verifying that the device was successfully removed
func testAPIReachableWhenBlocked() throws {
let hasTimeAccountNumber = getAccountWithTime()
addTeardownBlock {
// Reset any filters
self.login(accountNumber: hasTimeAccountNumber)
TunnelControlPage(self.app)
.tapSelectLocationButton()
let filterCloseButtons = self.app.buttons
.matching(identifier: AccessibilityIdentifier.relayFilterChipCloseButton.asString)
.allElementsBoundByIndex
for filterCloseButton in filterCloseButtons where filterCloseButton.isHittable {
filterCloseButton.tap()
}
// Reset selected location to Sweden
SelectLocationPage(self.app)
.tapLocationCell(withName: BaseUITestCase.appDefaultCountry)
self.allowAddVPNConfigurationsIfAsked()
TunnelControlPage(self.app)
.tapCancelOrDisconnectButton()
self.deleteTemporaryAccountWithTime(accountNumber: hasTimeAccountNumber)
}
// Setup. Enter blocked state by connecting to relay and applying filter which relay isn't part of.
login(accountNumber: hasTimeAccountNumber)
TunnelControlPage(app)
.tapSelectLocationButton()
SelectLocationPage(app)
.tapFilterButton()
SelectLocationFilterPage(app)
.tapMullvadOwnershipCell()
.tapApplyButton()
// Select the first country, its first city and its first relay
SelectLocationPage(app)
.tapCountryLocationCellExpandButton(
withName: BaseUITestCase
.testsDefaultCountryName
) // Must be a little specific here in order to avoid using relay services country with experimental relays
.tapCityLocationCellExpandButton(withIndex: 0)
.tapRelayLocationCell(withIndex: 0)
allowAddVPNConfigurationsIfAsked()
TunnelControlPage(app)
.tapSelectLocationButton()
SelectLocationPage(app)
.tapFilterButton()
SelectLocationFilterPage(app)
.tapRentedOwnershipCell()
.tapApplyButton()
SelectLocationPage(app)
.tapDoneButton()
// Get device name, log out and make sure device was removed as a a way of verifying that the API can be reached
HeaderBar(app)
.tapAccountButton()
let deviceName = try AccountPage(app).getDeviceName()
AccountPage(app)
.tapLogOutButton()
.waitForLogoutSpinnerToDisappear()
LoginPage(app)
verifyDeviceHasBeenRemoved(deviceName: deviceName, accountNumber: hasTimeAccountNumber)
}
/// Test that the app is functioning when API is down. To simulate API being down we create a dummy access method
func testAppStillFunctioningWhenAPIDown() throws {
let hasTimeAccountNumber = getAccountWithTime()
let customAccessMethodName = "Disable-access-dummy"
addTeardownBlock {
HeaderBar(self.app)
.tapSettingsButton()
SettingsPage(self.app)
.tapAPIAccessCell()
self.toggleAllAccessMethodsEnabledSwitchesIfOff()
APIAccessPage(self.app)
.editAccessMethod(customAccessMethodName)
EditAccessMethodPage(self.app)
.tapDeleteButton()
.confirmAccessMethodDeletion()
self.deleteTemporaryAccountWithTime(accountNumber: hasTimeAccountNumber)
}
// Setup. Create a dummy access method to simulate API being down(unreachable)
LoginPage(app)
.tapAccountNumberTextField()
.enterText(hasTimeAccountNumber)
.tapAccountNumberSubmitButton()
TunnelControlPage(app)
HeaderBar(app)
.tapSettingsButton()
SettingsPage(app)
.tapAPIAccessCell()
APIAccessPage(app)
.tapAddButton()
allowLocalNetworkAccessIfAsked()
AddAccessMethodPage(app)
.tapNameCell()
.enterText(customAccessMethodName)
.tapTypeCell()
.tapSOCKS5TypeValueCell()
.tapServerCell()
.enterText("123.123.123.123")
.dismissKeyboard()
.tapPortCell()
.enterText("123")
.dismissKeyboard()
.tapAddButton()
.waitForAPIUnreachableLabel()
AddAccessMethodAPIUnreachableAlert(app)
.tapSaveButton()
disableBuiltinAccessMethods()
SettingsPage(app)
.swipeDownToDismissModal()
// Actual test. Make sure it is possible to connect to a relay
TunnelControlPage(app)
.tapConnectButton()
allowAddVPNConfigurationsIfAsked()
TunnelControlPage(app)
.waitForConnectedLabel()
HeaderBar(app)
.tapAccountButton()
// Log out will take long because API cannot be reached
AccountPage(app)
.tapLogOutButton()
.waitForLogoutSpinnerToDisappear()
// Verify API cannot be reached by doing a login attempt which should fail
LoginPage(app)
.tapAccountNumberTextField()
.enterText(hasTimeAccountNumber)
.tapAccountNumberSubmitButton()
.verifyFailIconShown()
}
func testIfLocalNetworkSharingIsBlocking() throws {
let skipReason = """
This test is currently skipped since there is no way to allow local network access for UI tests.
Since its blocked by the system, there is no way of testing the `Local network sharing` switch.
Non of these solutions worked: https://developer.apple.com/forums/thread/668729
"""
try XCTSkipIf(true, skipReason)
let hasTimeAccountNumber = getAccountWithTime()
addTeardownBlock {
self.deleteTemporaryAccountWithTime(accountNumber: hasTimeAccountNumber)
}
agreeToTermsOfServiceIfShown()
login(accountNumber: hasTimeAccountNumber)
TunnelControlPage(app)
.tapConnectButton()
allowAddVPNConfigurationsIfAsked()
TunnelControlPage(app)
.waitForConnectedLabel()
try Networking.verifyCannotAccessLocalNetwork()
HeaderBar(app)
.tapSettingsButton()
SettingsPage(app)
.tapVPNSettingsCell()
VPNSettingsPage(app)
.tapLocalNetworkSharingSwitch()
.tapBackButton()
SettingsPage(app)
.tapDoneButton()
TunnelControlPage(app)
.waitForConnectedLabel()
try Networking.verifyCanAccessLocalNetwork()
}
private func verifyDeviceHasBeenRemoved(deviceName: String, accountNumber: String) {
do {
let devices = try MullvadAPIWrapper().getDevices(accountNumber)
for device in devices where device.name == deviceName {
XCTFail("Device has not been removed which tells us that the logout was not successful")
}
} catch {
XCTFail("Failed to get devices from app API")
}
}
/// Toggle enabled switch for all existing access methods.
/// Preconditions:
/// - The app is currently showing API access view.
/// - There is one custom access method enabled
/// - The extra access method is not disabled
private func disableBuiltinAccessMethods() {
var accessMethods = APIAccessPage(app).getAccessMethodCells()
accessMethods.removeLast()
for cell in accessMethods {
cell.tap()
EditAccessMethodPage(app)
.tapEnableMethodSwitch()
.tapBackButton()
}
}
/// Toggle enabled switch for all existing access methods if the switch is in off state. It is a precondition that the app is currently showing API access view.
private func toggleAllAccessMethodsEnabledSwitchesIfOff() {
for cell in APIAccessPage(app).getAccessMethodCells() {
cell.tap()
EditAccessMethodPage(app)
.tapEnableMethodSwitchIfOff()
.tapBackButton()
}
}
}
|