diff options
| author | Albin <albin@mullvad.net> | 2022-03-07 13:26:03 +0100 |
|---|---|---|
| committer | Albin <albin@mullvad.net> | 2022-04-20 18:00:41 +0200 |
| commit | b93a0ba3e235c8d5e53298f30322d47c34a1e76a (patch) | |
| tree | 0a0007ccf7b75cc2750c45ce0710a137093fd967 /android/e2e/src | |
| parent | f45276ac2dca91ad6a36d3a4ef948b2cbe3f9bd1 (diff) | |
| download | mullvadvpn-b93a0ba3e235c8d5e53298f30322d47c34a1e76a.tar.xz mullvadvpn-b93a0ba3e235c8d5e53298f30322d47c34a1e76a.zip | |
Add Android e2e connection test
Diffstat (limited to 'android/e2e/src')
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 + ) +} |
