summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBug Magnet <marco.nikic@mullvad.net>2024-02-14 11:30:41 +0100
committerBug Magnet <marco.nikic@mullvad.net>2024-02-14 11:30:41 +0100
commit5c29187dbc4104d2f81f5c8e2cd53299a78c53fe (patch)
treec0157efc23ee39ee0ed0384c90ed14802a1e4137
parent3a7fdd497202182f28c7eba45fbb9c563f444f6c (diff)
parentbf96edc4621f21e4c4f0da2127e234c2932a996a (diff)
downloadmullvadvpn-5c29187dbc4104d2f81f5c8e2cd53299a78c53fe.tar.xz
mullvadvpn-5c29187dbc4104d2f81f5c8e2cd53299a78c53fe.zip
Merge branch 'test-api-connection-via-alternative-means-ios-438'
-rw-r--r--ios/Configurations/UITests.xcconfig.template25
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj55
-rw-r--r--ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPNUITests.xcscheme14
-rw-r--r--ios/MullvadVPN/Classes/AccessbilityIdentifier.swift7
-rw-r--r--ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController.swift4
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift3
-rw-r--r--ios/MullvadVPNUITests/ConnectivityTests.swift52
-rw-r--r--ios/MullvadVPNUITests/Info.plist30
-rw-r--r--ios/MullvadVPNUITests/Networking/FirewallAPIClient.swift113
-rw-r--r--ios/MullvadVPNUITests/Networking/FirewallRule.swift43
-rw-r--r--ios/MullvadVPNUITests/Networking/MullvadAPIWrapper.swift45
-rw-r--r--ios/MullvadVPNUITests/Networking/Networking.swift146
-rw-r--r--ios/MullvadVPNUITests/Pages/LoginPage.swift4
-rw-r--r--ios/MullvadVPNUITests/Pages/Page.swift9
-rw-r--r--ios/MullvadVPNUITests/Pages/ProblemReportPage.swift48
-rw-r--r--ios/MullvadVPNUITests/Pages/SettingsPage.swift8
-rw-r--r--ios/MullvadVPNUITests/Pages/TunnelControlPage.swift11
-rw-r--r--ios/MullvadVPNUITests/RelayTests.swift53
-rw-r--r--ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift12
19 files changed, 584 insertions, 98 deletions
diff --git a/ios/Configurations/UITests.xcconfig.template b/ios/Configurations/UITests.xcconfig.template
index 2ed40f1624..0584179b3d 100644
--- a/ios/Configurations/UITests.xcconfig.template
+++ b/ios/Configurations/UITests.xcconfig.template
@@ -1,19 +1,18 @@
-//
-// MullvadVPNUITests.xcconfig
-// MullvadVPN
-//
-// Created by Niklas Berglund on 2024-01-10.
-// Copyright © 2024 Mullvad VPN AB. All rights reserved.
-//
+#include "Base.xcconfig"
// Pin code of the iOS device under test
-MULLVAD_IOS_DEVICE_PIN_CODE =
+IOS_DEVICE_PIN_CODE =
-// Ad serving domain used when testing ad blocking. Not that we are assuming there's an HTTP server running on the host.
-MULLVAD_AD_SERVING_DOMAIN =
+// UUID to identify test runs. Should be unique per test device. Generate with for example uuidgen on macOS.
+TEST_DEVICE_IDENTIFIER_UUID =
// Mullvad accounts used by UI tests
-MULLVAD_NO_TIME_ACCOUNT_NUMBER =
-MULLVAD_HAS_TIME_ACCOUNT_NUMBER =
-MULLVAD_FIVE_WIREGUARD_KEYS_ACCOUNT_NUMBER =
+NO_TIME_ACCOUNT_NUMBER =
+HAS_TIME_ACCOUNT_NUMBER =
+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
+// 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
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index e2ad8ebbcc..b7685c0ad8 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -38,10 +38,10 @@
06799AFC28F98EE300ACD94E /* AddressCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AC114128F8413A0037AF9A /* AddressCache.swift */; };
0697D6E728F01513007A9E99 /* TransportMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0697D6E628F01513007A9E99 /* TransportMonitor.swift */; };
06AC116228F94C450037AF9A /* ApplicationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */; };
- 44DD7D2D2B74E44A0005F67F /* QuantumResistanceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D2C2B74E44A0005F67F /* QuantumResistanceSettings.swift */; };
44DD7D242B6CFFD70005F67F /* StartTunnelOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */; };
44DD7D272B6D18FB0005F67F /* MockTunnelInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */; };
44DD7D292B7113CA0005F67F /* MockTunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D282B7113CA0005F67F /* MockTunnel.swift */; };
+ 44DD7D2D2B74E44A0005F67F /* QuantumResistanceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D2C2B74E44A0005F67F /* QuantumResistanceSettings.swift */; };
5803B4B02940A47300C23744 /* TunnelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5803B4AF2940A47300C23744 /* TunnelConfiguration.swift */; };
5803B4B22940A48700C23744 /* TunnelStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5803B4B12940A48700C23744 /* TunnelStore.swift */; };
5807E2C02432038B00F5FF30 /* String+Split.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5807E2BF2432038B00F5FF30 /* String+Split.swift */; };
@@ -590,13 +590,18 @@
852969362B4E9724007EAD4C /* AccessbilityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0B311D2B303A0D004B12E0 /* AccessbilityIdentifier.swift */; };
8529693A2B4F0238007EAD4C /* TermsOfServicePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852969392B4F0238007EAD4C /* TermsOfServicePage.swift */; };
8529693C2B4F0257007EAD4C /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8529693B2B4F0257007EAD4C /* Alert.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 */; };
+ 85557B142B5983CF00795FE1 /* MullvadAPIWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B132B5983CF00795FE1 /* MullvadAPIWrapper.swift */; };
85557B162B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B152B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift */; };
85557B1E2B5FB8C700795FE1 /* HeaderBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B1D2B5FB8C700795FE1 /* HeaderBar.swift */; };
85557B202B5FBBD700795FE1 /* AccountPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B1F2B5FBBD700795FE1 /* AccountPage.swift */; };
+ 855D9F5B2B63E56B00D7C64D /* ProblemReportPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 855D9F5A2B63E56B00D7C64D /* ProblemReportPage.swift */; };
8590896C2B61763B003AF5F5 /* LoggedInWithoutTimeUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 859089682B61763B003AF5F5 /* LoggedInWithoutTimeUITestCase.swift */; };
- 8590896D2B61763B003AF5F5 /* LoggedInWithTimeUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 859089692B61763B003AF5F5 /* LoggedInWithTimeUITestCase.swift */; };
- 8590896E2B61763B003AF5F5 /* BaseUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590896A2B61763B003AF5F5 /* BaseUITestCase.swift */; };
8590896F2B61763B003AF5F5 /* LoggedOutUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590896B2B61763B003AF5F5 /* LoggedOutUITestCase.swift */; };
+ 85D2B0B12B6BD32400DF9DA7 /* BaseUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590896A2B61763B003AF5F5 /* BaseUITestCase.swift */; };
+ 85E3BDE52B70E18C00FA71FD /* Networking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E3BDE42B70E18C00FA71FD /* Networking.swift */; };
A900E9B82ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A900E9B72ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift */; };
A900E9BA2ACC5D0600C95F67 /* RESTRequestExecutor+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A900E9B92ACC5D0600C95F67 /* RESTRequestExecutor+Stubs.swift */; };
A900E9BC2ACC609200C95F67 /* DevicesProxy+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A900E9BB2ACC609200C95F67 /* DevicesProxy+Stubs.swift */; };
@@ -1245,10 +1250,10 @@
06FAE67A28F83CA50033DD93 /* RESTDevicesProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTDevicesProxy.swift; sourceTree = "<group>"; };
06FAE67B28F83CA50033DD93 /* REST.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = REST.swift; sourceTree = "<group>"; };
06FAE67D28F83CA50033DD93 /* RESTTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTTransport.swift; sourceTree = "<group>"; };
- 44DD7D2C2B74E44A0005F67F /* QuantumResistanceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuantumResistanceSettings.swift; sourceTree = "<group>"; };
44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartTunnelOperationTests.swift; sourceTree = "<group>"; };
44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnelInteractor.swift; sourceTree = "<group>"; };
44DD7D282B7113CA0005F67F /* MockTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnel.swift; sourceTree = "<group>"; };
+ 44DD7D2C2B74E44A0005F67F /* QuantumResistanceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuantumResistanceSettings.swift; sourceTree = "<group>"; };
5802EBC42A8E44AC00E5CE4C /* AppRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRoutes.swift; sourceTree = "<group>"; };
5802EBC62A8E457A00E5CE4C /* AppRouteProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouteProtocol.swift; sourceTree = "<group>"; };
5802EBC82A8E45BA00E5CE4C /* ApplicationRouterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationRouterDelegate.swift; sourceTree = "<group>"; };
@@ -1774,13 +1779,19 @@
852969382B4ED818007EAD4C /* UITests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UITests.xcconfig; sourceTree = "<group>"; };
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>"; };
+ 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>"; };
+ 85557B132B5983CF00795FE1 /* MullvadAPIWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadAPIWrapper.swift; sourceTree = "<group>"; };
85557B152B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElementQuery+Extensions.swift"; sourceTree = "<group>"; };
85557B1D2B5FB8C700795FE1 /* HeaderBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderBar.swift; sourceTree = "<group>"; };
85557B1F2B5FBBD700795FE1 /* AccountPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPage.swift; sourceTree = "<group>"; };
+ 855D9F5A2B63E56B00D7C64D /* ProblemReportPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportPage.swift; sourceTree = "<group>"; };
859089682B61763B003AF5F5 /* LoggedInWithoutTimeUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedInWithoutTimeUITestCase.swift; sourceTree = "<group>"; };
859089692B61763B003AF5F5 /* LoggedInWithTimeUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedInWithTimeUITestCase.swift; sourceTree = "<group>"; };
8590896A2B61763B003AF5F5 /* BaseUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseUITestCase.swift; sourceTree = "<group>"; };
8590896B2B61763B003AF5F5 /* LoggedOutUITestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedOutUITestCase.swift; sourceTree = "<group>"; };
+ 85E3BDE42B70E18C00FA71FD /* Networking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Networking.swift; sourceTree = "<group>"; };
A900E9B72ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountsProxy+Stubs.swift"; sourceTree = "<group>"; };
A900E9B92ACC5D0600C95F67 /* RESTRequestExecutor+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RESTRequestExecutor+Stubs.swift"; sourceTree = "<group>"; };
A900E9BB2ACC609200C95F67 /* DevicesProxy+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DevicesProxy+Stubs.swift"; sourceTree = "<group>"; };
@@ -3390,12 +3401,14 @@
852969262B4D9C1F007EAD4C /* MullvadVPNUITests */ = {
isa = PBXGroup;
children = (
- 8518F6392B601910009EB113 /* Test base classes */,
- 852969312B4E9220007EAD4C /* Pages */,
852969272B4D9C1F007EAD4C /* AccountTests.swift */,
+ 85557B112B594FC900795FE1 /* ConnectivityTests.swift */,
+ 852969372B4ED20E007EAD4C /* Info.plist */,
+ 85557B0C2B591B0F00795FE1 /* Networking */,
+ 852969312B4E9220007EAD4C /* Pages */,
850201DA2B503D7700EF8C96 /* RelayTests.swift */,
+ 8518F6392B601910009EB113 /* Test base classes */,
85557B152B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift */,
- 852969372B4ED20E007EAD4C /* Info.plist */,
);
path = MullvadVPNUITests;
sourceTree = "<group>";
@@ -3403,19 +3416,31 @@
852969312B4E9220007EAD4C /* Pages */ = {
isa = PBXGroup;
children = (
+ 85557B1F2B5FBBD700795FE1 /* AccountPage.swift */,
8529693B2B4F0257007EAD4C /* Alert.swift */,
+ 85557B1D2B5FB8C700795FE1 /* HeaderBar.swift */,
852969342B4E9270007EAD4C /* LoginPage.swift */,
852969322B4E9232007EAD4C /* Page.swift */,
+ 855D9F5A2B63E56B00D7C64D /* ProblemReportPage.swift */,
850201DC2B503D8C00EF8C96 /* SelectLocationPage.swift */,
850201E22B51A93C00EF8C96 /* SettingsPage.swift */,
- 850201DE2B5040A500EF8C96 /* TunnelControlPage.swift */,
- 85557B1D2B5FB8C700795FE1 /* HeaderBar.swift */,
852969392B4F0238007EAD4C /* TermsOfServicePage.swift */,
- 85557B1F2B5FBBD700795FE1 /* AccountPage.swift */,
+ 850201DE2B5040A500EF8C96 /* TunnelControlPage.swift */,
);
path = Pages;
sourceTree = "<group>";
};
+ 85557B0C2B591B0F00795FE1 /* Networking */ = {
+ isa = PBXGroup;
+ children = (
+ 85557B0D2B591B2600795FE1 /* FirewallAPIClient.swift */,
+ 85557B0F2B59215F00795FE1 /* FirewallRule.swift */,
+ 85557B132B5983CF00795FE1 /* MullvadAPIWrapper.swift */,
+ 85E3BDE42B70E18C00FA71FD /* Networking.swift */,
+ );
+ path = Networking;
+ sourceTree = "<group>";
+ };
A907639F2B2857D50045ADF0 /* Socks5 */ = {
isa = PBXGroup;
children = (
@@ -5291,23 +5316,29 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 8590896D2B61763B003AF5F5 /* LoggedInWithTimeUITestCase.swift in Sources */,
+ 85D2B0B12B6BD32400DF9DA7 /* BaseUITestCase.swift in Sources */,
8529693C2B4F0257007EAD4C /* Alert.swift in Sources */,
850201DD2B503D8C00EF8C96 /* SelectLocationPage.swift in Sources */,
850201DB2B503D7700EF8C96 /* RelayTests.swift in Sources */,
+ 85557B142B5983CF00795FE1 /* MullvadAPIWrapper.swift in Sources */,
852969362B4E9724007EAD4C /* AccessbilityIdentifier.swift in Sources */,
+ 85E3BDE52B70E18C00FA71FD /* Networking.swift in Sources */,
8590896C2B61763B003AF5F5 /* LoggedInWithoutTimeUITestCase.swift in Sources */,
8590896F2B61763B003AF5F5 /* LoggedOutUITestCase.swift in Sources */,
85557B202B5FBBD700795FE1 /* AccountPage.swift in Sources */,
852969352B4E9270007EAD4C /* LoginPage.swift in Sources */,
- 8590896E2B61763B003AF5F5 /* BaseUITestCase.swift in Sources */,
850201E32B51A93C00EF8C96 /* SettingsPage.swift in Sources */,
8518F6382B60157E009EB113 /* LoggedInWithoutTimeUITestCase.swift in Sources */,
+ 85557B102B59215F00795FE1 /* FirewallRule.swift in Sources */,
+ 850201E32B51A93C00EF8C96 /* SettingsPage.swift in Sources */,
+ 85557B0E2B591B2600795FE1 /* FirewallAPIClient.swift in Sources */,
852969282B4D9C1F007EAD4C /* AccountTests.swift in Sources */,
85557B162B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift in Sources */,
+ 855D9F5B2B63E56B00D7C64D /* ProblemReportPage.swift in Sources */,
8529693A2B4F0238007EAD4C /* TermsOfServicePage.swift in Sources */,
850201DF2B5040A500EF8C96 /* TunnelControlPage.swift in Sources */,
85557B1E2B5FB8C700795FE1 /* HeaderBar.swift in Sources */,
+ 85557B122B594FC900795FE1 /* ConnectivityTests.swift in Sources */,
852969332B4E9232007EAD4C /* Page.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPNUITests.xcscheme b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPNUITests.xcscheme
index 45ec07ec85..7c65faea3b 100644
--- a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPNUITests.xcscheme
+++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPNUITests.xcscheme
@@ -40,7 +40,8 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
- <MacroExpansion>
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "58CE5E5F224146200008646E"
@@ -48,7 +49,7 @@
BlueprintName = "MullvadVPN"
ReferencedContainer = "container:MullvadVPN.xcodeproj">
</BuildableReference>
- </MacroExpansion>
+ </BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -56,6 +57,15 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "58CE5E5F224146200008646E"
+ BuildableName = "MullvadVPN.app"
+ BlueprintName = "MullvadVPN"
+ ReferencedContainer = "container:MullvadVPN.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
diff --git a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
index 24a87ed14f..855ea3c748 100644
--- a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
+++ b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
@@ -27,9 +27,12 @@ public enum AccessibilityIdentifier: String {
case purchaseButton
case redeemVoucherButton
case restorePurchasesButton
+ case secureConnectionButton
case selectLocationButton
case settingsButton
case startUsingTheAppButton
+ case problemReportAppLogsButton
+ case problemReportSendButton
// Cells
case preferencesCell
@@ -43,6 +46,7 @@ public enum AccessibilityIdentifier: String {
// Labels
case headerDeviceNameLabel
+ case connectionStatusLabel
// Views
case accountView
@@ -55,6 +59,7 @@ public enum AccessibilityIdentifier: String {
case selectLocationTableView
case settingsTableView
case tunnelControlView
+ case problemReportView
// Other UI elements
case connectionPanelInAddressRow
@@ -63,6 +68,8 @@ public enum AccessibilityIdentifier: String {
case dnsContentBlockersHeaderView
case loginTextField
case selectLocationSearchTextField
+ case problemReportEmailTextField
+ case problemReportMessageTextView
// DNS settings
case dnsSettings
diff --git a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController.swift b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController.swift
index 45292672d3..f255b43b51 100644
--- a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController.swift
+++ b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController.swift
@@ -75,6 +75,7 @@ final class ProblemReportViewController: UIViewController, UITextFieldDelegate {
super.viewDidLoad()
view.backgroundColor = .secondaryColor
+ view.accessibilityIdentifier = .problemReportView
navigationItem.title = Self.persistentViewModel.navigationTitle
@@ -90,6 +91,9 @@ final class ProblemReportViewController: UIViewController, UITextFieldDelegate {
messageTextView.setContentHuggingPriority(.defaultLow, for: .vertical)
messageTextView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
+ emailTextField.accessibilityIdentifier = .problemReportEmailTextField
+ messageTextView.accessibilityIdentifier = .problemReportMessageTextView
+
addConstraints()
registerForNotifications()
loadPersistentViewModel()
diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift
index e1f393b1b7..bd991142e6 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift
@@ -64,6 +64,7 @@ final class TunnelControlView: UIView {
private let connectButton: AppButton = {
let button = AppButton(style: .success)
+ button.accessibilityIdentifier = .secureConnectionButton
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
@@ -120,6 +121,8 @@ final class TunnelControlView: UIView {
accessibilityContainerType = .semanticGroup
accessibilityIdentifier = .tunnelControlView
+ secureLabel.accessibilityIdentifier = .connectionStatusLabel
+
addSubviews()
addButtonHandlers()
}
diff --git a/ios/MullvadVPNUITests/ConnectivityTests.swift b/ios/MullvadVPNUITests/ConnectivityTests.swift
new file mode 100644
index 0000000000..1c4e46ac14
--- /dev/null
+++ b/ios/MullvadVPNUITests/ConnectivityTests.swift
@@ -0,0 +1,52 @@
+//
+// ConnectivityTests.swift
+// MullvadVPNUITests
+//
+// Created by Niklas Berglund on 2024-01-18.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import Network
+import XCTest
+
+class ConnectivityTests: LoggedOutUITestCase {
+ let firewallAPIClient = FirewallAPIClient()
+
+ override func setUpWithError() throws {
+ super.setUp()
+ }
+
+ override func tearDownWithError() throws {
+ super.tearDown()
+ firewallAPIClient.removeRules()
+ }
+
+ /// Verifies that the app still functions when API has been blocked
+ func testAPIConnectionViaBridges() throws {
+ let app = XCUIApplication()
+ app.launch()
+
+ try Networking.verifyCanAccessAPI() // Just to make sure there's no old firewall rule still active
+ firewallAPIClient.createRule(try FirewallRule.makeBlockAPIAccessFirewallRule())
+ try Networking.verifyCannotAccessAPI()
+
+ LoginPage(app)
+ .tapAccountNumberTextField()
+ .enterText(self.hasTimeAccountNumber)
+ .tapAccountNumberSubmitButton()
+
+ // After creating firewall rule first login attempt might fail. One more attempt is allowed since the app is cycling between two methods.
+ if isLoggedIn() {
+ LoginPage(app)
+ .verifySuccessIconShown()
+ .verifyDeviceLabelShown()
+ } else {
+ LoginPage(app)
+ .verifyFailIconShown()
+ .tapAccountNumberSubmitButton()
+ .verifySuccessIconShown()
+ .verifyDeviceLabelShown()
+ }
+ }
+}
diff --git a/ios/MullvadVPNUITests/Info.plist b/ios/MullvadVPNUITests/Info.plist
index 85eb161b49..dd7e65bae2 100644
--- a/ios/MullvadVPNUITests/Info.plist
+++ b/ios/MullvadVPNUITests/Info.plist
@@ -2,17 +2,25 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
- <key>MullvadAdServingDomain</key>
- <string>$(MULLVAD_AD_SERVING_DOMAIN)</string>
- <key>MullvadDisplayName</key>
+ <key>AdServingDomain</key>
+ <string>$(AD_SERVING_DOMAIN)</string>
+ <key>ApiEndpoint</key>
+ <string>$(API_ENDPOINT)</string>
+ <key>ApiHostName</key>
+ <string>$(API_HOST_NAME)</string>
+ <key>DisplayName</key>
<string>$(DISPLAY_NAME)</string>
- <key>MullvadFiveWireGuardKeysAccountNumber</key>
- <string>$(MULLVAD_FIVE_WIREGUARD_KEYS_ACCOUNT_NUMBER)</string>
- <key>MullvadHasTimeAccountNumber</key>
- <string>$(MULLVAD_HAS_TIME_ACCOUNT_NUMBER)</string>
- <key>MullvadIOSDevicePinCode</key>
- <string>$(MULLVAD_IOS_DEVICE_PIN_CODE)</string>
- <key>MullvadNoTimeAccountNumber</key>
- <string>$(MULLVAD_NO_TIME_ACCOUNT_NUMBER)</string>
+ <key>FirewallApiBaseURL</key>
+ <string>$(FIREWALL_API_BASE_URL)</string>
+ <key>FiveWireGuardKeysAccountNumber</key>
+ <string>$(FIVE_WIREGUARD_KEYS_ACCOUNT_NUMBER)</string>
+ <key>HasTimeAccountNumber</key>
+ <string>$(HAS_TIME_ACCOUNT_NUMBER)</string>
+ <key>IOSDevicePinCode</key>
+ <string>$(IOS_DEVICE_PIN_CODE)</string>
+ <key>NoTimeAccountNumber</key>
+ <string>$(NO_TIME_ACCOUNT_NUMBER)</string>
+ <key>TestDeviceIdentifier</key>
+ <string>$(TEST_DEVICE_IDENTIFIER_UUID</string>
</dict>
</plist>
diff --git a/ios/MullvadVPNUITests/Networking/FirewallAPIClient.swift b/ios/MullvadVPNUITests/Networking/FirewallAPIClient.swift
new file mode 100644
index 0000000000..49496f3d2e
--- /dev/null
+++ b/ios/MullvadVPNUITests/Networking/FirewallAPIClient.swift
@@ -0,0 +1,113 @@
+//
+// FirewallClient.swift
+// MullvadVPNUITests
+//
+// Created by Niklas Berglund on 2024-01-18.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import SystemConfiguration
+import UIKit
+import XCTest
+
+class FirewallAPIClient {
+ // swiftlint:disable force_cast
+ let baseURL = URL(
+ string:
+ Bundle(for: FirewallAPIClient.self).infoDictionary?["FirewallApiBaseURL"] as! String
+ )!
+ let testDeviceIdentifier = Bundle(for: FirewallAPIClient.self).infoDictionary?["TestDeviceIdentifier"] as! String
+ // swiftlint:enable force_cast
+
+ lazy var sessionIdentifier = "urn:uuid:" + testDeviceIdentifier
+
+ /// Create a new rule associated to the device under test
+ public func createRule(_ firewallRule: FirewallRule) {
+ let createRuleURL = baseURL.appendingPathComponent("rule")
+
+ var request = URLRequest(url: createRuleURL)
+ request.httpMethod = "POST"
+ request.setValue("application/json", forHTTPHeaderField: "Content-Type")
+
+ let dataDictionary: [String: Any] = [
+ "label": sessionIdentifier,
+ "from": firewallRule.fromIPAddress,
+ "to": firewallRule.toIPAddress,
+ ]
+
+ var requestError: Error?
+ var requestResponse: URLResponse?
+ let completionHandlerInvokedExpectation = XCTestExpectation(
+ description: "Completion handler for the request is invoked"
+ )
+
+ do {
+ let jsonData = try JSONSerialization.data(withJSONObject: dataDictionary)
+ request.httpBody = jsonData
+
+ let dataTask = URLSession.shared.dataTask(with: request) { _, response, error in
+ requestError = error
+ requestResponse = response
+ completionHandlerInvokedExpectation.fulfill()
+ }
+
+ dataTask.resume()
+
+ let waitResult = XCTWaiter.wait(for: [completionHandlerInvokedExpectation], timeout: 30)
+
+ if waitResult != .completed {
+ XCTFail("Failed to create firewall rule - timeout")
+ } else {
+ if let response = requestResponse as? HTTPURLResponse {
+ if response.statusCode != 201 {
+ XCTFail("Failed to create firewall rule - unexpected server response")
+ }
+ }
+
+ if let error = requestError {
+ XCTFail("Failed to create firewall rule - encountered error \(error.localizedDescription)")
+ }
+ }
+ } catch {
+ XCTFail("Failed to create firewall rule - couldn't serialize JSON")
+ }
+ }
+
+ /// Remove all firewall rules associated to this device under test
+ public func removeRules() {
+ let removeRulesURL = baseURL.appendingPathComponent("remove-rules/\(sessionIdentifier)")
+
+ var request = URLRequest(url: removeRulesURL)
+ request.httpMethod = "DELETE"
+ request.setValue("application/json", forHTTPHeaderField: "Content-Type")
+
+ var requestResponse: URLResponse?
+ var requestError: Error?
+ let completionHandlerInvokedExpectation = XCTestExpectation(
+ description: "Completion handler for the request is invoked"
+ )
+
+ let dataTask = URLSession.shared.dataTask(with: request) { _, response, error in
+ requestResponse = response
+ requestError = error
+ completionHandlerInvokedExpectation.fulfill()
+ }
+
+ dataTask.resume()
+
+ let waitResult = XCTWaiter.wait(for: [completionHandlerInvokedExpectation], timeout: 30)
+
+ if waitResult != .completed {
+ XCTFail("Failed to remove firewall rules - timeout")
+ } else {
+ if let response = requestResponse as? HTTPURLResponse, response.statusCode != 200 {
+ XCTFail("Failed to remove firewall rules - unexpected server response")
+ }
+
+ if let error = requestError {
+ XCTFail("Failed to remove firewall rules - encountered error \(error.localizedDescription)")
+ }
+ }
+ }
+}
diff --git a/ios/MullvadVPNUITests/Networking/FirewallRule.swift b/ios/MullvadVPNUITests/Networking/FirewallRule.swift
new file mode 100644
index 0000000000..d89dbc6853
--- /dev/null
+++ b/ios/MullvadVPNUITests/Networking/FirewallRule.swift
@@ -0,0 +1,43 @@
+//
+// FirewallRule.swift
+// MullvadVPNUITests
+//
+// Created by Niklas Berglund on 2024-01-18.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import XCTest
+
+enum NetworkingProtocol: String {
+ case TCP = "tcp"
+ case UDP = "udp"
+ case ICMP = "icmp"
+}
+
+struct FirewallRule {
+ let fromIPAddress: String
+ let toIPAddress: String
+ let protocols: [NetworkingProtocol]
+
+ /// - Parameters:
+ /// - fromIPAddress: Block traffic originating from this source IP address.
+ /// - toIPAddress: Block traffic to this destination IP address.
+ /// - protocols: Protocols which should be blocked. If none is specified all will be blocked.
+ private init(fromIPAddress: String, toIPAddress: String, protocols: [NetworkingProtocol]) {
+ self.fromIPAddress = fromIPAddress
+ self.toIPAddress = toIPAddress
+ self.protocols = protocols
+ }
+
+ /// 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]
+ )
+ }
+}
diff --git a/ios/MullvadVPNUITests/Networking/MullvadAPIWrapper.swift b/ios/MullvadVPNUITests/Networking/MullvadAPIWrapper.swift
new file mode 100644
index 0000000000..71343dd4f3
--- /dev/null
+++ b/ios/MullvadVPNUITests/Networking/MullvadAPIWrapper.swift
@@ -0,0 +1,45 @@
+//
+// AppAPI.swift
+// MullvadVPNUITests
+//
+// Created by Niklas Berglund on 2024-01-18.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import XCTest
+
+enum MullvadAPIError: Error {
+ case incorrectConfigurationFormat
+}
+
+class MullvadAPIWrapper {
+ // swiftlint:disable force_cast
+ static let hostName = Bundle(for: MullvadAPIWrapper.self)
+ .infoDictionary?["ApiHostName"] as! String
+
+ /// API endpoint configuration value in the format <IP-address>:<port>
+ static let endpoint = Bundle(for: MullvadAPIWrapper.self)
+ .infoDictionary?["ApiEndpoint"] as! String
+ // swiftlint:enable force_cast
+
+ public static func getAPIHostname() -> String {
+ return hostName
+ }
+
+ public static func getAPIIPAddress() throws -> String {
+ guard let ipAddress = endpoint.components(separatedBy: ":").first else {
+ throw MullvadAPIError.incorrectConfigurationFormat
+ }
+
+ return ipAddress
+ }
+
+ public static func getAPIPort() throws -> String {
+ guard let port = endpoint.components(separatedBy: ":").last else {
+ throw MullvadAPIError.incorrectConfigurationFormat
+ }
+
+ return port
+ }
+}
diff --git a/ios/MullvadVPNUITests/Networking/Networking.swift b/ios/MullvadVPNUITests/Networking/Networking.swift
new file mode 100644
index 0000000000..e8a038b4a2
--- /dev/null
+++ b/ios/MullvadVPNUITests/Networking/Networking.swift
@@ -0,0 +1,146 @@
+//
+// Networking.swift
+// MullvadVPNUITests
+//
+// Created by Niklas Berglund on 2024-02-05.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import Network
+import XCTest
+
+enum NetworkingError: Error {
+ case notConfiguredError
+ case internalError(reason: String)
+}
+
+/// Class with methods for verifying network connectivity
+class Networking {
+ /// Get IP address of the iOS device under test
+ static func getIPAddress() throws -> String {
+ var ipAddress: String
+ // Get list of all interfaces on the local machine:
+ var interfaceList: UnsafeMutablePointer<ifaddrs>?
+ guard getifaddrs(&interfaceList) == 0, let firstInterfaceAddress = interfaceList else {
+ throw NetworkingError.internalError(reason: "Failed to locate local networking interface")
+ }
+
+ // For each interface
+ for interfacePointer in sequence(first: firstInterfaceAddress, next: { $0.pointee.ifa_next }) {
+ let flags = Int32(interfacePointer.pointee.ifa_flags)
+ let interfaceAddress = interfacePointer.pointee.ifa_addr.pointee
+
+ // Check for running IPv4 interfaces. Skip the loopback interface.
+ if (
+ flags &
+ (IFF_UP | IFF_RUNNING | IFF_LOOPBACK)
+ ) == (IFF_UP | IFF_RUNNING),
+ interfaceAddress.sa_family == UInt8(AF_INET) {
+ // Check if interface is en0 which is the WiFi connection on the iPhone
+ let name = String(cString: interfacePointer.pointee.ifa_name)
+ if name == "en0" {
+ // Convert interface address to a human readable string:
+ var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
+ if getnameinfo(
+ interfacePointer.pointee.ifa_addr,
+ socklen_t(interfaceAddress.sa_len),
+ &hostname,
+ socklen_t(hostname.count),
+ nil,
+ socklen_t(0),
+ NI_NUMERICHOST
+ ) == 0 {
+ ipAddress = String(cString: hostname)
+ return ipAddress
+ }
+ }
+ }
+ }
+
+ freeifaddrs(interfaceList)
+
+ 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
+ }
+
+ return adServingDomainURL
+ }
+
+ private static func getAdServingDomain() throws -> String {
+ guard let adServingDomain = Bundle(for: BaseUITestCase.self)
+ .infoDictionary?["AdServingDomain"] as? String else {
+ throw NetworkingError.notConfiguredError
+ }
+
+ return adServingDomain
+ }
+
+ /// Check whether host and port is reachable by attempting to connect a socket
+ private static func canConnectSocket(host: String, port: String) throws -> Bool {
+ let socketHost = NWEndpoint.Host(host)
+ let socketPort = try XCTUnwrap(NWEndpoint.Port(port))
+ let connection = NWConnection(host: socketHost, port: socketPort, using: .tcp)
+ var connectionError: Error?
+
+ let connectionStateDeterminedExpectation = XCTestExpectation(
+ description: "Completion handler for the reach ad serving domain request is invoked"
+ )
+
+ connection.stateUpdateHandler = { state in
+ print("State: \(state)")
+
+ switch state {
+ case let .failed(error):
+ connection.cancel()
+ connectionError = error
+ connectionStateDeterminedExpectation.fulfill()
+ case .ready:
+ connection.cancel()
+ connectionStateDeterminedExpectation.fulfill()
+ default:
+ break
+ }
+ }
+
+ connection.start(queue: .global())
+ let waitResult = XCTWaiter.wait(for: [connectionStateDeterminedExpectation], timeout: 15)
+
+ if waitResult != .completed || connectionError != nil {
+ return false
+ }
+
+ return true
+ }
+
+ /// Verify API can be accessed by attempting to connect a socket to the configured API host and port
+ public static func verifyCanAccessAPI() throws {
+ let apiIPAddress = try MullvadAPIWrapper.getAPIIPAddress()
+ let apiPort = try MullvadAPIWrapper.getAPIPort()
+ XCTAssertTrue(try canConnectSocket(host: apiIPAddress, port: apiPort))
+ }
+
+ /// Verify API cannot be accessed by attempting to connect a socket to the configured API host and port
+ public static func verifyCannotAccessAPI() throws {
+ let apiIPAddress = try MullvadAPIWrapper.getAPIIPAddress()
+ let apiPort = try MullvadAPIWrapper.getAPIPort()
+ XCTAssertFalse(try canConnectSocket(host: apiIPAddress, port: apiPort))
+ }
+
+ /// 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"))
+ }
+
+ /// Verify that an ad serving domain is NOT reachable by making sure a connection can not be established on port 80
+ public static func verifyCannotReachAdServingDomain() throws {
+ XCTAssertFalse(try Self.canConnectSocket(host: try Self.getAdServingDomain(), port: "80"))
+ }
+}
diff --git a/ios/MullvadVPNUITests/Pages/LoginPage.swift b/ios/MullvadVPNUITests/Pages/LoginPage.swift
index 18a44b5e74..399943fe52 100644
--- a/ios/MullvadVPNUITests/Pages/LoginPage.swift
+++ b/ios/MullvadVPNUITests/Pages/LoginPage.swift
@@ -37,12 +37,12 @@ class LoginPage: Page {
}
@discardableResult public func verifySuccessIconShown() -> Self {
- app.images.element(matching: .image, identifier: "IconSuccess")
+ _ = app.images.element(matching: .image, identifier: "IconSuccess")
return self
}
@discardableResult public func verifyFailIconShown() -> Self {
- app.images.element(matching: .image, identifier: "IconFail")
+ _ = app.images.element(matching: .image, identifier: "IconFail").waitForExistence(timeout: 15)
return self
}
}
diff --git a/ios/MullvadVPNUITests/Pages/Page.swift b/ios/MullvadVPNUITests/Pages/Page.swift
index 5a11e586e5..453bd57375 100644
--- a/ios/MullvadVPNUITests/Pages/Page.swift
+++ b/ios/MullvadVPNUITests/Pages/Page.swift
@@ -17,7 +17,7 @@ class Page {
self.app = app
}
- public func waitForPageToBeShown() {
+ func waitForPageToBeShown() {
if let pageAccessibilityIdentifier = self.pageAccessibilityIdentifier {
XCTAssert(
self.app.otherElements[pageAccessibilityIdentifier]
@@ -26,7 +26,7 @@ class Page {
}
}
- @discardableResult public func enterText(_ text: String) -> Self {
+ @discardableResult func enterText(_ text: String) -> Self {
app.typeText(text)
return self
}
@@ -36,4 +36,9 @@ class Page {
app.swipeDown(velocity: .fast)
return self
}
+
+ @discardableResult func tapKeyboardDoneButton() -> Self {
+ app.toolbars.buttons["Done"].tap()
+ return self
+ }
}
diff --git a/ios/MullvadVPNUITests/Pages/ProblemReportPage.swift b/ios/MullvadVPNUITests/Pages/ProblemReportPage.swift
new file mode 100644
index 0000000000..4bb2407cec
--- /dev/null
+++ b/ios/MullvadVPNUITests/Pages/ProblemReportPage.swift
@@ -0,0 +1,48 @@
+//
+// ProblemReportPage.swift
+// MullvadVPNUITests
+//
+// Created by Niklas Berglund on 2024-01-26.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import XCTest
+
+class ProblemReportPage: Page {
+ @discardableResult override init(_ app: XCUIApplication) {
+ super.init(app)
+
+ pageAccessibilityIdentifier = .problemReportView
+
+ waitForPageToBeShown()
+ }
+
+ @discardableResult func tapEmailTextField() -> Self {
+ app.textFields[AccessibilityIdentifier.problemReportEmailTextField]
+ .tap()
+
+ return self
+ }
+
+ @discardableResult func tapMessageTextView() -> Self {
+ app.textViews[AccessibilityIdentifier.problemReportMessageTextView]
+ .tap()
+
+ return self
+ }
+
+ @discardableResult func tapViewAppLogsButton() -> Self {
+ app.otherElements[AccessibilityIdentifier.problemReportAppLogsButton]
+ .tap()
+
+ return self
+ }
+
+ @discardableResult func tapSendButton() -> Self {
+ app.otherElements[AccessibilityIdentifier.problemReportSendButton]
+ .tap()
+
+ return self
+ }
+}
diff --git a/ios/MullvadVPNUITests/Pages/SettingsPage.swift b/ios/MullvadVPNUITests/Pages/SettingsPage.swift
index c57fd3ff04..72acac4019 100644
--- a/ios/MullvadVPNUITests/Pages/SettingsPage.swift
+++ b/ios/MullvadVPNUITests/Pages/SettingsPage.swift
@@ -24,6 +24,14 @@ class SettingsPage: Page {
return self
}
+ @discardableResult func tapReportAProblemCell() -> Self {
+ app.tables[AccessibilityIdentifier.settingsTableView]
+ .cells[AccessibilityIdentifier.problemReportCell]
+ .tap()
+
+ return self
+ }
+
@discardableResult func tapDNSSettingsCell() -> Self {
app.tables
.cells[AccessibilityIdentifier.dnsSettings]
diff --git a/ios/MullvadVPNUITests/Pages/TunnelControlPage.swift b/ios/MullvadVPNUITests/Pages/TunnelControlPage.swift
index 6469ed2232..860b928024 100644
--- a/ios/MullvadVPNUITests/Pages/TunnelControlPage.swift
+++ b/ios/MullvadVPNUITests/Pages/TunnelControlPage.swift
@@ -20,4 +20,15 @@ class TunnelControlPage: Page {
app.buttons[AccessibilityIdentifier.selectLocationButton].tap()
return self
}
+
+ @discardableResult func tapSecureConnectionButton() -> Self {
+ app.buttons[AccessibilityIdentifier.secureConnectionButton].tap()
+ return self
+ }
+
+ @discardableResult func waitForSecureConnectionLabel() -> Self {
+ _ = app.staticTexts[AccessibilityIdentifier.connectionStatusLabel]
+ .waitForExistence(timeout: BaseUITestCase.defaultTimeout)
+ return self
+ }
}
diff --git a/ios/MullvadVPNUITests/RelayTests.swift b/ios/MullvadVPNUITests/RelayTests.swift
index cb1454c801..a186bd7548 100644
--- a/ios/MullvadVPNUITests/RelayTests.swift
+++ b/ios/MullvadVPNUITests/RelayTests.swift
@@ -22,58 +22,13 @@ class RelayTests: LoggedInWithTimeUITestCase {
.swipeDownToDismissModal()
TunnelControlPage(app)
- .tapSelectLocationButton()
-
- SelectLocationPage(app)
- .tapLocationCellExpandButton(withName: "Sweden")
- .tapLocationCellExpandButton(withName: "Gothenburg")
- .tapLocationCell(withName: "se-got-wg-001")
+ .tapSecureConnectionButton()
allowAddVPNConfigurations() // Allow adding VPN configurations iOS permission
- TunnelControlPage(app) // Make sure we're taken back to tunnel control page again
-
- verifyCannotReachAdServingDomain()
- }
-
- /// Verify that an ad serving domain is reachable by making sure the host can be found when sending HTTP request to it
- func verifyCanReachAdServingDomain() {
- XCTAssertTrue(canReachAdServingDomain())
- }
-
- /// Verify that an ad serving domain is NOT reachable by making sure the host cannot be found when sending HTTP request to it
- func verifyCannotReachAdServingDomain() {
- XCTAssertFalse(canReachAdServingDomain())
- }
-
- /// Attempt to reach HTTP server on an ad serving domain
- /// - Returns: `true` if host can be resolved, otherwise `false`
- private func canReachAdServingDomain() -> Bool {
- guard let url = URL(string: "http://\(adServingDomain)") else { return false }
-
- var requestError: Error?
- var requestResponse: URLResponse?
-
- let completionHandlerInvokedExpectation = expectation(
- description: "Completion handler for the request is invoked"
- )
-
- let task = URLSession.shared.dataTask(with: url) { _, response, error in
- requestError = error
- requestResponse = response
- completionHandlerInvokedExpectation.fulfill()
- }
-
- task.resume()
-
- wait(for: [completionHandlerInvokedExpectation], timeout: 30)
-
- if let urlError = requestError as? URLError {
- if urlError.code == .cannotFindHost && requestResponse == nil {
- return false
- }
- }
+ TunnelControlPage(app)
+ .waitForSecureConnectionLabel()
- return true
+ try Networking.verifyCannotReachAdServingDomain()
}
}
diff --git a/ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift b/ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift
index cedba952dc..eae33b0805 100644
--- a/ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift
+++ b/ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift
@@ -15,17 +15,15 @@ class BaseUITestCase: XCTestCase {
// swiftlint:disable force_cast
let displayName = Bundle(for: BaseUITestCase.self)
- .infoDictionary?["MullvadDisplayName"] as! String
+ .infoDictionary?["DisplayName"] as! String
let noTimeAccountNumber = Bundle(for: BaseUITestCase.self)
- .infoDictionary?["MullvadNoTimeAccountNumber"] as! String
+ .infoDictionary?["NoTimeAccountNumber"] as! String
let hasTimeAccountNumber = Bundle(for: BaseUITestCase.self)
- .infoDictionary?["MullvadHasTimeAccountNumber"] as! String
+ .infoDictionary?["HasTimeAccountNumber"] as! String
let fiveWireGuardKeysAccountNumber = Bundle(for: BaseUITestCase.self)
- .infoDictionary?["MullvadFiveWireGuardKeysAccountNumber"] as! String
+ .infoDictionary?["FiveWireGuardKeysAccountNumber"] as! String
let iOSDevicePinCode = Bundle(for: BaseUITestCase.self)
- .infoDictionary?["MullvadIOSDevicePinCode"] as! String
- let adServingDomain = Bundle(for: BaseUITestCase.self)
- .infoDictionary?["MullvadAdServingDomain"] as! String
+ .infoDictionary?["IOSDevicePinCode"] as! String
// swiftlint:enable force_cast
/// Handle iOS add VPN configuration permission alert - allow and enter device PIN code