diff --git a/.github/workflows/ios-end-to-end-tests.yml b/.github/workflows/ios-end-to-end-tests.yml
new file mode 100644
index 000000000000..28da6da122c0
--- /dev/null
+++ b/.github/workflows/ios-end-to-end-tests.yml
@@ -0,0 +1,80 @@
+---
+name: iOS end-to-end tests
+permissions:
+  contents: read
+  issues: write
+  pull-requests: write
+on:
+  pull_request:
+    types:
+      - closed
+    branches:
+      - main
+  workflow_dispatch:
+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 }}
+      NO_TIME_ACCOUNT_NUMBER: ${{ secrets.IOS_NO_TIME_ACCOUNT_NUMBER }}
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Configure Rust
+        run: rustup target install aarch64-apple-ios aarch64-apple-ios-sim
+
+      - name: Configure Xcode project
+        run: |
+          for file in *.xcconfig.template ; do cp $file ${file//.template/} ; done
+          sed -i "" \
+            "/MULLVAD_IOS_DEVICE_PIN_CODE =/ s/= .*/= $IOS_DEVICE_PIN_CODE/" \
+            UITests.xcconfig
+          sed -i "" \
+            "/MULLVAD_TEST_DEVICE_IDENTIFIER_UUID =/ s/= .*/= $TEST_DEVICE_IDENTIFIER_UUID/" \
+            UITests.xcconfig
+          sed -i "" \
+            "/MULLVAD_HAS_TIME_ACCOUNT_NUMBER =/ s/= .*/= $HAS_TIME_ACCOUNT_NUMBER/" \
+            UITests.xcconfig
+          sed -i "" \
+            "/MULLVAD_NO_TIME_ACCOUNT_NUMBER =/ s/= .*/= $NO_TIME_ACCOUNT_NUMBER/" \
+            UITests.xcconfig
+        working-directory: ios/Configurations
+
+      - name: Run end-to-end-tests
+        run: |
+          set -o pipefail && env NSUnbufferedIO=YES xcodebuild \
+            -project MullvadVPN.xcodeproj \
+            -scheme MullvadVPNUITests \
+            -testPlan MullvadVPNUITestsSmoke \
+            -destination "platform=iOS,id=$TEST_DEVICE_UDID" \
+            test 2>&1 | xcbeautify --report junit --report-path test-report
+        working-directory: ios/
+
+      - name: Comment PR on test failure
+        if: failure() && github.event_name != 'workflow_dispatch'
+        uses: actions/github-script@v7
+        with:
+          github-token: ${{secrets.GITHUB_TOKEN}}
+          script: |
+            const issue_number = context.issue.number;
+            const run_id = context.runId;
+            const run_url = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${run_id}`;
+            github.rest.issues.createComment({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              issue_number: issue_number,
+              body: `🚨 End to end tests failed. Please check the [failed workflow run](${run_url}).`
+            });
+
+      - name: Store test report artifact
+        if: always()
+        uses: actions/upload-artifact@v4
+        with:
+          name: test-report
+          path: ios/test-report/junit.xml
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index c648222b8f8d..a0943f1f1ab6 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -1810,6 +1810,7 @@
 		7AF9BE8F2A39F26000DBFEDB /* Collection+Sorting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+Sorting.swift"; sourceTree = "<group>"; };
 		7AF9BE942A40461100DBFEDB /* RelayFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterView.swift; sourceTree = "<group>"; };
 		7AF9BE962A41C71F00DBFEDB /* RelayFilterChipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterChipView.swift; sourceTree = "<group>"; };
+		85006A8E2B73EF67004AD8FB /* MullvadVPNUITestsSmoke.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MullvadVPNUITestsSmoke.xctestplan; sourceTree = "<group>"; };
 		850201DA2B503D7700EF8C96 /* RelayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayTests.swift; sourceTree = "<group>"; };
 		850201DC2B503D8C00EF8C96 /* SelectLocationPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationPage.swift; sourceTree = "<group>"; };
 		850201DE2B5040A500EF8C96 /* TunnelControlPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelControlPage.swift; sourceTree = "<group>"; };
@@ -1817,7 +1818,7 @@
 		8518F6372B60157E009EB113 /* LoggedInWithoutTimeUITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedInWithoutTimeUITestCase.swift; sourceTree = "<group>"; };
 		852969252B4D9C1F007EAD4C /* MullvadVPNUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MullvadVPNUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		852969272B4D9C1F007EAD4C /* AccountTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTests.swift; sourceTree = "<group>"; };
-		852969302B4D9E70007EAD4C /* MullvadVPNUITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MullvadVPNUITests.xctestplan; sourceTree = "<group>"; };
+		852969302B4D9E70007EAD4C /* MullvadVPNUITestsAll.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MullvadVPNUITestsAll.xctestplan; sourceTree = "<group>"; };
 		852969322B4E9232007EAD4C /* Page.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Page.swift; sourceTree = "<group>"; };
 		852969342B4E9270007EAD4C /* LoginPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPage.swift; sourceTree = "<group>"; };
 		852969372B4ED20E007EAD4C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
@@ -3443,7 +3444,8 @@
 		7A83C3FC2A55B39500DFB83A /* TestPlans */ = {
 			isa = PBXGroup;
 			children = (
-				852969302B4D9E70007EAD4C /* MullvadVPNUITests.xctestplan */,
+				852969302B4D9E70007EAD4C /* MullvadVPNUITestsAll.xctestplan */,
+				85006A8E2B73EF67004AD8FB /* MullvadVPNUITestsSmoke.xctestplan */,
 				7A83C3FE2A55B72E00DFB83A /* MullvadVPNApp.xctestplan */,
 				7A83C4002A55B81A00DFB83A /* MullvadVPNCI.xctestplan */,
 				7A02D4EA2A9CEC7A00C19E31 /* MullvadVPNScreenshots.xctestplan */,
diff --git a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPNUITests.xcscheme b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPNUITests.xcscheme
index 7c65faea3b98..7603c9373776 100644
--- a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPNUITests.xcscheme
+++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPNUITests.xcscheme
@@ -13,9 +13,12 @@
       shouldUseLaunchSchemeArgsEnv = "YES">
       <TestPlans>
          <TestPlanReference
-            reference = "container:TestPlans/MullvadVPNUITests.xctestplan"
+            reference = "container:TestPlans/MullvadVPNUITestsAll.xctestplan"
             default = "YES">
          </TestPlanReference>
+         <TestPlanReference
+            reference = "container:TestPlans/MullvadVPNUITestsSmoke.xctestplan">
+         </TestPlanReference>
       </TestPlans>
       <Testables>
          <TestableReference
diff --git a/ios/MullvadVPNUITests/README.md b/ios/MullvadVPNUITests/README.md
new file mode 100644
index 000000000000..8602409322c8
--- /dev/null
+++ b/ios/MullvadVPNUITests/README.md
@@ -0,0 +1,34 @@
+# Integration tests
+
+## iOS device setup
+1. Make sure device is added to provisioning profiles
+2. Disable passcode in iOS settings - otherwise tests cannot be started without manually entering passcode
+3. Make sure device is configured in GitHub secrets(see *GitHub setup* below)
+
+## Set up of runner environment
+1. Install Xcode
+2. Sign in with Apple id in Xcode
+3. Download manual provisioning profiles in Xcode
+4. Install Xcode command line tools `xcode-select --install`
+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
+  - `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`
+  - `brew install xcbeautify wget swiftlint`
+7. Install Ruby
+  - `\curl -sSL https://get.rvm.io | bash`
+8. 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
+  - `brew install go@1.19`
+
+## GitHub runner setup
+1. Ask GitHub admin for new runner token and set it up according to the steps presented, pass `--labels ios-test` to `config.sh` when running it. By default it will also have the labels `self-hosted` and `macOS` which are required as well.
+2. Make sure GitHub actions secrets for the repository are correctly set up:
+  - `IOS_DEVICE_PIN_CODE` - Device passcode if the device require it, otherwise leave blank. Devices used with CI should not require passcode.
+  - `IOS_HAS_TIME_ACCOUNT_NUMBER` - Production server account without time left
+  - `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.
\ No newline at end of file
diff --git a/ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift b/ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift
index eae33b08050f..00a73cd36c1c 100644
--- a/ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift	
+++ b/ios/MullvadVPNUITests/Test base classes/BaseUITestCase.swift	
@@ -35,8 +35,10 @@ class BaseUITestCase: XCTestCase {
             alertAllowButton.tap()
         }
 
-        _ = springboard.buttons["1"].waitForExistence(timeout: Self.defaultTimeout)
-        springboard.typeText(iOSDevicePinCode)
+        if iOSDevicePinCode.isEmpty == false {
+            _ = springboard.buttons["1"].waitForExistence(timeout: Self.defaultTimeout)
+            springboard.typeText(iOSDevicePinCode)
+        }
     }
 
     // MARK: - Setup & teardown
diff --git a/ios/TestPlans/MullvadVPNUITests.xctestplan b/ios/TestPlans/MullvadVPNUITestsAll.xctestplan
similarity index 81%
rename from ios/TestPlans/MullvadVPNUITests.xctestplan
rename to ios/TestPlans/MullvadVPNUITestsAll.xctestplan
index dd5cb5a09e24..29951c80fe1b 100644
--- a/ios/TestPlans/MullvadVPNUITests.xctestplan
+++ b/ios/TestPlans/MullvadVPNUITestsAll.xctestplan
@@ -17,6 +17,10 @@
   },
   "testTargets" : [
     {
+      "selectedTests" : [
+        "AccountTests\/testLogin()",
+        "AccountTests\/testLoginWithIncorrectAccountNumber()"
+      ],
       "target" : {
         "containerPath" : "container:MullvadVPN.xcodeproj",
         "identifier" : "852969242B4D9C1F007EAD4C",
diff --git a/ios/TestPlans/MullvadVPNUITestsSmoke.xctestplan b/ios/TestPlans/MullvadVPNUITestsSmoke.xctestplan
new file mode 100644
index 000000000000..10665084a92d
--- /dev/null
+++ b/ios/TestPlans/MullvadVPNUITestsSmoke.xctestplan
@@ -0,0 +1,28 @@
+{
+  "configurations" : [
+    {
+      "id" : "3BCBAC52-59BD-45E8-BC9D-1AF65D1B15B7",
+      "name" : "Configuration 1",
+      "options" : {
+
+      }
+    }
+  ],
+  "defaultOptions" : {
+
+  },
+  "testTargets" : [
+    {
+      "selectedTests" : [
+        "AccountTests\/testLogin()",
+        "AccountTests\/testLoginWithIncorrectAccountNumber()"
+      ],
+      "target" : {
+        "containerPath" : "container:MullvadVPN.xcodeproj",
+        "identifier" : "852969242B4D9C1F007EAD4C",
+        "name" : "MullvadVPNUITests"
+      }
+    }
+  ],
+  "version" : 1
+}