summaryrefslogtreecommitdiffhomepage
path: root/android/e2e/src/main/java
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2022-03-07 13:26:03 +0100
committerAlbin <albin@mullvad.net>2022-04-20 18:00:41 +0200
commitb93a0ba3e235c8d5e53298f30322d47c34a1e76a (patch)
tree0a0007ccf7b75cc2750c45ce0710a137093fd967 /android/e2e/src/main/java
parentf45276ac2dca91ad6a36d3a4ef948b2cbe3f9bd1 (diff)
downloadmullvadvpn-b93a0ba3e235c8d5e53298f30322d47c34a1e76a.tar.xz
mullvadvpn-b93a0ba3e235c8d5e53298f30322d47c34a1e76a.zip
Add Android e2e connection test
Diffstat (limited to 'android/e2e/src/main/java')
-rw-r--r--android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/ConnectionTest.kt32
-rw-r--r--android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/EndToEndTest.kt7
-rw-r--r--android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/Constants.kt1
-rw-r--r--android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/ResourceConstants.kt2
-rw-r--r--android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/TextConstants.kt4
-rw-r--r--android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/TimeoutConstants.kt1
-rw-r--r--android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/extension/UiAutomatorExtensions.kt29
-rw-r--r--android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/interactor/AppInteractor.kt23
-rw-r--r--android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/interactor/SystemSettingsInteractor.kt36
-rw-r--r--android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/interactor/WebViewInteractor.kt47
10 files changed, 178 insertions, 4 deletions
diff --git a/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/ConnectionTest.kt b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/ConnectionTest.kt
new file mode 100644
index 0000000000..4334ae2265
--- /dev/null
+++ b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/ConnectionTest.kt
@@ -0,0 +1,32 @@
+package net.mullvad.mullvadvpn.e2e
+
+import androidx.test.uiautomator.By
+import junit.framework.Assert.assertEquals
+import net.mullvad.mullvadvpn.e2e.extension.findObjectWithTimeout
+import net.mullvad.mullvadvpn.e2e.interactor.WebViewInteractor
+import net.mullvad.mullvadvpn.e2e.misc.CleanupAccountTestRule
+import org.junit.Rule
+import org.junit.Test
+
+class ConnectionTest : EndToEndTest() {
+
+ @Rule
+ @JvmField
+ val cleanupAccountTestRule = CleanupAccountTestRule()
+
+ @Test
+ fun testConnectAndVerifyWithConnectionCheck() {
+ // Given
+ app.launchAndEnsureLoggedIn()
+
+ // When
+ device.findObjectWithTimeout(By.text("Secure my connection")).click()
+ device.findObjectWithTimeout(By.text("OK")).click()
+ device.findObjectWithTimeout(By.text("SECURE CONNECTION"))
+ val expected = WebViewInteractor.ConnCheckState(true, app.extractIpAddress())
+
+ // Then
+ val result = web.launchAndExtractConnCheckState()
+ assertEquals(expected, result)
+ }
+}
diff --git a/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/EndToEndTest.kt b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/EndToEndTest.kt
index 0963821785..d3f3c564b7 100644
--- a/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/EndToEndTest.kt
+++ b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/EndToEndTest.kt
@@ -8,6 +8,7 @@ import net.mullvad.mullvadvpn.e2e.constant.INVALID_TEST_ACCOUNT_TOKEN_ARGUMENT_K
import net.mullvad.mullvadvpn.e2e.constant.VALID_TEST_ACCOUNT_TOKEN_ARGUMENT_KEY
import net.mullvad.mullvadvpn.e2e.extension.getRequiredArgument
import net.mullvad.mullvadvpn.e2e.interactor.AppInteractor
+import net.mullvad.mullvadvpn.e2e.interactor.WebViewInteractor
import net.mullvad.mullvadvpn.e2e.misc.CaptureScreenshotOnFailedTestRule
import org.junit.Before
import org.junit.Rule
@@ -23,6 +24,7 @@ abstract class EndToEndTest {
lateinit var device: UiDevice
lateinit var targetContext: Context
lateinit var app: AppInteractor
+ lateinit var web: WebViewInteractor
lateinit var validTestAccountToken: String
lateinit var invalidTestAccountToken: String
@@ -42,5 +44,10 @@ abstract class EndToEndTest {
validTestAccountToken,
invalidTestAccountToken
)
+
+ web = WebViewInteractor(
+ targetContext,
+ device
+ )
}
}
diff --git a/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/Constants.kt b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/Constants.kt
index ed2d8f5ba2..3b6a04b51e 100644
--- a/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/Constants.kt
+++ b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/Constants.kt
@@ -1,5 +1,6 @@
package net.mullvad.mullvadvpn.e2e.constant
const val LOG_TAG = "mullvad-e2e"
+const val CONN_CHECK_URL = "https://mullvad.net/en/check/"
const val VALID_TEST_ACCOUNT_TOKEN_ARGUMENT_KEY = "valid_test_account_token"
const val INVALID_TEST_ACCOUNT_TOKEN_ARGUMENT_KEY = "invalid_test_account_token"
diff --git a/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/ResourceConstants.kt b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/ResourceConstants.kt
index 0263a0b0d6..07b2f03311 100644
--- a/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/ResourceConstants.kt
+++ b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/ResourceConstants.kt
@@ -1,3 +1,5 @@
package net.mullvad.mullvadvpn.e2e.constant
const val SETTINGS_COG_ID = "net.mullvad.mullvadvpn:id/settings"
+const val TUNNEL_INFO_ID = "net.mullvad.mullvadvpn:id/tunnel_info"
+const val TUNNEL_OUT_ADDRESS_ID = "net.mullvad.mullvadvpn:id/out_address"
diff --git a/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/TextConstants.kt b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/TextConstants.kt
new file mode 100644
index 0000000000..ff8e0088d4
--- /dev/null
+++ b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/TextConstants.kt
@@ -0,0 +1,4 @@
+package net.mullvad.mullvadvpn.e2e.constant
+
+const val CONNECTION_CHECK_IS_CONNECTED = "Using Mullvad VPN"
+const val CONNECTION_CHECK_IS_NOT_CONNECTED = "Not using Mullvad VPN"
diff --git a/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/TimeoutConstants.kt b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/TimeoutConstants.kt
index bda07c1067..ecc70c28b1 100644
--- a/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/TimeoutConstants.kt
+++ b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/constant/TimeoutConstants.kt
@@ -1,6 +1,7 @@
package net.mullvad.mullvadvpn.e2e.constant
const val APP_LAUNCH_TIMEOUT = 5000L
+const val CONNECTION_TIMEOUT = 30000L
const val DEFAULT_INTERACTION_TIMEOUT = 3000L
const val LOGIN_TIMEOUT = 30000L
const val LOGIN_FAILURE_TIMEOUT = 60000L
diff --git a/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/extension/UiAutomatorExtensions.kt b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/extension/UiAutomatorExtensions.kt
index 8c7dd16cee..5ecc16016d 100644
--- a/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/extension/UiAutomatorExtensions.kt
+++ b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/extension/UiAutomatorExtensions.kt
@@ -1,11 +1,21 @@
package net.mullvad.mullvadvpn.e2e.extension
+import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
+import java.util.regex.Pattern
import net.mullvad.mullvadvpn.e2e.constant.DEFAULT_INTERACTION_TIMEOUT
+fun UiDevice.findObjectByCaseInsensitiveText(text: String): UiObject2 {
+ return findObjectWithTimeout(By.text(Pattern.compile(text, Pattern.CASE_INSENSITIVE)))
+}
+
+fun UiObject2.findObjectByCaseInsensitiveText(text: String): UiObject2 {
+ return findObjectWithTimeout(By.text(Pattern.compile(text, Pattern.CASE_INSENSITIVE)))
+}
+
fun UiDevice.findObjectWithTimeout(
selector: BySelector,
timeout: Long = DEFAULT_INTERACTION_TIMEOUT
@@ -24,3 +34,22 @@ fun UiDevice.findObjectWithTimeout(
)
}
}
+
+fun UiObject2.findObjectWithTimeout(
+ selector: BySelector,
+ timeout: Long = DEFAULT_INTERACTION_TIMEOUT
+): UiObject2 {
+
+ wait(
+ Until.hasObject(selector),
+ timeout
+ )
+
+ return try {
+ findObject(selector)
+ } catch (e: NullPointerException) {
+ throw IllegalArgumentException(
+ "No matches for selector within timeout ($timeout): $selector"
+ )
+ }
+}
diff --git a/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/interactor/AppInteractor.kt b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/interactor/AppInteractor.kt
index 2ecbe748c5..680850e718 100644
--- a/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/interactor/AppInteractor.kt
+++ b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/interactor/AppInteractor.kt
@@ -7,9 +7,12 @@ import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import net.mullvad.mullvadvpn.e2e.constant.APP_LAUNCH_TIMEOUT
+import net.mullvad.mullvadvpn.e2e.constant.CONNECTION_TIMEOUT
import net.mullvad.mullvadvpn.e2e.constant.LOGIN_TIMEOUT
import net.mullvad.mullvadvpn.e2e.constant.MULLVAD_PACKAGE
import net.mullvad.mullvadvpn.e2e.constant.SETTINGS_COG_ID
+import net.mullvad.mullvadvpn.e2e.constant.TUNNEL_INFO_ID
+import net.mullvad.mullvadvpn.e2e.constant.TUNNEL_OUT_ADDRESS_ID
import net.mullvad.mullvadvpn.e2e.extension.findObjectWithTimeout
class AppInteractor(
@@ -37,6 +40,12 @@ class AppInteractor(
)
}
+ fun launchAndEnsureLoggedIn(accountToken: String = validTestAccountToken) {
+ launch()
+ attemptLogin(accountToken)
+ ensureLoggedIn()
+ }
+
fun attemptLogin(accountToken: String = validTestAccountToken) {
device.findObjectWithTimeout(By.text("Login"))
val loginObject = device.findObjectWithTimeout(By.clazz("android.widget.EditText"))
@@ -48,10 +57,12 @@ class AppInteractor(
device.findObjectWithTimeout(By.text("UNSECURED CONNECTION"), LOGIN_TIMEOUT)
}
- fun launchAndEnsureLoggedIn(accountToken: String = validTestAccountToken) {
- launch()
- attemptLogin(accountToken)
- ensureLoggedIn()
+ fun extractIpAddress(): String {
+ device.findObjectWithTimeout(By.res(TUNNEL_INFO_ID)).click()
+ return device.findObjectWithTimeout(
+ By.res(TUNNEL_OUT_ADDRESS_ID),
+ CONNECTION_TIMEOUT
+ ).text.extractIpAddress()
}
fun clickSettingsCog() {
@@ -65,4 +76,8 @@ class AppInteractor(
fun clickActionButtonByText(text: String) {
device.findObjectWithTimeout(By.text(text)).click()
}
+
+ private fun String.extractIpAddress(): String {
+ return split(" ")[1].split(" ")[0]
+ }
}
diff --git a/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/interactor/SystemSettingsInteractor.kt b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/interactor/SystemSettingsInteractor.kt
new file mode 100644
index 0000000000..29cef35b0a
--- /dev/null
+++ b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/interactor/SystemSettingsInteractor.kt
@@ -0,0 +1,36 @@
+package net.mullvad.mullvadvpn.e2e.interactor
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import net.mullvad.mullvadvpn.e2e.extension.findObjectByCaseInsensitiveText
+
+class SystemSettingsInteractor(
+ private val uiDevice: UiDevice,
+ private val context: Context
+) {
+ fun openVpnSettings() {
+ val intent = Intent("com.intent.MAIN").apply {
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ intent.component = ComponentName.unflattenFromString(
+ "com.android.settings/.Settings\$VpnSettingsActivity"
+ )
+ context.startActivity(intent)
+ Thread.sleep(1000)
+ }
+
+ fun removeAllVpnPermissions() {
+ openVpnSettings()
+ uiDevice.findObjects(By.descContains("Settings")).forEach {
+ it.click()
+ Thread.sleep(1000)
+ uiDevice.findObjectByCaseInsensitiveText("forget vpn").click()
+ Thread.sleep(1000)
+ uiDevice.findObjectByCaseInsensitiveText("forget").click()
+ }
+ }
+}
diff --git a/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/interactor/WebViewInteractor.kt b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/interactor/WebViewInteractor.kt
new file mode 100644
index 0000000000..7a251f082b
--- /dev/null
+++ b/android/e2e/src/main/java/net/mullvad/mullvadvpn/e2e/interactor/WebViewInteractor.kt
@@ -0,0 +1,47 @@
+package net.mullvad.mullvadvpn.e2e.interactor
+
+import android.content.Context
+import android.content.Intent
+import android.view.View
+import android.webkit.WebView
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import net.mullvad.mullvadvpn.TestActivity
+import net.mullvad.mullvadvpn.e2e.constant.CONNECTION_CHECK_IS_CONNECTED
+import net.mullvad.mullvadvpn.e2e.constant.CONN_CHECK_URL
+import net.mullvad.mullvadvpn.e2e.extension.findObjectByCaseInsensitiveText
+import net.mullvad.mullvadvpn.e2e.extension.findObjectWithTimeout
+
+class WebViewInteractor(
+ private val context: Context,
+ private val device: UiDevice
+) {
+ fun launchWebView(context: Context, url: String) {
+ val intent = Intent(context, TestActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ putExtra("url", url)
+ }
+ context.startActivity(intent)
+ }
+
+ fun launchAndExtractConnCheckState(): ConnCheckState {
+ launchWebView(context, CONN_CHECK_URL)
+ val webView = device.findObjectWithTimeout(By.clazz(WebView::class.java))
+ val stateText = device.findObjectByCaseInsensitiveText("using Mullvad VPN").apply {
+ click()
+ }
+
+ // Wait for view to expand after click.
+ Thread.sleep(1000)
+
+ val wireGuardIpv4Connection = webView.findObjects(By.clazz(View::class.java))
+ .first { it.text?.startsWith("WireGuard") == true }
+ val wireGuardIpv4Address = wireGuardIpv4Connection.text.split("\n")[2]
+ return ConnCheckState(stateText.text == CONNECTION_CHECK_IS_CONNECTED, wireGuardIpv4Address)
+ }
+
+ data class ConnCheckState(
+ val isConnected: Boolean,
+ val ipAddress: String
+ )
+}