From d34461affd206b7bf0ee76919f478f362056de5f Mon Sep 17 00:00:00 2001 From: Niklas Berglund Date: Wed, 11 Dec 2024 16:32:21 +0100 Subject: [PATCH] Migrate remaining e2e tests to POP --- .../test/common/interactor/AppInteractor.kt | 6 +- .../test/common/page/AccountPage.kt | 16 +++ .../test/common/page/ConnectPage.kt | 5 + .../test/common/page/MullvadWebsite.kt | 14 +++ .../test/common/page/SettingsPage.kt | 5 + .../test/common/page/VpnSettingsPage.kt | 6 + .../common/page/WireGuardCustomPortDialog.kt | 30 +++++ .../mullvad/mullvadvpn/test/e2e/LeakTest.kt | 112 +++++++++++------- .../mullvad/mullvadvpn/test/e2e/LogoutTest.kt | 16 +-- .../mullvadvpn/test/e2e/WebLinkTest.kt | 27 ++--- 10 files changed, 164 insertions(+), 73 deletions(-) create mode 100644 android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/AccountPage.kt create mode 100644 android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/MullvadWebsite.kt create mode 100644 android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/WireGuardCustomPortDialog.kt diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt index ea9b761ea15c..41fe28b68cf5 100644 --- a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt +++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt @@ -39,11 +39,15 @@ class AppInteractor( device.wait(Until.hasObject(By.pkg(targetPackageName).depth(0)), LONG_TIMEOUT) } - fun launchAndEnsureLoggedIn(accountNumber: String) { + fun launchAndEnsureOnLoginPage() { launch() device.clickAgreeOnPrivacyDisclaimer() device.clickAllowOnNotificationPermissionPromptIfApiLevel33AndAbove() waitForLoginPrompt() + } + + fun launchAndEnsureLoggedIn(accountNumber: String) { + launchAndEnsureOnLoginPage() attemptLogin(accountNumber) device.dismissChangelogDialogIfShown() ensureLoggedIn() diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/AccountPage.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/AccountPage.kt new file mode 100644 index 000000000000..db4a96364887 --- /dev/null +++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/AccountPage.kt @@ -0,0 +1,16 @@ +package net.mullvad.mullvadvpn.test.common.page + +import androidx.test.uiautomator.By +import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout + +class AccountPage internal constructor() : Page() { + private val logOutSelector = By.text("Log out") + + override fun assertIsDisplayed() { + uiDevice.findObjectWithTimeout(By.text("Account")) + } + + fun clickLogOut() { + uiDevice.findObjectWithTimeout(logOutSelector).click() + } +} diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/ConnectPage.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/ConnectPage.kt index 320c01d7aad7..552fc17ee4e1 100644 --- a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/ConnectPage.kt +++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/ConnectPage.kt @@ -9,6 +9,7 @@ class ConnectPage internal constructor() : Page() { private val cancelSelector = By.text("Cancel") private val connectedSelector = By.text("CONNECTED") private val connectingSelector = By.text("CONNECTING...") + private val disconnectedSelector = By.text("DISCONNECTED") override fun assertIsDisplayed() { uiDevice.findObjectWithTimeout(By.res(CONNECT_CARD_HEADER_TEST_TAG)) @@ -34,6 +35,10 @@ class ConnectPage internal constructor() : Page() { uiDevice.findObjectWithTimeout(connectedSelector, timeout) } + fun waitForDisconnectedLabel(timeout: Long = VERY_LONG_TIMEOUT) { + uiDevice.findObjectWithTimeout(disconnectedSelector, timeout) + } + fun waitForConnectingLabel() { uiDevice.findObjectWithTimeout(connectingSelector) } diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/MullvadWebsite.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/MullvadWebsite.kt new file mode 100644 index 000000000000..a9de089b2415 --- /dev/null +++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/MullvadWebsite.kt @@ -0,0 +1,14 @@ +package net.mullvad.mullvadvpn.test.common.page + +import androidx.test.uiautomator.By +import net.mullvad.mullvadvpn.test.common.constant.VERY_LONG_TIMEOUT +import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout + +class MullvadWebsite internal constructor() : Page() { + override fun assertIsDisplayed() { + uiDevice.findObjectWithTimeout( + selector = By.text("Mullvad help center"), + timeout = VERY_LONG_TIMEOUT, + ) + } +} diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/SettingsPage.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/SettingsPage.kt index 86a317d153e5..ebeb8eea4a82 100644 --- a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/SettingsPage.kt +++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/SettingsPage.kt @@ -5,6 +5,7 @@ import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout class SettingsPage internal constructor() : Page() { private val settingsSelector = By.text("Settings") + private val faqAndGuidesSelector = By.text("FAQs & Guides") override fun assertIsDisplayed() { uiDevice.findObjectWithTimeout(settingsSelector) @@ -14,6 +15,10 @@ class SettingsPage internal constructor() : Page() { uiDevice.findObjectWithTimeout(By.res(VPN_SETTINGS_CELL_TEST_TAG)).click() } + fun clickFaqAndGuides() { + uiDevice.findObjectWithTimeout(faqAndGuidesSelector).click() + } + companion object { const val VPN_SETTINGS_CELL_TEST_TAG = "vpn_settings_cell_test_tag" } diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/VpnSettingsPage.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/VpnSettingsPage.kt index 698c84aa6bd4..1a74b8023276 100644 --- a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/VpnSettingsPage.kt +++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/VpnSettingsPage.kt @@ -45,12 +45,18 @@ class VpnSettingsPage internal constructor() : Page() { scrollView2.scrollUntil(Direction.DOWN, Until.hasObject(By.res(testTag))) } + fun clickWireguardCustomPort() { + uiDevice.findObjectWithTimeout(By.res(WIREGUARD_CUSTOM_PORT_CELL_TEST_TAG)).click() + } + companion object { const val SETTINGS_SCROLL_VIEW_TEST_TAG = "lazy_list_vpn_settings_test_tag" const val WIREGUARD_OBFUSCATION_UDP_OVER_TCP_CELL_TEST_TAG = "wireguard_obfuscation_udp_over_tcp_cell_test_tag" const val WIREGUARD_OBFUSCATION_OFF_CELL_TEST_TAG = "wireguard_obfuscation_off_cell_test_tag" + const val WIREGUARD_CUSTOM_PORT_CELL_TEST_TAG = + "lazy_list_wireguard_custom_port_text_test_tag" const val SWITCH_TEST_TAG = "switch_test_tag" } } diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/WireGuardCustomPortDialog.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/WireGuardCustomPortDialog.kt new file mode 100644 index 000000000000..ee112a5d462b --- /dev/null +++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/WireGuardCustomPortDialog.kt @@ -0,0 +1,30 @@ +package net.mullvad.mullvadvpn.test.common.page + +import androidx.test.uiautomator.By +import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout + +class WireGuardCustomPortDialog internal constructor() : Page() { + private val textFieldLabelSelector = By.text("Enter port") + private val setPortSelector = By.text("Set port") + private val cancelSelector = By.text("Cancel") + + override fun assertIsDisplayed() { + uiDevice.findObjectWithTimeout(textFieldLabelSelector) + } + + fun enterCustomPort(port: String) { + uiDevice.findObjectWithTimeout(textFieldLabelSelector).parent.text = port + } + + fun clickSetPort() { + uiDevice.findObjectWithTimeout(setPortSelector).click() + } + + fun clickCancel() { + uiDevice.findObjectWithTimeout(cancelSelector).click() + } + + companion object { + const val TEXT_FIELD_TEST_TAG = "custom_port_dialog_input_test_tag" + } +} diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt index 47f2c720685d..51b6d84708bc 100644 --- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt +++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LeakTest.kt @@ -1,16 +1,17 @@ package net.mullvad.mullvadvpn.test.e2e -import androidx.test.uiautomator.By import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking -import net.mullvad.mullvadvpn.compose.test.EXPAND_BUTTON_TEST_TAG -import net.mullvad.mullvadvpn.compose.test.SELECT_LOCATION_BUTTON_TEST_TAG -import net.mullvad.mullvadvpn.compose.test.SWITCH_TEST_TAG -import net.mullvad.mullvadvpn.compose.test.TOP_BAR_SETTINGS_BUTTON -import net.mullvad.mullvadvpn.test.common.constant.VERY_LONG_TIMEOUT -import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout import net.mullvad.mullvadvpn.test.common.misc.Attachment +import net.mullvad.mullvadvpn.test.common.page.ConnectPage +import net.mullvad.mullvadvpn.test.common.page.SelectLocationPage +import net.mullvad.mullvadvpn.test.common.page.SettingsPage +import net.mullvad.mullvadvpn.test.common.page.SystemVpnConfigurationAlert +import net.mullvad.mullvadvpn.test.common.page.TopBar +import net.mullvad.mullvadvpn.test.common.page.VpnSettingsPage +import net.mullvad.mullvadvpn.test.common.page.WireGuardCustomPortDialog +import net.mullvad.mullvadvpn.test.common.page.on import net.mullvad.mullvadvpn.test.common.rule.ForgetAllVpnAppsInSettingsTestRule import net.mullvad.mullvadvpn.test.e2e.annotations.HasDependencyOnLocalAPI import net.mullvad.mullvadvpn.test.e2e.misc.AccountTestRule @@ -34,18 +35,22 @@ class LeakTest : EndToEndTest(BuildConfig.FLAVOR_infrastructure) { @BeforeEach fun setupVPNSettings() { app.launchAndEnsureLoggedIn(accountTestRule.validAccountNumber) - device.findObjectWithTimeout(By.res(TOP_BAR_SETTINGS_BUTTON)).click() - device.findObjectWithTimeout(By.text("VPN settings")).click() - val localNetworkSharingCell = - device.findObjectWithTimeout(By.text("Local network sharing")).parent - val localNetworkSharingSwitch = - localNetworkSharingCell.findObjectWithTimeout(By.res(SWITCH_TEST_TAG)) + on { clickSettings() } - localNetworkSharingSwitch.click() + on { clickVpnSettings() } - // Only use port 51820 to make packet capture more deterministic - device.findObjectWithTimeout(By.text("51820")).click() + on { + clickLocalNetworkSharingSwitch() + clickWireguardCustomPort() + } + + on { + enterCustomPort("51820") + clickSetPort() + } + + on {} device.pressBack() device.pressBack() @@ -56,18 +61,26 @@ class LeakTest : EndToEndTest(BuildConfig.FLAVOR_infrastructure) { fun testNegativeLeak() = runBlocking { app.launch() - device.findObjectWithTimeout(By.text("DISCONNECTED")) - val targetIpAddress = BuildConfig.TRAFFIC_GENERATION_IP_ADDRESS - val targetPort = 80 + on { + waitForDisconnectedLabel() + + clickSelectLocation() + } - device.findObjectWithTimeout(By.res(SELECT_LOCATION_BUTTON_TEST_TAG)).click() - clickLocationExpandButton(DEFAULT_COUNTRY) - clickLocationExpandButton(DEFAULT_CITY) - device.findObjectWithTimeout(By.text(DEFAULT_RELAY)).click() - device.findObjectWithTimeout(By.text("OK")).click() - device.findObjectWithTimeout(By.text("CONNECTED"), VERY_LONG_TIMEOUT) + on { + clickLocationExpandButton(DEFAULT_COUNTRY) + clickLocationExpandButton(DEFAULT_CITY) + clickLocationCell(DEFAULT_RELAY) + } + on { clickOk() } + + on { waitForConnectedLabel() } + + // Capture generated traffic to a specific host + val targetIpAddress = BuildConfig.TRAFFIC_GENERATION_IP_ADDRESS + val targetPort = 80 val captureResult = PacketCapture().capturePackets { TrafficGenerator(targetIpAddress, targetPort).generateTraffic(10.milliseconds) { @@ -76,11 +89,10 @@ class LeakTest : EndToEndTest(BuildConfig.FLAVOR_infrastructure) { } } - device.findObjectWithTimeout(By.text("Disconnect")).click() + on { clickDisconnect() } val capturedStreams = captureResult.streams val capturedPcap = captureResult.pcap - val timestamp = System.currentTimeMillis() Attachment.saveAttachment("capture-testNegativeLeak-$timestamp.pcap", capturedPcap) @@ -93,37 +105,51 @@ class LeakTest : EndToEndTest(BuildConfig.FLAVOR_infrastructure) { fun testShouldHaveNegativeLeak() = runBlocking { app.launch() - device.findObjectWithTimeout(By.text("DISCONNECTED")) - val targetIpAddress = BuildConfig.TRAFFIC_GENERATION_IP_ADDRESS - val targetPort = 80 + on { + waitForDisconnectedLabel() + + clickSelectLocation() + } + + on { + clickLocationExpandButton(DEFAULT_COUNTRY) + clickLocationExpandButton(DEFAULT_CITY) + clickLocationCell(DEFAULT_RELAY) + } - device.findObjectWithTimeout(By.res(SELECT_LOCATION_BUTTON_TEST_TAG)).click() - delay(1000.milliseconds) - clickLocationExpandButton(DEFAULT_COUNTRY) - clickLocationExpandButton(DEFAULT_CITY) - device.findObjectWithTimeout(By.text(DEFAULT_RELAY)).click() - device.findObjectWithTimeout(By.text("OK")).click() - device.findObjectWithTimeout(By.text("CONNECTED"), VERY_LONG_TIMEOUT) + on { clickOk() } + on { waitForConnectedLabel() } + + // Capture generated traffic to a specific host + val targetIpAddress = BuildConfig.TRAFFIC_GENERATION_IP_ADDRESS + val targetPort = 80 val captureResult: PacketCaptureResult = PacketCapture().capturePackets { TrafficGenerator(targetIpAddress, targetPort).generateTraffic(10.milliseconds) { delay( 3000.milliseconds ) // Give it some time for generating traffic in tunnel - device.findObjectWithTimeout(By.text("Disconnect")).click() + + on { clickDisconnect() } + delay( 2000.milliseconds ) // Give it some time to leak traffic outside of tunnel - device.findObjectWithTimeout(By.text("Connect")).click() + + on { + clickConnect() + waitForConnectedLabel() + } + delay( 3000.milliseconds ) // Give it some time for generating traffic in tunnel } } - device.findObjectWithTimeout(By.text("Disconnect")).click() + on { clickDisconnect() } val capturedStreams = captureResult.streams val capturedPcap = captureResult.pcap @@ -133,10 +159,4 @@ class LeakTest : EndToEndTest(BuildConfig.FLAVOR_infrastructure) { val leakRules = listOf(NoTrafficToHostRule(targetIpAddress)) LeakCheck.assertLeaks(capturedStreams, leakRules) } - - private fun clickLocationExpandButton(locationName: String) { - val locationCell = device.findObjectWithTimeout(By.text(locationName)).parent.parent - val expandButton = locationCell.findObjectWithTimeout(By.res(EXPAND_BUTTON_TEST_TAG)) - expandButton.click() - } } diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LogoutTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LogoutTest.kt index 7a8c8818f4bc..af03c462c620 100644 --- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LogoutTest.kt +++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LogoutTest.kt @@ -1,9 +1,10 @@ package net.mullvad.mullvadvpn.test.e2e -import androidx.test.uiautomator.By -import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout +import net.mullvad.mullvadvpn.test.common.page.AccountPage +import net.mullvad.mullvadvpn.test.common.page.LoginPage +import net.mullvad.mullvadvpn.test.common.page.TopBar +import net.mullvad.mullvadvpn.test.common.page.on import net.mullvad.mullvadvpn.test.e2e.misc.AccountTestRule -import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension @@ -16,11 +17,10 @@ class LogoutTest : EndToEndTest(BuildConfig.FLAVOR_infrastructure) { // Given app.launchAndEnsureLoggedIn(accountTestRule.validAccountNumber) - // When - app.clickAccountCog() - app.clickActionButtonByText("Log out") + on { clickAccount() } - // Then - assertNotNull(device.findObjectWithTimeout(By.text("Login"))) + on { clickLogOut() } + + on() } } diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/WebLinkTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/WebLinkTest.kt index 1b774d1134d7..402939db7af8 100644 --- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/WebLinkTest.kt +++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/WebLinkTest.kt @@ -1,31 +1,22 @@ package net.mullvad.mullvadvpn.test.e2e -import androidx.test.uiautomator.By import net.mullvad.mullvadvpn.test.common.annotation.SkipForFlavors -import net.mullvad.mullvadvpn.test.common.constant.VERY_LONG_TIMEOUT -import net.mullvad.mullvadvpn.test.common.extension.clickAgreeOnPrivacyDisclaimer -import net.mullvad.mullvadvpn.test.common.extension.clickAllowOnNotificationPermissionPromptIfApiLevel33AndAbove -import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout +import net.mullvad.mullvadvpn.test.common.page.MullvadWebsite +import net.mullvad.mullvadvpn.test.common.page.SettingsPage +import net.mullvad.mullvadvpn.test.common.page.TopBar +import net.mullvad.mullvadvpn.test.common.page.on import org.junit.jupiter.api.Test class WebLinkTest : EndToEndTest(BuildConfig.FLAVOR_infrastructure) { @Test @SkipForFlavors(currentFlavor = BuildConfig.FLAVOR_billing, "play") fun testOpenFaqFromApp() { - // Given - app.launch() + app.launchAndEnsureOnLoginPage() - // When - device.clickAgreeOnPrivacyDisclaimer() - device.clickAllowOnNotificationPermissionPromptIfApiLevel33AndAbove() - device.findObjectWithTimeout(By.text("Login")) - app.clickSettingsCog() - app.clickListItemByText("FAQs & Guides") + on { clickSettings() } - // Then - device.findObjectWithTimeout( - selector = By.text("Mullvad help center"), - timeout = VERY_LONG_TIMEOUT, - ) + on { clickFaqAndGuides() } + + on() } }