summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.github/actions/ios-end-to-end-tests/action.yml59
-rw-r--r--.github/workflows/ios-end-to-end-tests-settings-migration.yml116
-rw-r--r--.github/workflows/ios-end-to-end-tests.yml69
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj34
-rw-r--r--ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPNUITests.xcscheme12
-rw-r--r--ios/MullvadVPN/Classes/AccessbilityIdentifier.swift6
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsDNSTextCell.swift1
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCellFactory.swift2
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift2
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift2
-rw-r--r--ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift2
-rw-r--r--ios/MullvadVPNUITests/ConnectivityTests.swift4
-rw-r--r--ios/MullvadVPNUITests/Pages/DNSSettingsPage.swift165
-rw-r--r--ios/MullvadVPNUITests/Pages/SettingsPage.swift24
-rw-r--r--ios/MullvadVPNUITests/Pages/VPNSettingsPage.swift80
-rw-r--r--ios/MullvadVPNUITests/README.md22
-rw-r--r--ios/MullvadVPNUITests/RelayTests.swift6
-rw-r--r--ios/MullvadVPNUITests/SettingsMigrationTests.swift138
-rw-r--r--ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift14
-rw-r--r--ios/MullvadVPNUITests/XCUIElement+Extensions.swift18
-rw-r--r--ios/TestPlans/MullvadVPNUITestsAll.xctestplan3
-rw-r--r--ios/TestPlans/MullvadVPNUITestsChangeDNSSettings.xctestplan28
-rw-r--r--ios/TestPlans/MullvadVPNUITestsChangeSettings.xctestplan28
-rw-r--r--ios/TestPlans/MullvadVPNUITestsVerifyDNSSettingsChanged.xctestplan28
-rw-r--r--ios/TestPlans/MullvadVPNUITestsVerifySettingsChanged.xctestplan28
25 files changed, 811 insertions, 80 deletions
diff --git a/.github/actions/ios-end-to-end-tests/action.yml b/.github/actions/ios-end-to-end-tests/action.yml
new file mode 100644
index 0000000000..9d771d151c
--- /dev/null
+++ b/.github/actions/ios-end-to-end-tests/action.yml
@@ -0,0 +1,59 @@
+name: 'iOS end to end tests action'
+description: 'Prepares and runs end to end tests on iOS device'
+inputs:
+ ios_device_pin_code:
+ description: 'iOS Device Pin Code'
+ required: true
+ test_device_identifier_uuid:
+ description: 'Test Device Identifier UUID'
+ required: true
+ has_time_account_number:
+ description: 'Has Time Account Number'
+ required: true
+ no_time_account_number:
+ description: 'No Time Account Number'
+ required: true
+ test_device_udid:
+ description: 'Test Device UDID'
+ required: true
+ xcode_test_plan:
+ description: 'Xcode Test Plan to run'
+ required: true
+
+runs:
+ using: 'composite'
+ steps:
+ - name: Configure Xcode project
+ run: |
+ for file in *.xcconfig.template ; do cp $file ${file//.template/} ; done
+ sed -i "" "/^HAS_TIME_ACCOUNT_NUMBER/d" UITests.xcconfig
+ sed -i "" "/^NO_TIME_ACCOUNT_NUMBER/d" UITests.xcconfig
+ sed -i "" \
+ "/IOS_DEVICE_PIN_CODE =/ s/= .*/= $IOS_DEVICE_PIN_CODE/" \
+ UITests.xcconfig
+ sed -i "" \
+ "/TEST_DEVICE_IDENTIFIER_UUID =/ s/= .*/= $TEST_DEVICE_IDENTIFIER_UUID/" \
+ UITests.xcconfig
+ echo -e "\nHAS_TIME_ACCOUNT_NUMBER = $HAS_TIME_ACCOUNT_NUMBER" >> UITests.xcconfig
+ echo "NO_TIME_ACCOUNT_NUMBER = $NO_TIME_ACCOUNT_NUMBER" >> UITests.xcconfig
+ shell: bash
+ working-directory: ios/Configurations
+ env:
+ IOS_DEVICE_PIN_CODE: ${{ inputs.ios_device_pin_code }}
+ TEST_DEVICE_IDENTIFIER_UUID: ${{ inputs.test_device_identifier_uuid }}
+ HAS_TIME_ACCOUNT_NUMBER: ${{ inputs.has_time_account_number }}
+ NO_TIME_ACCOUNT_NUMBER: ${{ inputs.no_time_account_number }}
+
+ - name: Run end-to-end-tests
+ run: |
+ set -o pipefail && env NSUnbufferedIO=YES xcodebuild \
+ -project MullvadVPN.xcodeproj \
+ -scheme MullvadVPNUITests \
+ -testPlan $XCODE_TEST_PLAN \
+ -destination "platform=iOS,id=$TEST_DEVICE_UDID" \
+ clean test 2>&1 | xcbeautify --report junit --report-path test-report
+ shell: bash
+ working-directory: ios/
+ env:
+ XCODE_TEST_PLAN: ${{ inputs.xcode_test_plan }}
+ TEST_DEVICE_UDID: ${{ inputs.test_device_udid }}
diff --git a/.github/workflows/ios-end-to-end-tests-settings-migration.yml b/.github/workflows/ios-end-to-end-tests-settings-migration.yml
new file mode 100644
index 0000000000..180f454cec
--- /dev/null
+++ b/.github/workflows/ios-end-to-end-tests-settings-migration.yml
@@ -0,0 +1,116 @@
+---
+name: iOS settings migration tests
+permissions:
+ contents: read
+on:
+ workflow_dispatch:
+ schedule:
+ # At midnight every day.
+ # Notifications for scheduled workflows are sent to the user who last modified the cron
+ # syntax in the workflow file. If you update this you must have notifications for
+ # Github Actions enabled, so these don't go unnoticed.
+ # https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/notifications-for-workflow-runs
+ - cron: '0 0 * * *'
+jobs:
+ test:
+ name: Settings migration end to end tests
+ runs-on: [self-hosted, macOS, ios-test]
+ env:
+ OLD_APP_COMMIT_HASH: f741bf159d5719301e4d1fd099b3d353457532fd
+ steps:
+ - name: Configure Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ override: true
+ target: aarch64-apple-ios
+
+ - name: Uninstall app
+ run: ios-deploy --id ${{ secrets.IOS_TEST_DEVICE_UDID }} --uninstall_only --bundle_id net.mullvad.MullvadVPN
+
+ - name: Checkout old repository version
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ env.OLD_APP_COMMIT_HASH }}
+
+ - name: Change DNS settings on old app version
+ uses: ./.github/actions/ios-end-to-end-tests
+ with:
+ ios_device_pin_code: ${{ secrets.IOS_DEVICE_PIN_CODE }}
+ test_device_identifier_uuid: ${{ secrets.IOS_TEST_DEVICE_IDENTIFIER_UUID }}
+ has_time_account_number: ${{ secrets.IOS_HAS_TIME_ACCOUNT_NUMBER_PRODUCTION }}
+ no_time_account_number: ${{ secrets.IOS_NO_TIME_ACCOUNT_NUMBER_PRODUCTION }}
+ test_device_udid: ${{ secrets.IOS_TEST_DEVICE_UDID }}
+ xcode_test_plan: 'MullvadVPNUITestsChangeDNSSettings'
+
+ - name: Store test report for changing DNS settings
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: test-report-change-dns-settings
+ path: ios/test-report/junit.xml
+
+ - name: Checkout repository to get the current app version
+ uses: actions/checkout@v4
+
+ - name: Verify DNS settings still changed on current app version
+ uses: ./.github/actions/ios-end-to-end-tests
+ if: always()
+ with:
+ ios_device_pin_code: ${{ secrets.IOS_DEVICE_PIN_CODE }}
+ test_device_identifier_uuid: ${{ secrets.IOS_TEST_DEVICE_IDENTIFIER_UUID }}
+ has_time_account_number: ${{ secrets.IOS_HAS_TIME_ACCOUNT_NUMBER_PRODUCTION }}
+ no_time_account_number: ${{ secrets.IOS_NO_TIME_ACCOUNT_NUMBER_PRODUCTION }}
+ test_device_udid: ${{ secrets.IOS_TEST_DEVICE_UDID }}
+ xcode_test_plan: 'MullvadVPNUITestsVerifyDNSSettingsChanged'
+
+ - name: Store test report for verifying DNS settings
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: test-report-verify-dns-settings
+ path: ios/test-report/junit.xml
+
+ - name: Checkout old repository version
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ env.OLD_APP_COMMIT_HASH }}
+
+ - name: Change all other settings on old app version
+ uses: ./.github/actions/ios-end-to-end-tests
+ if: always()
+ with:
+ ios_device_pin_code: ${{ secrets.IOS_DEVICE_PIN_CODE }}
+ test_device_identifier_uuid: ${{ secrets.IOS_TEST_DEVICE_IDENTIFIER_UUID }}
+ has_time_account_number: ${{ secrets.IOS_HAS_TIME_ACCOUNT_NUMBER_PRODUCTION }}
+ no_time_account_number: ${{ secrets.IOS_NO_TIME_ACCOUNT_NUMBER_PRODUCTION }}
+ test_device_udid: ${{ secrets.IOS_TEST_DEVICE_UDID }}
+ xcode_test_plan: 'MullvadVPNUITestsChangeSettings'
+
+ - name: Store test report for changing all settings
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: test-report-change-all-other-settings
+ path: ios/test-report/junit.xml
+
+ - name: Checkout repository to get the current app version
+ uses: actions/checkout@v4
+
+ - name: Verify all other settings still changed on current app version
+ uses: ./.github/actions/ios-end-to-end-tests
+ if: always()
+ with:
+ ios_device_pin_code: ${{ secrets.IOS_DEVICE_PIN_CODE }}
+ test_device_identifier_uuid: ${{ secrets.IOS_TEST_DEVICE_IDENTIFIER_UUID }}
+ has_time_account_number: ${{ secrets.IOS_HAS_TIME_ACCOUNT_NUMBER_PRODUCTION }}
+ no_time_account_number: ${{ secrets.IOS_NO_TIME_ACCOUNT_NUMBER_PRODUCTION }}
+ test_device_udid: ${{ secrets.IOS_TEST_DEVICE_UDID }}
+ xcode_test_plan: 'MullvadVPNUITestsVerifySettingsChanged'
+
+ - name: Store test report for verifying all other settings
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: test-report-verify-all-other-settings
+ path: ios/test-report/junit.xml
diff --git a/.github/workflows/ios-end-to-end-tests.yml b/.github/workflows/ios-end-to-end-tests.yml
index 6f953cd346..b44665649f 100644
--- a/.github/workflows/ios-end-to-end-tests.yml
+++ b/.github/workflows/ios-end-to-end-tests.yml
@@ -14,56 +14,51 @@ on:
- .github/workflows/ios-end-to-end-tests.yml
- ios/**
workflow_dispatch:
+ schedule:
+ # At midnight every day.
+ # Notifications for scheduled workflows are sent to the user who last modified the cron
+ # syntax in the workflow file. If you update this you must have notifications for
+ # Github Actions enabled, so these don't go unnoticed.
+ # https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/notifications-for-workflow-runs
+ - cron: '0 0 * * *'
jobs:
test:
if: github.event.pull_request.merged || github.event_name == 'workflow_dispatch'
name: End to end tests
runs-on: [self-hosted, macOS, ios-test]
- env:
- IOS_DEVICE_PIN_CODE: ${{ secrets.IOS_DEVICE_PIN_CODE }}
- TEST_DEVICE_IDENTIFIER_UUID: ${{ secrets.IOS_TEST_DEVICE_IDENTIFIER_UUID }}
- TEST_DEVICE_UDID: ${{ secrets.IOS_TEST_DEVICE_UDID }}
- HAS_TIME_ACCOUNT_NUMBER: ${{ secrets.IOS_HAS_TIME_ACCOUNT_NUMBER_PRODUCTION }}
- NO_TIME_ACCOUNT_NUMBER: ${{ secrets.IOS_NO_TIME_ACCOUNT_NUMBER_PRODUCTION }}
steps:
+ - name: Configure Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ override: true
+ target: aarch64-apple-ios
+
- name: Checkout repository
uses: actions/checkout@v4
- - name: Configure Rust
- run: |
- rustup default stable
- rustup update stable
- rustup target add aarch64-apple-ios aarch64-apple-ios-sim
-
- - name: Configure Xcode project
+ - name: Select test plan to execute
run: |
- for file in *.xcconfig.template ; do cp $file ${file//.template/} ; done
- sed -i "" "/^HAS_TIME_ACCOUNT_NUMBER/d" UITests.xcconfig
- sed -i "" "/^NO_TIME_ACCOUNT_NUMBER/d" UITests.xcconfig
- sed -i "" \
- "/IOS_DEVICE_PIN_CODE =/ s/= .*/= $IOS_DEVICE_PIN_CODE/" \
- UITests.xcconfig
- sed -i "" \
- "/TEST_DEVICE_IDENTIFIER_UUID =/ s/= .*/= $TEST_DEVICE_IDENTIFIER_UUID/" \
- UITests.xcconfig
- echo -e "\nHAS_TIME_ACCOUNT_NUMBER = $HAS_TIME_ACCOUNT_NUMBER" >> UITests.xcconfig
- echo "NO_TIME_ACCOUNT_NUMBER = $NO_TIME_ACCOUNT_NUMBER" >> UITests.xcconfig
- working-directory: ios/Configurations
+ if [[ "${{ github.event_name }}" == "pull_request" ]]; then
+ echo "XCODE_TEST_PLAN=MullvadVPNUITestsSmoke" >> $GITHUB_ENV
+ elif [[ "${{ github.event_name }}" == "schedule" ]]; then
+ echo "XCODE_TEST_PLAN=MullvadVPNUITestsAll" >> $GITHUB_ENV
+ elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
+ echo "XCODE_TEST_PLAN=MullvadVPNUITestsAll" >> $GITHUB_ENV
+ fi
- - name: Run end-to-end-tests
- run: |
- set -o pipefail && env NSUnbufferedIO=YES xcodebuild \
- -project MullvadVPN.xcodeproj \
- -scheme MullvadVPNUITests \
- -configuration Debug \
- -testPlan MullvadVPNUITestsSmoke \
- -destination "platform=iOS,id=$TEST_DEVICE_UDID" \
- -disableAutomaticPackageResolution \
- test 2>&1 | xcbeautify --report junit --report-path test-report
- working-directory: ios/
+ - name: iOS end to end tests action
+ uses: ./.github/actions/ios-end-to-end-tests
+ with:
+ xcode_test_plan: ${{ env.XCODE_TEST_PLAN }}
+ ios_device_pin_code: ${{ secrets.IOS_DEVICE_PIN_CODE }}
+ test_device_identifier_uuid: ${{ secrets.IOS_TEST_DEVICE_IDENTIFIER_UUID }}
+ has_time_account_number: ${{ secrets.IOS_HAS_TIME_ACCOUNT_NUMBER_PRODUCTION }}
+ no_time_account_number: ${{ secrets.IOS_NO_TIME_ACCOUNT_NUMBER_PRODUCTION }}
+ test_device_udid: ${{ secrets.IOS_TEST_DEVICE_UDID }}
- name: Comment PR on test failure
- if: failure() && github.event_name != 'workflow_dispatch'
+ if: failure() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
github-token: ${{secrets.GITHUB_TOKEN}}
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index f1ee25f1a1..af62482d12 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -617,6 +617,11 @@
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 */; };
+ 852A26462BA9C9CB006EB9C8 /* DNSSettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852A26452BA9C9CB006EB9C8 /* DNSSettingsPage.swift */; };
+ 852BC66F2BAB44F500A47558 /* MullvadVPNUITestsVerifySettingsChanged.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 852BC66C2BAB44F500A47558 /* MullvadVPNUITestsVerifySettingsChanged.xctestplan */; };
+ 852BC6702BAB44F500A47558 /* MullvadVPNUITestsChangeSettings.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 852BC66D2BAB44F500A47558 /* MullvadVPNUITestsChangeSettings.xctestplan */; };
+ 852BC6712BAB44F500A47558 /* MullvadVPNUITestsVerifyDNSSettingsChanged.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 852BC66E2BAB44F500A47558 /* MullvadVPNUITestsVerifyDNSSettingsChanged.xctestplan */; };
+ 852BC6732BAB450B00A47558 /* MullvadVPNUITestsChangeDNSSettings.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 852BC6722BAB450B00A47558 /* MullvadVPNUITestsChangeDNSSettings.xctestplan */; };
8532E6872B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8532E6862B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift */; };
8542CE242B95F7B9006FCA14 /* VPNSettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */; };
85557B0E2B591B2600795FE1 /* FirewallAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B0D2B591B2600795FE1 /* FirewallAPIClient.swift */; };
@@ -632,8 +637,10 @@
8587A05D2B84D43100152938 /* ChangeLogAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8587A05C2B84D43100152938 /* ChangeLogAlert.swift */; };
8590896C2B61763B003AF5F5 /* LoggedInWithoutTimeUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 859089682B61763B003AF5F5 /* LoggedInWithoutTimeUITestCase.swift */; };
8590896F2B61763B003AF5F5 /* LoggedOutUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590896B2B61763B003AF5F5 /* LoggedOutUITestCase.swift */; };
+ 85A42B862BB1D627007BABF7 /* XCUIElement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A42B852BB1D627007BABF7 /* XCUIElement+Extensions.swift */; };
85B267612B849ADB0098E3CD /* mullvad-api.h in Headers */ = {isa = PBXBuildFile; fileRef = 85B267602B849ADB0098E3CD /* mullvad-api.h */; };
85C7A2E92B89024B00035D5A /* SettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C7A2E82B89024B00035D5A /* SettingsTests.swift */; };
+ 85D039982BA4711800940E7F /* SettingsMigrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D039972BA4711800940E7F /* SettingsMigrationTests.swift */; };
85D2B0B12B6BD32400DF9DA7 /* BaseUITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590896A2B61763B003AF5F5 /* BaseUITestCase.swift */; };
85E3BDE52B70E18C00FA71FD /* Networking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E3BDE42B70E18C00FA71FD /* Networking.swift */; };
85EC620C2B838D10005AFFB5 /* MullvadAPIWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B132B5983CF00795FE1 /* MullvadAPIWrapper.swift */; };
@@ -1861,6 +1868,11 @@
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>"; };
+ 852A26452BA9C9CB006EB9C8 /* DNSSettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSSettingsPage.swift; sourceTree = "<group>"; };
+ 852BC66C2BAB44F500A47558 /* MullvadVPNUITestsVerifySettingsChanged.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNUITestsVerifySettingsChanged.xctestplan; sourceTree = "<group>"; };
+ 852BC66D2BAB44F500A47558 /* MullvadVPNUITestsChangeSettings.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNUITestsChangeSettings.xctestplan; sourceTree = "<group>"; };
+ 852BC66E2BAB44F500A47558 /* MullvadVPNUITestsVerifyDNSSettingsChanged.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNUITestsVerifyDNSSettingsChanged.xctestplan; sourceTree = "<group>"; };
+ 852BC6722BAB450B00A47558 /* MullvadVPNUITestsChangeDNSSettings.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNUITestsChangeDNSSettings.xctestplan; sourceTree = "<group>"; };
8532E6862B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportSubmittedPage.swift; sourceTree = "<group>"; };
8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNSettingsPage.swift; sourceTree = "<group>"; };
85557B0D2B591B2600795FE1 /* FirewallAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirewallAPIClient.swift; sourceTree = "<group>"; };
@@ -1879,8 +1891,10 @@
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>"; };
+ 85A42B852BB1D627007BABF7 /* XCUIElement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extensions.swift"; sourceTree = "<group>"; };
85B267602B849ADB0098E3CD /* mullvad-api.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "mullvad-api.h"; path = "../../mullvad-api/include/mullvad-api.h"; sourceTree = "<group>"; };
85C7A2E82B89024B00035D5A /* SettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = "<group>"; };
+ 85D039972BA4711800940E7F /* SettingsMigrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsMigrationTests.swift; sourceTree = "<group>"; };
85E3BDE42B70E18C00FA71FD /* Networking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Networking.swift; sourceTree = "<group>"; };
85FB5A0B2B6903990015DCED /* WelcomePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomePage.swift; sourceTree = "<group>"; };
85FB5A0F2B6960A30015DCED /* AccountDeletionPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletionPage.swift; sourceTree = "<group>"; };
@@ -3508,11 +3522,15 @@
7A83C3FC2A55B39500DFB83A /* TestPlans */ = {
isa = PBXGroup;
children = (
- 852969302B4D9E70007EAD4C /* MullvadVPNUITestsAll.xctestplan */,
- 85006A8E2B73EF67004AD8FB /* MullvadVPNUITestsSmoke.xctestplan */,
7A83C3FE2A55B72E00DFB83A /* MullvadVPNApp.xctestplan */,
7A83C4002A55B81A00DFB83A /* MullvadVPNCI.xctestplan */,
7A02D4EA2A9CEC7A00C19E31 /* MullvadVPNScreenshots.xctestplan */,
+ 852969302B4D9E70007EAD4C /* MullvadVPNUITestsAll.xctestplan */,
+ 852BC6722BAB450B00A47558 /* MullvadVPNUITestsChangeDNSSettings.xctestplan */,
+ 852BC66D2BAB44F500A47558 /* MullvadVPNUITestsChangeSettings.xctestplan */,
+ 85006A8E2B73EF67004AD8FB /* MullvadVPNUITestsSmoke.xctestplan */,
+ 852BC66E2BAB44F500A47558 /* MullvadVPNUITestsVerifyDNSSettingsChanged.xctestplan */,
+ 852BC66C2BAB44F500A47558 /* MullvadVPNUITestsVerifySettingsChanged.xctestplan */,
);
path = TestPlans;
sourceTree = "<group>";
@@ -3587,7 +3605,9 @@
850201DA2B503D7700EF8C96 /* RelayTests.swift */,
8518F6392B601910009EB113 /* Test base classes */,
85557B152B5ABBBE00795FE1 /* XCUIElementQuery+Extensions.swift */,
+ 85A42B852BB1D627007BABF7 /* XCUIElement+Extensions.swift */,
85C7A2E82B89024B00035D5A /* SettingsTests.swift */,
+ 85D039972BA4711800940E7F /* SettingsMigrationTests.swift */,
);
path = MullvadVPNUITests;
sourceTree = "<group>";
@@ -3599,6 +3619,7 @@
85557B1F2B5FBBD700795FE1 /* AccountPage.swift */,
8529693B2B4F0257007EAD4C /* Alert.swift */,
8587A05C2B84D43100152938 /* ChangeLogAlert.swift */,
+ 852A26452BA9C9CB006EB9C8 /* DNSSettingsPage.swift */,
85557B1D2B5FB8C700795FE1 /* HeaderBar.swift */,
852969342B4E9270007EAD4C /* LoginPage.swift */,
85139B2C2B84B4A700734217 /* OutOfTimePage.swift */,
@@ -3610,8 +3631,8 @@
850201E22B51A93C00EF8C96 /* SettingsPage.swift */,
852969392B4F0238007EAD4C /* TermsOfServicePage.swift */,
850201DE2B5040A500EF8C96 /* TunnelControlPage.swift */,
- 85FB5A0B2B6903990015DCED /* WelcomePage.swift */,
8542CE232B95F7B9006FCA14 /* VPNSettingsPage.swift */,
+ 85FB5A0B2B6903990015DCED /* WelcomePage.swift */,
);
path = Pages;
sourceTree = "<group>";
@@ -4564,6 +4585,10 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 852BC66F2BAB44F500A47558 /* MullvadVPNUITestsVerifySettingsChanged.xctestplan in Resources */,
+ 852BC6712BAB44F500A47558 /* MullvadVPNUITestsVerifyDNSSettingsChanged.xctestplan in Resources */,
+ 852BC6702BAB44F500A47558 /* MullvadVPNUITestsChangeSettings.xctestplan in Resources */,
+ 852BC6732BAB450B00A47558 /* MullvadVPNUITestsChangeDNSSettings.xctestplan in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -5580,8 +5605,10 @@
85D2B0B12B6BD32400DF9DA7 /* BaseUITestCase.swift in Sources */,
8529693C2B4F0257007EAD4C /* Alert.swift in Sources */,
850201DD2B503D8C00EF8C96 /* SelectLocationPage.swift in Sources */,
+ 85D039982BA4711800940E7F /* SettingsMigrationTests.swift in Sources */,
850201DB2B503D7700EF8C96 /* RelayTests.swift in Sources */,
85139B2D2B84B4A700734217 /* OutOfTimePage.swift in Sources */,
+ 85A42B862BB1D627007BABF7 /* XCUIElement+Extensions.swift in Sources */,
852969362B4E9724007EAD4C /* AccessbilityIdentifier.swift in Sources */,
85E3BDE52B70E18C00FA71FD /* Networking.swift in Sources */,
85C7A2E92B89024B00035D5A /* SettingsTests.swift in Sources */,
@@ -5600,6 +5627,7 @@
8529693A2B4F0238007EAD4C /* TermsOfServicePage.swift in Sources */,
8532E6872B8CCED600ACECD1 /* ProblemReportSubmittedPage.swift in Sources */,
85FB5A0C2B6903990015DCED /* WelcomePage.swift in Sources */,
+ 852A26462BA9C9CB006EB9C8 /* DNSSettingsPage.swift in Sources */,
850201DF2B5040A500EF8C96 /* TunnelControlPage.swift in Sources */,
8542CE242B95F7B9006FCA14 /* VPNSettingsPage.swift in Sources */,
85557B1E2B5FB8C700795FE1 /* HeaderBar.swift in Sources */,
diff --git a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPNUITests.xcscheme b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPNUITests.xcscheme
index 7603c93737..a206bbdc71 100644
--- a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPNUITests.xcscheme
+++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPNUITests.xcscheme
@@ -19,6 +19,18 @@
<TestPlanReference
reference = "container:TestPlans/MullvadVPNUITestsSmoke.xctestplan">
</TestPlanReference>
+ <TestPlanReference
+ reference = "container:TestPlans/MullvadVPNUITestsVerifyDNSSettingsChanged.xctestplan">
+ </TestPlanReference>
+ <TestPlanReference
+ reference = "container:TestPlans/MullvadVPNUITestsChangeSettings.xctestplan">
+ </TestPlanReference>
+ <TestPlanReference
+ reference = "container:TestPlans/MullvadVPNUITestsVerifySettingsChanged.xctestplan">
+ </TestPlanReference>
+ <TestPlanReference
+ reference = "container:TestPlans/MullvadVPNUITestsChangeDNSSettings.xctestplan">
+ </TestPlanReference>
</TestPlans>
<Testables>
<TestableReference
diff --git a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
index 9ee844b906..1ddd40663d 100644
--- a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
+++ b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
@@ -21,6 +21,7 @@ public enum AccessibilityIdentifier: String {
case deleteButton
case disconnectButton
case revokedDeviceLoginButton
+ case dnsSettingsEditButton
case infoButton
case learnAboutPrivacyButton
case loginBarButton
@@ -40,6 +41,9 @@ public enum AccessibilityIdentifier: String {
// Cells
case vpnSettingsCell
+ case dnsSettingsAddServerCell
+ case dnsSettingsUseCustomDNSCell
+ case preferencesCell
case versionCell
case problemReportCell
case faqCell
@@ -84,6 +88,7 @@ public enum AccessibilityIdentifier: String {
case customSwitch
case customWireGuardPortTextField
case dnsContentBlockersHeaderView
+ case dnsSettingsEnterIPAddressTextField
case loginTextField
case selectLocationSearchTextField
case problemReportEmailTextField
@@ -95,6 +100,7 @@ public enum AccessibilityIdentifier: String {
case ipOverrides
case wireGuardCustomPort
case wireGuardObfuscationAutomatic
+ case wireGuardObfuscationPort
case wireGuardObfuscationOff
case wireGuardObfuscationOn
case wireGuardPort
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsDNSTextCell.swift b/ios/MullvadVPN/View controllers/Settings/SettingsDNSTextCell.swift
index 039d3b34ec..dc03559b84 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsDNSTextCell.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsDNSTextCell.swift
@@ -34,6 +34,7 @@ class SettingsDNSTextCell: SettingsCell, UITextFieldDelegate {
value: "Enter IP",
comment: ""
)
+ textField.accessibilityIdentifier = .dnsSettingsEnterIPAddressTextField
textField.cornerRadius = 0
textField.keyboardType = .numbersAndPunctuation
textField.returnKeyType = .done
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCellFactory.swift b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCellFactory.swift
index a45be6a538..6de5f04ddd 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCellFactory.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSCellFactory.swift
@@ -161,6 +161,7 @@ final class CustomDNSCellFactory: CellFactoryProtocol {
cell.setOn(viewModel.effectiveEnableCustomDNS, animated: false)
cell.accessibilityHint = viewModel.customDNSPrecondition
.localizedDescription(isEditing: isEditing)
+ cell.accessibilityIdentifier = .dnsSettingsUseCustomDNSCell
cell.action = { [weak self] isOn in
self?.delegate?.didChangeState(for: .useCustomDNS, isOn: isOn)
}
@@ -174,6 +175,7 @@ final class CustomDNSCellFactory: CellFactoryProtocol {
value: "Add a server",
comment: ""
)
+ cell.accessibilityIdentifier = .dnsSettingsAddServerCell
cell.action = { [weak self] in
self?.delegate?.addDNSEntry()
}
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift
index 53a52275ea..67e6ffce19 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift
@@ -47,7 +47,9 @@ class CustomDNSViewController: UITableViewController, VPNSettingsDataSourceDeleg
value: "DNS settings",
comment: ""
)
+
navigationItem.rightBarButtonItem = editButtonItem
+ navigationItem.rightBarButtonItem?.accessibilityIdentifier = .dnsSettingsEditButton
interactor.tunnelSettingsDidChange = { [weak self] newSettings in
self?.dataSource?.update(from: newSettings)
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift
index 198aca720c..bd3b42b76a 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsCellFactory.swift
@@ -167,7 +167,7 @@ final class VPNSettingsCellFactory: CellFactoryProtocol {
value: portString,
comment: ""
)
- cell.accessibilityIdentifier = "\(item.accessibilityIdentifier.rawValue) (\(portString))"
+ cell.accessibilityIdentifier = "\(item.accessibilityIdentifier.rawValue)\(portString)"
cell.applySubCellStyling()
#if DEBUG
diff --git a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
index a87f2ebd96..a3f5b16747 100644
--- a/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
+++ b/ios/MullvadVPN/View controllers/VPNSettings/VPNSettingsDataSource.swift
@@ -115,7 +115,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case .wireGuardObfuscationOff:
return .wireGuardObfuscationOff
case .wireGuardObfuscationPort:
- return .wireGuardObfuscationAutomatic
+ return .wireGuardObfuscationPort
#if DEBUG
case .quantumResistanceAutomatic:
return .quantumResistanceAutomatic
diff --git a/ios/MullvadVPNUITests/ConnectivityTests.swift b/ios/MullvadVPNUITests/ConnectivityTests.swift
index 1c4e46ac14..9288f84f5d 100644
--- a/ios/MullvadVPNUITests/ConnectivityTests.swift
+++ b/ios/MullvadVPNUITests/ConnectivityTests.swift
@@ -13,10 +13,6 @@ import XCTest
class ConnectivityTests: LoggedOutUITestCase {
let firewallAPIClient = FirewallAPIClient()
- override func setUpWithError() throws {
- super.setUp()
- }
-
override func tearDownWithError() throws {
super.tearDown()
firewallAPIClient.removeRules()
diff --git a/ios/MullvadVPNUITests/Pages/DNSSettingsPage.swift b/ios/MullvadVPNUITests/Pages/DNSSettingsPage.swift
new file mode 100644
index 0000000000..9c994921fb
--- /dev/null
+++ b/ios/MullvadVPNUITests/Pages/DNSSettingsPage.swift
@@ -0,0 +1,165 @@
+//
+// DNSSettingsPage.swift
+// MullvadVPNUITests
+//
+// Created by Niklas Berglund on 2024-03-19.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import XCTest
+
+class DNSSettingsPage: Page {
+ @discardableResult override init(_ app: XCUIApplication) {
+ super.init(app)
+
+ self.pageAccessibilityIdentifier = .dnsSettings
+ }
+
+ private func assertSwitchOn(accessibilityIdentifier: AccessibilityIdentifier) -> Self {
+ let switchElement = app.cells[accessibilityIdentifier]
+ .switches[AccessibilityIdentifier.customSwitch]
+
+ guard let switchValue = switchElement.value as? String else {
+ XCTFail("Failed to read switch state")
+ return self
+ }
+
+ XCTAssertEqual(switchValue, "1")
+
+ return self
+ }
+
+ @discardableResult func tapBackButton() -> Self {
+ // Workaround for setting accessibility identifier on navigation bar button being non-trivial
+ app.buttons.matching(identifier: "VPN settings").allElementsBoundByIndex.last?.tap()
+ return self
+ }
+
+ @discardableResult func tapDNSContentBlockersHeaderExpandButton() -> Self {
+ let headerView = app.otherElements[AccessibilityIdentifier.dnsContentBlockersHeaderView]
+ let expandButton = headerView.buttons[AccessibilityIdentifier.collapseButton]
+ expandButton.tap()
+
+ return self
+ }
+
+ @discardableResult func tapUseCustomDNSSwitch() -> Self {
+ app.cells[AccessibilityIdentifier.dnsSettingsUseCustomDNSCell]
+ .switches[AccessibilityIdentifier.customSwitch]
+ .tap()
+
+ return self
+ }
+
+ @discardableResult func tapBlockAdsSwitch() -> Self {
+ app.cells[AccessibilityIdentifier.blockAdvertising]
+ .switches[AccessibilityIdentifier.customSwitch]
+ .tap()
+
+ return self
+ }
+
+ @discardableResult func tapBlockTrackerSwitch() -> Self {
+ app.cells[AccessibilityIdentifier.blockTracking]
+ .switches[AccessibilityIdentifier.customSwitch]
+ .tap()
+
+ return self
+ }
+
+ @discardableResult func tapBlockMalwareSwitch() -> Self {
+ app.cells[AccessibilityIdentifier.blockMalware]
+ .switches[AccessibilityIdentifier.customSwitch]
+ .tap()
+
+ return self
+ }
+
+ @discardableResult func tapBlockAdultContentSwitch() -> Self {
+ app.cells[AccessibilityIdentifier.blockAdultContent]
+ .switches[AccessibilityIdentifier.customSwitch]
+ .tap()
+
+ return self
+ }
+
+ @discardableResult func tapBlockGamblingSwitch() -> Self {
+ app.cells[AccessibilityIdentifier.blockGambling]
+ .switches[AccessibilityIdentifier.customSwitch]
+ .tap()
+
+ return self
+ }
+
+ @discardableResult func tapBlockSocialMediaSwitch() -> Self {
+ app.cells[AccessibilityIdentifier.blockSocialMedia]
+ .switches[AccessibilityIdentifier.customSwitch]
+ .tap()
+
+ return self
+ }
+
+ @discardableResult func tapEditButton() -> Self {
+ app.buttons[AccessibilityIdentifier.dnsSettingsEditButton]
+ .tap()
+ return self
+ }
+
+ @discardableResult func tapDoneButton() -> Self {
+ return self.tapEditButton()
+ }
+
+ @discardableResult func tapAddAServer() -> Self {
+ app.cells[AccessibilityIdentifier.dnsSettingsAddServerCell]
+ .tap()
+ return self
+ }
+
+ @discardableResult func tapEnterIPAddressTextField() -> Self {
+ app.textFields[AccessibilityIdentifier.dnsSettingsEnterIPAddressTextField]
+ .tap()
+ return self
+ }
+
+ @discardableResult func verifyBlockAdsSwitchOn() -> Self {
+ return assertSwitchOn(accessibilityIdentifier: .blockAdvertising)
+ }
+
+ @discardableResult func verifyBlockTrackerSwitchOn() -> Self {
+ return assertSwitchOn(accessibilityIdentifier: .blockTracking)
+ }
+
+ @discardableResult func verifyBlockMalwareSwitchOn() -> Self {
+ return assertSwitchOn(accessibilityIdentifier: .blockMalware)
+ }
+
+ @discardableResult func verifyBlockAdultContentSwitchOn() -> Self {
+ return assertSwitchOn(accessibilityIdentifier: .blockAdultContent)
+ }
+
+ @discardableResult func verifyBlockGamblingSwitchOn() -> Self {
+ return assertSwitchOn(accessibilityIdentifier: .blockGambling)
+ }
+
+ @discardableResult func verifyBlockSocialMediaSwitchOn() -> Self {
+ return assertSwitchOn(accessibilityIdentifier: .blockSocialMedia)
+ }
+
+ @discardableResult func verifyUseCustomDNSSwitchOn() -> Self {
+ return assertSwitchOn(accessibilityIdentifier: .dnsSettingsUseCustomDNSCell)
+ }
+
+ /// Verify that the UI shows stored DNS server IP address same as `ipAddress`. Note that this function assumes there is only one custom DNS server IP address stored.
+ @discardableResult func verifyCustomDNSIPAddress(_ ipAddress: String) -> Self {
+ let textField = app.textFields[AccessibilityIdentifier.dnsSettingsEnterIPAddressTextField]
+
+ guard let settingIPAddress = textField.value as? String else {
+ XCTFail("Failed to read configured DNS IP address")
+ return self
+ }
+
+ XCTAssertEqual(ipAddress, settingIPAddress)
+ return self
+ }
+}
diff --git a/ios/MullvadVPNUITests/Pages/SettingsPage.swift b/ios/MullvadVPNUITests/Pages/SettingsPage.swift
index b65ca02f81..6ed383afa9 100644
--- a/ios/MullvadVPNUITests/Pages/SettingsPage.swift
+++ b/ios/MullvadVPNUITests/Pages/SettingsPage.swift
@@ -39,28 +39,4 @@ class SettingsPage: Page {
return self
}
-
- @discardableResult func tapDNSSettingsCell() -> Self {
- app.tables
- .cells[AccessibilityIdentifier.dnsSettings]
- .tap()
-
- return self
- }
-
- @discardableResult func tapDNSContentBlockingHeaderExpandButton() -> Self {
- let headerView = app.otherElements[AccessibilityIdentifier.dnsContentBlockersHeaderView]
- let expandButton = headerView.buttons[AccessibilityIdentifier.collapseButton]
- expandButton.tap()
-
- return self
- }
-
- @discardableResult func tapBlockAdsSwitch() -> Self {
- app.cells[AccessibilityIdentifier.blockAdvertising]
- .switches[AccessibilityIdentifier.customSwitch]
- .tap()
-
- return self
- }
}
diff --git a/ios/MullvadVPNUITests/Pages/VPNSettingsPage.swift b/ios/MullvadVPNUITests/Pages/VPNSettingsPage.swift
index 22ad20540b..71adb3eaaa 100644
--- a/ios/MullvadVPNUITests/Pages/VPNSettingsPage.swift
+++ b/ios/MullvadVPNUITests/Pages/VPNSettingsPage.swift
@@ -28,9 +28,16 @@ class VPNSettingsPage: Page {
return self
}
+ @discardableResult func tapDNSSettingsCell() -> Self {
+ app.tables
+ .cells[AccessibilityIdentifier.dnsSettings]
+ .tap()
+
+ return self
+ }
+
@discardableResult func tapWireGuardPortsExpandButton() -> Self {
cellExpandCollapseButton(AccessibilityIdentifier.wireGuardPortsCell).tap()
-
return self
}
@@ -46,16 +53,51 @@ class VPNSettingsPage: Page {
return self
}
+ @discardableResult func tapUDPOverTCPPortAutomaticCell() -> Self {
+ app.cells["\(AccessibilityIdentifier.wireGuardObfuscationPort)Automatic"]
+ .tap()
+ return self
+ }
+
+ @discardableResult func tapUDPOverTCPPort80Cell() -> Self {
+ app.cells["\(AccessibilityIdentifier.wireGuardObfuscationPort)80"]
+ .tap()
+ return self
+ }
+
+ @discardableResult func tapUDPOverTCPPort5001Cell() -> Self {
+ app.cells["\(AccessibilityIdentifier.wireGuardObfuscationPort)5001"]
+ .tap()
+ return self
+ }
+
@discardableResult func tapQuantumResistantTunnelExpandButton() -> Self {
cellExpandCollapseButton(AccessibilityIdentifier.quantumResistantTunnelCell).tap()
return self
}
+ @discardableResult func tapQuantumResistantTunnelAutomaticCell() -> Self {
+ app.cells[AccessibilityIdentifier.quantumResistanceAutomatic]
+ .tap()
+ return self
+ }
+
+ @discardableResult func tapQuantumResistantTunnelOnCell() -> Self {
+ app.cells[AccessibilityIdentifier.quantumResistanceOn]
+ .tap()
+ return self
+ }
+
+ @discardableResult func tapQuantumResistantTunnelOffCell() -> Self {
+ app.cells[AccessibilityIdentifier.quantumResistanceOff]
+ .tap()
+ return self
+ }
+
@discardableResult func tapWireGuardObfuscationAutomaticCell() -> Self {
app.cells[AccessibilityIdentifier.wireGuardObfuscationAutomatic]
.tap()
-
return self
}
@@ -72,8 +114,40 @@ class VPNSettingsPage: Page {
}
@discardableResult func tapCustomWireGuardPortTextField() -> Self {
- app.textFields[AccessibilityIdentifier.customWireGuardPortTextField].tap()
+ app.textFields[AccessibilityIdentifier.customWireGuardPortTextField]
+ .tap()
+ return self
+ }
+
+ @discardableResult func verifyCustomWireGuardPortSelected(portNumber: String) -> Self {
+ let cell = app.cells[AccessibilityIdentifier.wireGuardCustomPort]
+ XCTAssertTrue(cell.isSelected)
+ let textField = app.textFields[AccessibilityIdentifier.customWireGuardPortTextField]
+
+ guard let textFieldValue = textField.value as? String else {
+ XCTFail("Failed to read custom port text field value")
+ return self
+ }
+
+ XCTAssertEqual(textFieldValue, portNumber)
+ return self
+ }
+
+ @discardableResult func verifyWireGuardObfuscationOnSelected() -> Self {
+ let onCell = app.cells[AccessibilityIdentifier.wireGuardObfuscationOn]
+ XCTAssertTrue(onCell.isSelected)
+ return self
+ }
+
+ @discardableResult func verifyUDPOverTCPPort80Selected() -> Self {
+ let cell = app.cells["\(AccessibilityIdentifier.wireGuardObfuscationPort)80"]
+ XCTAssertTrue(cell.isSelected)
+ return self
+ }
+ @discardableResult func verifyQuantumResistantTunnelOffSelected() -> Self {
+ let cell = app.cells[AccessibilityIdentifier.quantumResistanceOff]
+ XCTAssertTrue(cell.isSelected)
return self
}
}
diff --git a/ios/MullvadVPNUITests/README.md b/ios/MullvadVPNUITests/README.md
index 6f2b4f0f08..a8146b24d9 100644
--- a/ios/MullvadVPNUITests/README.md
+++ b/ios/MullvadVPNUITests/README.md
@@ -13,15 +13,17 @@
5. Install yeetd
- `wget https://github.com/biscuitehh/yeetd/releases/download/1.0/yeetd-normal.pkg`
- `sudo installer -pkg yeetd-normal.pkg -target yeetd`
-6. Install Homebrew and dependencies
+6. Install ios-deploy
+ - `brew install ios-deploy`
+7. Install Homebrew and dependencies
- `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`
- `brew install xcbeautify wget swiftlint`
-7. Install Ruby
+8. Install Ruby
- `\curl -sSL https://get.rvm.io | bash`
-8. Install Rust and add iOS targets
+9. Install Rust and add iOS targets
- `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`
- `rustup target install aarch64-apple-ios aarch64-apple-ios-sim`
-9. Install Go 1.19
+10. Install Go 1.19
- `brew install go@1.19`
## GitHub runner setup
@@ -32,3 +34,15 @@
- `IOS_NO_TIME_ACCOUNT_NUMBER` - Production server account with time added to it
- `IOS_TEST_DEVICE_IDENTIFIER_UUID` - unique identifier for the test device. Create new identifier with `uuidgen`.
- `IOS_TEST_DEVICE_UDID` - the iOS device's UDID.
+
+## Test plans
+There are a few different test plans which are mainly to be triggered by GitHub action workflows but can also be triggered manually with Xcode:
+* `MullvadVPNUITestsAll` - All tests except settings migration tests which are in separate test plan and workflow
+* `MullvadVPNUITestsSmoke` - A few tests for smoke testing when merge:ing to `main`
+
+And also the following test plans which are used for testing settings migration(`ios-end-to-end-tests-settings-migration`):
+
+* `MullvadVPNUITestsChangeDNSSettings` - Change settings for using custom DNS
+* `MullvadVPNUITestsVerifyDNSSettingsChanged` - Verify custom DNS settings still changed
+* `MullvadVPNUITestsChangeSettings` - Change all settings except custom DNS setting
+* `MullvadVPNUITestsVerifySettingsChanged` - Verify all settings except custom DNS setting still changed
diff --git a/ios/MullvadVPNUITests/RelayTests.swift b/ios/MullvadVPNUITests/RelayTests.swift
index b90d158ba7..04df1e3454 100644
--- a/ios/MullvadVPNUITests/RelayTests.swift
+++ b/ios/MullvadVPNUITests/RelayTests.swift
@@ -32,8 +32,12 @@ class RelayTests: LoggedInWithTimeUITestCase {
SettingsPage(app)
.tapVPNSettingsCell()
+
+ VPNSettingsPage(app)
.tapDNSSettingsCell()
- .tapDNSContentBlockingHeaderExpandButton()
+
+ DNSSettingsPage(app)
+ .tapDNSContentBlockersHeaderExpandButton()
.tapBlockAdsSwitch()
.swipeDownToDismissModal()
diff --git a/ios/MullvadVPNUITests/SettingsMigrationTests.swift b/ios/MullvadVPNUITests/SettingsMigrationTests.swift
new file mode 100644
index 0000000000..35d46b8c87
--- /dev/null
+++ b/ios/MullvadVPNUITests/SettingsMigrationTests.swift
@@ -0,0 +1,138 @@
+//
+// SettingsMigrationTests.swift
+// MullvadVPNUITests
+//
+// Created by Niklas Berglund on 2024-03-15.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import XCTest
+
+class SettingsMigrationTests: BaseUITestCase {
+ let customDNSServerIPAddress = "123.123.123.123"
+ let wireGuardPort = "4001"
+
+ override class func shouldUninstallAppInTeardown() -> Bool {
+ return false
+ }
+
+ override func setUp() {
+ super.setUp()
+
+ agreeToTermsOfServiceIfShown()
+ dismissChangeLogIfShown()
+
+ // Relaunch app so that tests start from a deterministic state
+ app.terminate()
+ app.launch()
+ }
+
+ func testChangeCustomDNSSettings() {
+ logoutIfLoggedIn()
+ login(accountNumber: hasTimeAccountNumber)
+
+ HeaderBar(app)
+ .tapSettingsButton()
+
+ SettingsPage(app)
+ .tapVPNSettingsCell()
+
+ VPNSettingsPage(app)
+ .tapDNSSettingsCell()
+
+ DNSSettingsPage(app)
+ .tapEditButton()
+ .tapAddAServer()
+ .tapEnterIPAddressTextField()
+ .enterText(customDNSServerIPAddress)
+ .dismissKeyboard()
+ .tapUseCustomDNSSwitch()
+ .tapDoneButton()
+ }
+
+ func testChangeSettings() {
+ logoutIfLoggedIn()
+ login(accountNumber: hasTimeAccountNumber)
+
+ HeaderBar(app)
+ .tapSettingsButton()
+
+ SettingsPage(app)
+ .tapVPNSettingsCell()
+
+ VPNSettingsPage(app)
+ .tapDNSSettingsCell()
+
+ DNSSettingsPage(app)
+ .tapDNSContentBlockersHeaderExpandButton()
+ .tapBlockAdsSwitch()
+ .tapBlockTrackerSwitch()
+ .tapBlockMalwareSwitch()
+ .tapBlockAdultContentSwitch()
+ .tapBlockGamblingSwitch()
+ .tapBlockSocialMediaSwitch()
+ .tapBackButton()
+
+ VPNSettingsPage(app)
+ .tapWireGuardPortsExpandButton()
+ .tapCustomWireGuardPortTextField()
+ .enterText(wireGuardPort)
+ .dismissKeyboard()
+ .tapWireGuardPortsExpandButton()
+ .tapWireGuardObfuscationExpandButton()
+ .tapWireGuardObfuscationOnCell()
+ .tapWireGuardObfuscationExpandButton()
+ .tapUDPOverTCPPortExpandButton()
+ .tapUDPOverTCPPort80Cell()
+ .tapUDPOverTCPPortExpandButton()
+ }
+
+ func testVerifyCustomDNSSettingsStillChanged() {
+ HeaderBar(app)
+ .tapSettingsButton()
+
+ SettingsPage(app)
+ .tapVPNSettingsCell()
+
+ VPNSettingsPage(app)
+ .tapDNSSettingsCell()
+
+ DNSSettingsPage(app)
+ .verifyUseCustomDNSSwitchOn()
+ .verifyCustomDNSIPAddress(customDNSServerIPAddress)
+ }
+
+ func testVerifySettingsStillChanged() {
+ HeaderBar(app)
+ .tapSettingsButton()
+
+ SettingsPage(app)
+ .tapVPNSettingsCell()
+
+ VPNSettingsPage(app)
+ .tapDNSSettingsCell()
+
+ DNSSettingsPage(app)
+ .tapDNSContentBlockersHeaderExpandButton()
+ .verifyBlockAdsSwitchOn()
+ .verifyBlockTrackerSwitchOn()
+ .verifyBlockMalwareSwitchOn()
+ .verifyBlockAdultContentSwitchOn()
+ .verifyBlockGamblingSwitchOn()
+ .verifyBlockSocialMediaSwitchOn()
+ .tapBackButton()
+
+ VPNSettingsPage(app)
+ .tapWireGuardPortsExpandButton()
+ .verifyCustomWireGuardPortSelected(portNumber: wireGuardPort)
+ .tapWireGuardPortsExpandButton()
+ .tapWireGuardObfuscationExpandButton()
+ .tapWireGuardObfuscationOnCell()
+ .verifyWireGuardObfuscationOnSelected()
+ .tapWireGuardObfuscationExpandButton()
+ .tapUDPOverTCPPortExpandButton()
+ .verifyUDPOverTCPPort80Selected()
+ .tapBackButton()
+ }
+}
diff --git a/ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift b/ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift
index be7d517d54..11b0cf6ee5 100644
--- a/ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift
+++ b/ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift
@@ -53,9 +53,16 @@ class BaseUITestCase: XCTestCase {
// MARK: - Setup & teardown
+ /// Override this class function to change the uninstall behaviour in suite level teardown
+ class func shouldUninstallAppInTeardown() -> Bool {
+ return true
+ }
+
/// Suite level teardown ran after test have executed
override class func tearDown() {
- uninstallApp()
+ if shouldUninstallAppInTeardown() {
+ uninstallApp()
+ }
}
/// Test level setup
@@ -104,6 +111,11 @@ class BaseUITestCase: XCTestCase {
ChangeLogAlert(app)
.tapOkay()
}
+
+ // Ensure changelog is no longer shown
+ _ = app
+ .otherElements[AccessibilityIdentifier.changeLogAlert.rawValue]
+ .waitForNonExistence(timeout: Self.shortTimeout)
}
/// Login with specified account number. It is a prerequisite that the login page is currently shown.
diff --git a/ios/MullvadVPNUITests/XCUIElement+Extensions.swift b/ios/MullvadVPNUITests/XCUIElement+Extensions.swift
new file mode 100644
index 0000000000..9f0aa87188
--- /dev/null
+++ b/ios/MullvadVPNUITests/XCUIElement+Extensions.swift
@@ -0,0 +1,18 @@
+//
+// XCUIElement+Extensions.swift
+// MullvadVPNUITests
+//
+// Created by Niklas Berglund on 2024-03-25.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import XCTest
+
+extension XCUIElement {
+ func waitForNonExistence(timeout: TimeInterval) -> Bool {
+ let predicate = NSPredicate(format: "exists == FALSE")
+ let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self)
+ _ = XCTWaiter.wait(for: [expectation], timeout: timeout)
+ return !exists
+ }
+}
diff --git a/ios/TestPlans/MullvadVPNUITestsAll.xctestplan b/ios/TestPlans/MullvadVPNUITestsAll.xctestplan
index 14cb0990dc..74265b9dda 100644
--- a/ios/TestPlans/MullvadVPNUITestsAll.xctestplan
+++ b/ios/TestPlans/MullvadVPNUITestsAll.xctestplan
@@ -21,7 +21,8 @@
"BaseUITestCase",
"LoggedInWithTimeUITestCase",
"LoggedInWithoutTimeUITestCase",
- "LoggedOutUITestCase"
+ "LoggedOutUITestCase",
+ "SettingsMigrationTests"
],
"target" : {
"containerPath" : "container:MullvadVPN.xcodeproj",
diff --git a/ios/TestPlans/MullvadVPNUITestsChangeDNSSettings.xctestplan b/ios/TestPlans/MullvadVPNUITestsChangeDNSSettings.xctestplan
new file mode 100644
index 0000000000..92d92d1974
--- /dev/null
+++ b/ios/TestPlans/MullvadVPNUITestsChangeDNSSettings.xctestplan
@@ -0,0 +1,28 @@
+{
+ "configurations" : [
+ {
+ "id" : "29A4F7B5-B97B-4F31-9EB0-3C09615D0ACF",
+ "name" : "Configuration 1",
+ "options" : {
+
+ }
+ }
+ ],
+ "defaultOptions" : {
+ "testTimeoutsEnabled" : true
+ },
+ "testTargets" : [
+ {
+ "selectedTests" : [
+ "MigrationTests\/testChangeCustomDNSSettings()",
+ "SettingsMigrationTests\/testChangeCustomDNSSettings()"
+ ],
+ "target" : {
+ "containerPath" : "container:MullvadVPN.xcodeproj",
+ "identifier" : "852969242B4D9C1F007EAD4C",
+ "name" : "MullvadVPNUITests"
+ }
+ }
+ ],
+ "version" : 1
+}
diff --git a/ios/TestPlans/MullvadVPNUITestsChangeSettings.xctestplan b/ios/TestPlans/MullvadVPNUITestsChangeSettings.xctestplan
new file mode 100644
index 0000000000..366e2e597b
--- /dev/null
+++ b/ios/TestPlans/MullvadVPNUITestsChangeSettings.xctestplan
@@ -0,0 +1,28 @@
+{
+ "configurations" : [
+ {
+ "id" : "29A4F7B5-B97B-4F31-9EB0-3C09615D0ACF",
+ "name" : "Configuration 1",
+ "options" : {
+
+ }
+ }
+ ],
+ "defaultOptions" : {
+ "testTimeoutsEnabled" : true
+ },
+ "testTargets" : [
+ {
+ "selectedTests" : [
+ "MigrationTests\/testChangeSettings()",
+ "SettingsMigrationTests\/testChangeSettings()"
+ ],
+ "target" : {
+ "containerPath" : "container:MullvadVPN.xcodeproj",
+ "identifier" : "852969242B4D9C1F007EAD4C",
+ "name" : "MullvadVPNUITests"
+ }
+ }
+ ],
+ "version" : 1
+}
diff --git a/ios/TestPlans/MullvadVPNUITestsVerifyDNSSettingsChanged.xctestplan b/ios/TestPlans/MullvadVPNUITestsVerifyDNSSettingsChanged.xctestplan
new file mode 100644
index 0000000000..7b2c070a3d
--- /dev/null
+++ b/ios/TestPlans/MullvadVPNUITestsVerifyDNSSettingsChanged.xctestplan
@@ -0,0 +1,28 @@
+{
+ "configurations" : [
+ {
+ "id" : "29A4F7B5-B97B-4F31-9EB0-3C09615D0ACF",
+ "name" : "Configuration 1",
+ "options" : {
+
+ }
+ }
+ ],
+ "defaultOptions" : {
+ "testTimeoutsEnabled" : true
+ },
+ "testTargets" : [
+ {
+ "selectedTests" : [
+ "MigrationTests\/testVerifyCustomDNSSettingsStillChanged()",
+ "SettingsMigrationTests\/testVerifyCustomDNSSettingsStillChanged()"
+ ],
+ "target" : {
+ "containerPath" : "container:MullvadVPN.xcodeproj",
+ "identifier" : "852969242B4D9C1F007EAD4C",
+ "name" : "MullvadVPNUITests"
+ }
+ }
+ ],
+ "version" : 1
+}
diff --git a/ios/TestPlans/MullvadVPNUITestsVerifySettingsChanged.xctestplan b/ios/TestPlans/MullvadVPNUITestsVerifySettingsChanged.xctestplan
new file mode 100644
index 0000000000..872d198dd9
--- /dev/null
+++ b/ios/TestPlans/MullvadVPNUITestsVerifySettingsChanged.xctestplan
@@ -0,0 +1,28 @@
+{
+ "configurations" : [
+ {
+ "id" : "29A4F7B5-B97B-4F31-9EB0-3C09615D0ACF",
+ "name" : "Configuration 1",
+ "options" : {
+
+ }
+ }
+ ],
+ "defaultOptions" : {
+ "testTimeoutsEnabled" : true
+ },
+ "testTargets" : [
+ {
+ "selectedTests" : [
+ "MigrationTests\/testVerifySettingsStillChanged()",
+ "SettingsMigrationTests\/testVerifySettingsStillChanged()"
+ ],
+ "target" : {
+ "containerPath" : "container:MullvadVPN.xcodeproj",
+ "identifier" : "852969242B4D9C1F007EAD4C",
+ "name" : "MullvadVPNUITests"
+ }
+ }
+ ],
+ "version" : 1
+}