From 393ad2fb941397411f5a009076c2dd91bb91d983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Wed, 30 Oct 2024 12:39:39 +0000 Subject: [PATCH] UI Testing for NewsMediaScan --- .github/workflows/instrumented-tests.yml | 9 +- .../ooni/probe/uitesting/DescriptorsTest.kt | 40 ++++-- .../ooni/probe/uitesting/OnboardingTest.kt | 48 ++++--- .../org/ooni/probe/uitesting/RunningTest.kt | 118 +++++++++++------- .../org/ooni/probe/uitesting/SettingsTest.kt | 82 +++++++----- .../ooni/probe/uitesting/UploadResultTest.kt | 80 +++++++++--- .../helpers/ApplicationTestHelper.kt | 7 ++ .../uitesting/helpers/ComposeTestHelpers.kt | 31 ++++- .../drawable-hdpi/onboarding.webp | Bin 0 -> 8032 bytes .../org/ooni/probe/config/TestingFlags.kt | 5 + .../probe/ui/measurement/MeasurementScreen.kt | 3 +- .../kotlin/org/ooni/probe/ui/run/RunScreen.kt | 14 ++- 12 files changed, 310 insertions(+), 127 deletions(-) create mode 100644 composeApp/src/commonMain/composeResources/drawable-hdpi/onboarding.webp create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/probe/config/TestingFlags.kt diff --git a/.github/workflows/instrumented-tests.yml b/.github/workflows/instrumented-tests.yml index 275acc1c..627303c8 100644 --- a/.github/workflows/instrumented-tests.yml +++ b/.github/workflows/instrumented-tests.yml @@ -1,14 +1,17 @@ name: Android Instrumented Tests on: push: - branches: - - main workflow_dispatch: jobs: instrumentation-tests: name: Run Android Instrumented Tests runs-on: macos-latest + + strategy: + matrix: + organization: [ ooni, dw ] + steps: - uses: actions/checkout@v4 @@ -16,7 +19,7 @@ jobs: uses: ./.github/actions/setup - name: Build APKs - run: ./gradlew copyBrandingToCommonResources assembleFullDebug assembleFullDebugAndroidTest + run: ./gradlew copyBrandingToCommonResources assembleFullDebug assembleFullDebugAndroidTest -Porganization=${{ matrix.organization }} - name: Set up Cloud SDK uses: google-github-actions/setup-gcloud@v2 diff --git a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/DescriptorsTest.kt b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/DescriptorsTest.kt index 41cf5c1b..6cfd5b3e 100644 --- a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/DescriptorsTest.kt +++ b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/DescriptorsTest.kt @@ -18,6 +18,14 @@ import androidx.compose.ui.test.swipeDown import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest +import ooniprobe.composeapp.generated.resources.AddDescriptor_Action +import ooniprobe.composeapp.generated.resources.AddDescriptor_AutoUpdate +import ooniprobe.composeapp.generated.resources.AddDescriptor_Title +import ooniprobe.composeapp.generated.resources.Dashboard_Progress_ReviewLink_Action +import ooniprobe.composeapp.generated.resources.Dashboard_ReviewDescriptor_Button_Last +import ooniprobe.composeapp.generated.resources.Dashboard_Runv2_Overview_UninstallLink +import ooniprobe.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.getString import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before @@ -27,9 +35,13 @@ import org.junit.runner.RunWith import org.ooni.engine.OonimkallBridge import org.ooni.engine.TestOonimkallBridge import org.ooni.probe.MainActivity +import org.ooni.probe.config.OrganizationConfig import org.ooni.probe.uitesting.helpers.clickOnText import org.ooni.probe.uitesting.helpers.context import org.ooni.probe.uitesting.helpers.dependencies +import org.ooni.probe.uitesting.helpers.isNewsMediaScan +import org.ooni.probe.uitesting.helpers.onAllNodesWithText +import org.ooni.probe.uitesting.helpers.onNodeWithText import org.ooni.probe.uitesting.helpers.preferences import org.ooni.probe.uitesting.helpers.skipOnboarding import org.ooni.probe.uitesting.helpers.start @@ -50,6 +62,8 @@ class DescriptorsTest { @Test fun installAndUninstall() { runTest { + if (isNewsMediaScan) return@runTest + start( Intent(context, MainActivity::class.java) .setAction(Intent.ACTION_VIEW) @@ -58,7 +72,7 @@ class DescriptorsTest { with(compose) { wait(DESCRIPTOR_DOWNLOAD_WAIT_TIMEOUT) { - onNodeWithText("Install New Link").isDisplayed() + onNodeWithText(Res.string.AddDescriptor_Title).isDisplayed() } onNodeWithText("Testing").assertIsDisplayed() @@ -81,9 +95,10 @@ class DescriptorsTest { assertTrue(preferences.isNetTestEnabled(descriptor, test, isAutoRun = true).first()) clickOnText("Android instrumented tests") - clickOnText("Uninstall Link") + clickOnText(Res.string.Dashboard_Runv2_Overview_UninstallLink) - compose.onAllNodesWithText("Uninstall Link").onLast().performClick() + onAllNodesWithText(Res.string.Dashboard_Runv2_Overview_UninstallLink).onLast() + .performClick() onNodeWithText("Testing").assertIsNotDisplayed() } @@ -93,6 +108,8 @@ class DescriptorsTest { @Test fun installAndUpdate() = runTest { + if (isNewsMediaScan) return@runTest + start( Intent(context, MainActivity::class.java) .setAction(Intent.ACTION_VIEW) @@ -101,10 +118,10 @@ class DescriptorsTest { with(compose) { wait(DESCRIPTOR_DOWNLOAD_WAIT_TIMEOUT) { - onNodeWithText("Install New Link").isDisplayed() + onNodeWithText(Res.string.AddDescriptor_Title).isDisplayed() } - clickOnText("Install updates automatically") - clickOnText("Install Link") + clickOnText(Res.string.AddDescriptor_AutoUpdate) + clickOnText(Res.string.AddDescriptor_Action) Thread.sleep(2000) @@ -114,15 +131,14 @@ class DescriptorsTest { // Pull down to refresh onNodeWithTag("Dashboard-List").performTouchInput { swipeDown() } - wait(DESCRIPTOR_DOWNLOAD_WAIT_TIMEOUT) { - onNodeWithText("Review").isDisplayed() - } - - clickOnText("Review") + clickOnText( + Res.string.Dashboard_Progress_ReviewLink_Action, + timeout = DESCRIPTOR_DOWNLOAD_WAIT_TIMEOUT + ) wait { onNodeWithText("Testing 2").isDisplayed() } - clickOnText("UPDATE AND FINISH (1 of 1)") + clickOnText(getString(Res.string.Dashboard_ReviewDescriptor_Button_Last, 1, 1)) onNodeWithTag("Dashboard-List") .performScrollToNode(hasText("Android instrumented tests")) diff --git a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/OnboardingTest.kt b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/OnboardingTest.kt index 62b319a9..6587988b 100644 --- a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/OnboardingTest.kt +++ b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/OnboardingTest.kt @@ -2,11 +2,21 @@ package org.ooni.probe.uitesting import androidx.compose.ui.test.isDisplayed import androidx.compose.ui.test.junit4.createEmptyComposeRule -import androidx.compose.ui.test.onNodeWithContentDescription -import androidx.compose.ui.test.onNodeWithText import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest +import ooniprobe.composeapp.generated.resources.Modal_EnableNotifications_Title +import ooniprobe.composeapp.generated.resources.Onboarding_AutomatedTesting_Title +import ooniprobe.composeapp.generated.resources.Onboarding_Crash_Title +import ooniprobe.composeapp.generated.resources.Onboarding_DefaultSettings_Button_Go +import ooniprobe.composeapp.generated.resources.Onboarding_DefaultSettings_Title +import ooniprobe.composeapp.generated.resources.Onboarding_PopQuiz_True +import ooniprobe.composeapp.generated.resources.Onboarding_ThingsToKnow_Button +import ooniprobe.composeapp.generated.resources.Onboarding_ThingsToKnow_Title +import ooniprobe.composeapp.generated.resources.Onboarding_WhatIsOONIProbe_GotIt +import ooniprobe.composeapp.generated.resources.Onboarding_WhatIsOONIProbe_Title +import ooniprobe.composeapp.generated.resources.Res +import ooniprobe.composeapp.generated.resources.app_name import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule @@ -16,6 +26,8 @@ import org.ooni.probe.data.models.SettingsKey import org.ooni.probe.uitesting.helpers.clickOnTag import org.ooni.probe.uitesting.helpers.clickOnText import org.ooni.probe.uitesting.helpers.dependencies +import org.ooni.probe.uitesting.helpers.onNodeWithContentDescription +import org.ooni.probe.uitesting.helpers.onNodeWithText import org.ooni.probe.uitesting.helpers.preferences import org.ooni.probe.uitesting.helpers.start import org.ooni.probe.uitesting.helpers.wait @@ -35,31 +47,39 @@ class OnboardingTest { fun onboarding() = runTest { with(compose) { - wait { onNodeWithText("What is OONI Probe?").isDisplayed() } - clickOnText("Got It") + wait { + onNodeWithText(Res.string.Onboarding_WhatIsOONIProbe_Title) + .isDisplayed() + } + clickOnText(Res.string.Onboarding_WhatIsOONIProbe_GotIt) - wait { onNodeWithText("Heads-up!").isDisplayed() } - clickOnText("I understand") + wait { + onNodeWithText(Res.string.Onboarding_ThingsToKnow_Title) + .isDisplayed() + } + clickOnText(Res.string.Onboarding_ThingsToKnow_Button) // Quiz - clickOnText("True") - clickOnText("True") + clickOnText(Res.string.Onboarding_PopQuiz_True) + clickOnText(Res.string.Onboarding_PopQuiz_True) - wait { onNodeWithText("Automated testing").isDisplayed() } + wait { onNodeWithText(Res.string.Onboarding_AutomatedTesting_Title).isDisplayed() } clickOnTag("No-AutoTest") - wait { onNodeWithText("Crash Reporting").isDisplayed() } + wait { onNodeWithText(Res.string.Onboarding_Crash_Title).isDisplayed() } clickOnTag("Yes-CrashReporting") if (dependencies.platformInfo.needsToRequestNotificationsPermission) { - wait { onNodeWithText("Get updates on internet censorship").isDisplayed() } + wait { + onNodeWithText(Res.string.Modal_EnableNotifications_Title).isDisplayed() + } clickOnTag("No-Notifications") } - wait { onNodeWithText("Default Settings").isDisplayed() } - clickOnText("Let’s go") + wait { onNodeWithText(Res.string.Onboarding_DefaultSettings_Title).isDisplayed() } + clickOnText(Res.string.Onboarding_DefaultSettings_Button_Go) - wait { onNodeWithContentDescription("OONI Probe").isDisplayed() } + wait { onNodeWithContentDescription(Res.string.app_name).isDisplayed() } } assertEquals( diff --git a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/RunningTest.kt b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/RunningTest.kt index f475220d..78fb962f 100644 --- a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/RunningTest.kt +++ b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/RunningTest.kt @@ -5,21 +5,38 @@ import androidx.compose.ui.test.isDisplayed import androidx.compose.ui.test.junit4.createEmptyComposeRule import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onNodeWithTag -import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollToNode import androidx.compose.ui.test.performTouchInput import androidx.compose.ui.test.swipeUp import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.test.runTest +import ooniprobe.composeapp.generated.resources.Dashboard_RunTests_RunButton_Label +import ooniprobe.composeapp.generated.resources.Dashboard_RunTests_SelectNone +import ooniprobe.composeapp.generated.resources.Dashboard_RunV2_RunFinished +import ooniprobe.composeapp.generated.resources.OONIRun_Run +import ooniprobe.composeapp.generated.resources.Res +import ooniprobe.composeapp.generated.resources.Test_Circumvention_Fullname +import ooniprobe.composeapp.generated.resources.Test_Experimental_Fullname +import ooniprobe.composeapp.generated.resources.Test_InstantMessaging_Fullname +import ooniprobe.composeapp.generated.resources.Test_Performance_Fullname +import ooniprobe.composeapp.generated.resources.Test_Psiphon_Fullname +import ooniprobe.composeapp.generated.resources.Test_Signal_Fullname +import ooniprobe.composeapp.generated.resources.measurement +import org.jetbrains.compose.resources.getPluralString import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.ooni.probe.config.OrganizationConfig +import org.ooni.probe.config.TestingFlags import org.ooni.probe.data.models.SettingsKey import org.ooni.probe.uitesting.helpers.checkLinkInsideWebView import org.ooni.probe.uitesting.helpers.checkSummaryInsideWebView import org.ooni.probe.uitesting.helpers.clickOnText +import org.ooni.probe.uitesting.helpers.isNewsMediaScan +import org.ooni.probe.uitesting.helpers.isOoni +import org.ooni.probe.uitesting.helpers.onNodeWithText import org.ooni.probe.uitesting.helpers.preferences import org.ooni.probe.uitesting.helpers.skipOnboarding import org.ooni.probe.uitesting.helpers.start @@ -34,6 +51,7 @@ class RunningTest { @Before fun setUp() = runTest { + TestingFlags.webviewJavascriptEnabled = true skipOnboarding() preferences.setValueByKey(SettingsKey.UPLOAD_RESULTS, true) start() @@ -42,21 +60,19 @@ class RunningTest { @Test fun signal() = runTest { + if (!isOoni) return@runTest with(compose) { - clickOnText("Run") + clickOnText(Res.string.OONIRun_Run) - clickOnText("Deselect all tests") - clickOnText("Signal Test") - clickOnText("Run 1 test") + clickOnText(Res.string.Dashboard_RunTests_SelectNone) + clickOnText(Res.string.Test_Signal_Fullname) + clickOnText(getPluralString(Res.plurals.Dashboard_RunTests_RunButton_Label, 1, 1)) - wait(TEST_WAIT_TIMEOUT) { - onNodeWithText("Run finished. Tap to view results.").isDisplayed() - } - clickOnText("Run finished. Tap to view results.") + clickOnText(Res.string.Dashboard_RunV2_RunFinished, timeout = TEST_WAIT_TIMEOUT) - clickOnText("Instant Messaging") - clickOnText("Signal Test") - wait { onNodeWithText("Measurement").isDisplayed() } + clickOnText(Res.string.Test_InstantMessaging_Fullname) + clickOnText(Res.string.Test_Signal_Fullname) + wait { onNodeWithText(Res.string.measurement).isDisplayed() } checkSummaryInsideWebView("Signal") } } @@ -64,21 +80,19 @@ class RunningTest { @Test fun psiphon() = runTest { + if (!isOoni) return@runTest with(compose) { - clickOnText("Run") + clickOnText(Res.string.OONIRun_Run) - clickOnText("Deselect all tests") - clickOnText("Psiphon Test") - clickOnText("Run 1 test") + clickOnText(Res.string.Dashboard_RunTests_SelectNone) + clickOnText(Res.string.Test_Psiphon_Fullname) + clickOnText(getPluralString(Res.plurals.Dashboard_RunTests_RunButton_Label, 1, 1)) - wait(TEST_WAIT_TIMEOUT) { - onNodeWithText("Run finished. Tap to view results.").isDisplayed() - } - clickOnText("Run finished. Tap to view results.") + clickOnText(Res.string.Dashboard_RunV2_RunFinished, timeout = TEST_WAIT_TIMEOUT) - clickOnText("Circumvention") - clickOnText("Psiphon Test") - wait { onNodeWithText("Measurement").isDisplayed() } + clickOnText(Res.string.Test_Circumvention_Fullname) + clickOnText(Res.string.Test_Psiphon_Fullname) + wait { onNodeWithText(Res.string.measurement).isDisplayed() } checkSummaryInsideWebView("Psiphon") } } @@ -86,24 +100,22 @@ class RunningTest { @Test fun httpHeader() = runTest { + if (!isOoni) return@runTest with(compose) { - clickOnText("Run") + clickOnText(Res.string.OONIRun_Run) - clickOnText("Deselect all tests") + clickOnText(Res.string.Dashboard_RunTests_SelectNone) onNodeWithTag("Run-DescriptorsList") .performScrollToNode(hasText("HTTP Header", substring = true)) .performTouchInput { swipeUp() } clickOnText("HTTP Header", substring = true) - clickOnText("Run 1 test") + clickOnText(getPluralString(Res.plurals.Dashboard_RunTests_RunButton_Label, 1, 1)) - wait(TEST_WAIT_TIMEOUT) { - onNodeWithText("Run finished. Tap to view results.").isDisplayed() - } - clickOnText("Run finished. Tap to view results.") + clickOnText(Res.string.Dashboard_RunV2_RunFinished, timeout = TEST_WAIT_TIMEOUT) - clickOnText("Performance") + clickOnText(Res.string.Test_Performance_Fullname) clickOnText("HTTP Header", substring = true) - wait { onNodeWithText("Measurement").isDisplayed() } + wait { onNodeWithText(Res.string.measurement).isDisplayed() } checkSummaryInsideWebView("middleboxes") } } @@ -111,25 +123,47 @@ class RunningTest { @Test fun stunReachability() = runTest { + if (!isOoni) return@runTest with(compose) { - clickOnText("Run") + clickOnText(Res.string.OONIRun_Run) - clickOnText("Deselect all tests") + clickOnText(Res.string.Dashboard_RunTests_SelectNone) onNodeWithTag("Run-DescriptorsList") .performScrollToNode(hasText("stunreachability")) .performTouchInput { swipeUp() } clickOnText("stunreachability", substring = true) - clickOnText("Run 1 test") + clickOnText(getPluralString(Res.plurals.Dashboard_RunTests_RunButton_Label, 1, 1)) - wait(TEST_WAIT_TIMEOUT) { - onNodeWithText("Run finished. Tap to view results.").isDisplayed() - } - clickOnText("Run finished. Tap to view results.") + clickOnText(Res.string.Dashboard_RunV2_RunFinished, timeout = TEST_WAIT_TIMEOUT) - clickOnText("Experimental") + clickOnText(Res.string.Test_Experimental_Fullname) compose.onAllNodesWithText("stunreachability")[0].performClick() - wait { onNodeWithText("Measurement").isDisplayed() } - checkLinkInsideWebView("https://ooni.org/nettest/http-requests/", "STUN Reachability") + wait { onNodeWithText(Res.string.measurement).isDisplayed() } + checkLinkInsideWebView( + "https://ooni.org/nettest/http-requests/", + "STUN Reachability" + ) + } + } + + + @Test + fun trustedInternationalMedia() = + runTest { + if (!isNewsMediaScan) return@runTest + with(compose) { + clickOnText(Res.string.OONIRun_Run) + + clickOnText(Res.string.Dashboard_RunTests_SelectNone) + clickOnText("Trusted International Media") + clickOnText(getPluralString(Res.plurals.Dashboard_RunTests_RunButton_Label, 1, 1)) + + clickOnText(Res.string.Dashboard_RunV2_RunFinished, timeout = TEST_WAIT_TIMEOUT) + + clickOnText("Trusted International Media") + clickOnText("https://www.dw.com") + wait { onNodeWithText(Res.string.measurement).isDisplayed() } + checkSummaryInsideWebView("https://www.dw.com") } } diff --git a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/SettingsTest.kt b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/SettingsTest.kt index 10a0e7e1..16cd017b 100644 --- a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/SettingsTest.kt +++ b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/SettingsTest.kt @@ -6,6 +6,27 @@ import androidx.compose.ui.test.onNodeWithText import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest +import ooniprobe.composeapp.generated.resources.CategoryCode_ANON_Name +import ooniprobe.composeapp.generated.resources.Res +import ooniprobe.composeapp.generated.resources.Settings_Advanced_DebugLogs +import ooniprobe.composeapp.generated.resources.Settings_Advanced_Label +import ooniprobe.composeapp.generated.resources.Settings_AutomatedTesting_RunAutomatically +import ooniprobe.composeapp.generated.resources.Settings_AutomatedTesting_RunAutomatically_ChargingOnly +import ooniprobe.composeapp.generated.resources.Settings_AutomatedTesting_RunAutomatically_WiFiOnly +import ooniprobe.composeapp.generated.resources.Settings_Notifications_Enabled +import ooniprobe.composeapp.generated.resources.Settings_Notifications_Label +import ooniprobe.composeapp.generated.resources.Settings_Privacy_Label +import ooniprobe.composeapp.generated.resources.Settings_Privacy_SendCrashReports +import ooniprobe.composeapp.generated.resources.Settings_Proxy_Label +import ooniprobe.composeapp.generated.resources.Settings_Proxy_Psiphon +import ooniprobe.composeapp.generated.resources.Settings_Sharing_UploadResults +import ooniprobe.composeapp.generated.resources.Settings_TestOptions_Label +import ooniprobe.composeapp.generated.resources.Settings_Title +import ooniprobe.composeapp.generated.resources.Settings_WarmVPNInUse_Label +import ooniprobe.composeapp.generated.resources.Settings_Websites_Categories_Description +import ooniprobe.composeapp.generated.resources.Settings_Websites_Categories_Label +import ooniprobe.composeapp.generated.resources.back +import org.jetbrains.compose.resources.getString import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule @@ -39,9 +60,9 @@ class SettingsTest { assertTrue(preferences.getValueByKey(SettingsKey.NOTIFICATIONS_ENABLED).first() != true) with(compose) { - clickOnText("Settings") - clickOnText("Notifications") - clickOnText("Enabled") + clickOnText(Res.string.Settings_Title) + clickOnText(Res.string.Settings_Notifications_Label) + clickOnText(Res.string.Settings_Notifications_Enabled) wait { preferences.getValueByKey(SettingsKey.NOTIFICATIONS_ENABLED).first() == true @@ -62,19 +83,19 @@ class SettingsTest { ) with(compose) { - clickOnText("Settings") - clickOnText("Test options") + clickOnText(Res.string.Settings_Title) + clickOnText(Res.string.Settings_TestOptions_Label) - clickOnText("Automatically Publish Results") + clickOnText(Res.string.Settings_Sharing_UploadResults) wait { preferences.getValueByKey(SettingsKey.UPLOAD_RESULTS).first() == true } - clickOnText("Run tests automatically") + clickOnText(Res.string.Settings_AutomatedTesting_RunAutomatically) wait { preferences.getValueByKey(SettingsKey.AUTOMATED_TESTING_ENABLED).first() == true } - clickOnText("Only on WiFi") - clickOnText("Only while charging") + clickOnText(Res.string.Settings_AutomatedTesting_RunAutomatically_WiFiOnly) + clickOnText(Res.string.Settings_AutomatedTesting_RunAutomatically_ChargingOnly) wait { preferences.getValueByKey(SettingsKey.AUTOMATED_TESTING_WIFIONLY) @@ -95,16 +116,18 @@ class SettingsTest { ) with(compose) { - clickOnText("Settings") - clickOnText("Test options") + clickOnText(Res.string.Settings_Title) + clickOnText(Res.string.Settings_TestOptions_Label) - onNodeWithText("0 categories enabled").assertIsDisplayed() - clickOnText("Website categories to test") + onNodeWithText(getString(Res.string.Settings_Websites_Categories_Description, 0)) + .assertIsDisplayed() + clickOnText(Res.string.Settings_Websites_Categories_Label) - clickOnText("Circumvention tools") - clickOnContentDescription("Back") + clickOnText(Res.string.CategoryCode_ANON_Name) + clickOnContentDescription(Res.string.back) - clickOnText("1 categories enabled") + onNodeWithText(getString(Res.string.Settings_Websites_Categories_Description, 1)) + .assertIsDisplayed() wait { preferences.getValueByKey(WebConnectivityCategory.ANON.settingsKey!!) @@ -123,9 +146,9 @@ class SettingsTest { ) with(compose) { - clickOnText("Settings") - clickOnText("Privacy") - clickOnText("Send crash reports") + clickOnText(Res.string.Settings_Title) + clickOnText(Res.string.Settings_Privacy_Label) + clickOnText(Res.string.Settings_Privacy_SendCrashReports) wait { preferences.getValueByKey(SettingsKey.SEND_CRASH).first() == true @@ -143,9 +166,9 @@ class SettingsTest { ) with(compose) { - clickOnText("Settings") - clickOnText("OONI backend proxy") - clickOnText("Psiphon") + clickOnText(Res.string.Settings_Title) + clickOnText(Res.string.Settings_Proxy_Label) + clickOnText(Res.string.Settings_Proxy_Psiphon) wait { preferences.getValueByKey(SettingsKey.PROXY_PROTOCOL) @@ -165,15 +188,14 @@ class SettingsTest { ) with(compose) { - clickOnText("Settings") - clickOnText("Advanced") - clickOnText("Debug logs") - clickOnText("Warn when VPN is in use") + clickOnText(Res.string.Settings_Title) + clickOnText(Res.string.Settings_Advanced_Label) - wait { - preferences.getValueByKey(SettingsKey.DEBUG_LOGS).first() == true && - preferences.getValueByKey(SettingsKey.WARN_VPN_IN_USE).first() == true - } + clickOnText(Res.string.Settings_Advanced_DebugLogs) + wait { preferences.getValueByKey(SettingsKey.DEBUG_LOGS).first() == true } + + clickOnText(Res.string.Settings_WarmVPNInUse_Label) + wait { preferences.getValueByKey(SettingsKey.WARN_VPN_IN_USE).first() == true } } } } diff --git a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/UploadResultTest.kt b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/UploadResultTest.kt index e7e084e7..4b1ab1dc 100644 --- a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/UploadResultTest.kt +++ b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/UploadResultTest.kt @@ -6,14 +6,30 @@ import androidx.compose.ui.test.junit4.createEmptyComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.test.runTest +import ooniprobe.composeapp.generated.resources.Dashboard_RunTests_RunButton_Label +import ooniprobe.composeapp.generated.resources.Dashboard_RunTests_SelectNone +import ooniprobe.composeapp.generated.resources.Dashboard_RunV2_RunFinished +import ooniprobe.composeapp.generated.resources.Modal_ResultsNotUploaded_Uploading +import ooniprobe.composeapp.generated.resources.OONIRun_Run +import ooniprobe.composeapp.generated.resources.Res +import ooniprobe.composeapp.generated.resources.Snackbar_ResultsSomeNotUploaded_UploadAll +import ooniprobe.composeapp.generated.resources.Test_InstantMessaging_Fullname +import ooniprobe.composeapp.generated.resources.Test_Signal_Fullname +import ooniprobe.composeapp.generated.resources.measurement +import org.jetbrains.compose.resources.getPluralString +import org.jetbrains.compose.resources.getString import org.junit.Before import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.ooni.probe.config.TestingFlags import org.ooni.probe.data.models.SettingsKey import org.ooni.probe.uitesting.helpers.checkSummaryInsideWebView import org.ooni.probe.uitesting.helpers.clickOnText +import org.ooni.probe.uitesting.helpers.isNewsMediaScan +import org.ooni.probe.uitesting.helpers.isOoni +import org.ooni.probe.uitesting.helpers.onNodeWithText import org.ooni.probe.uitesting.helpers.preferences import org.ooni.probe.uitesting.helpers.skipOnboarding import org.ooni.probe.uitesting.helpers.start @@ -29,6 +45,7 @@ class UploadResultTest { @Before fun setUp() = runTest { + TestingFlags.webviewJavascriptEnabled = true skipOnboarding() preferences.setValueByKey(SettingsKey.UPLOAD_RESULTS, false) start() @@ -38,37 +55,62 @@ class UploadResultTest { @Ignore("Single test upload is currently failing on the dev back-end") fun uploadSingleResult() = runTest { + if (!isOoni) return@runTest with(compose) { - clickOnText("Run") + clickOnText(Res.string.OONIRun_Run) - clickOnText("Deselect all tests") - clickOnText("Signal Test") - clickOnText("Run 1 test") + clickOnText(Res.string.Dashboard_RunTests_SelectNone) + clickOnText(Res.string.Test_Signal_Fullname) + clickOnText(getPluralString(Res.plurals.Dashboard_RunTests_RunButton_Label, 1, 1)) - wait(TEST_WAIT_TIMEOUT) { - onNodeWithText("Run finished. Tap to view results.").isDisplayed() - } - clickOnText("Run finished. Tap to view results.") + clickOnText(Res.string.Dashboard_RunV2_RunFinished, timeout = TEST_WAIT_TIMEOUT) - clickOnText("Instant Messaging") - clickOnText("Upload All") + clickOnText(Res.string.Test_InstantMessaging_Fullname) + clickOnText(Res.string.Snackbar_ResultsSomeNotUploaded_UploadAll) - wait { - onNodeWithText("Uploading", substring = true).isDisplayed() - } - waitUntil(10.seconds.inWholeMilliseconds) { - onNodeWithText("Uploading", substring = true).isNotDisplayed() - } + val onUploading = + onNodeWithText(getString(Res.string.Modal_ResultsNotUploaded_Uploading, 1)) + wait { onUploading.isDisplayed() } + wait(10.seconds) { onUploading.isNotDisplayed() } - Thread.sleep(10000) + Thread.sleep(5000) - clickOnText("Signal Test") + clickOnText(Res.string.Test_Signal_Fullname) - wait { onNodeWithText("Measurement").isDisplayed() } + wait { onNodeWithText(Res.string.measurement).isDisplayed() } checkSummaryInsideWebView("Signal") } } + @Test + fun uploadSingleResultNewsMediaScan() = + runTest { + if (!isNewsMediaScan) return@runTest + with(compose) { + clickOnText(Res.string.OONIRun_Run) + + clickOnText(Res.string.Dashboard_RunTests_SelectNone) + clickOnText("Trusted International Media") + clickOnText(getPluralString(Res.plurals.Dashboard_RunTests_RunButton_Label, 1, 1)) + + clickOnText(Res.string.Dashboard_RunV2_RunFinished, timeout = TEST_WAIT_TIMEOUT) + + clickOnText("Trusted International Media") + clickOnText(Res.string.Snackbar_ResultsSomeNotUploaded_UploadAll) + + val onUploading = onNodeWithText("Uploading", substring = true) + wait { onUploading.isDisplayed() } + wait(60.seconds) { onUploading.isNotDisplayed() } + + Thread.sleep(5000) + + clickOnText("https://www.dw.com") + + wait { onNodeWithText(Res.string.measurement).isDisplayed() } + checkSummaryInsideWebView("https://www.dw.com") + } + } + companion object { private val TEST_WAIT_TIMEOUT = 1.minutes } diff --git a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/helpers/ApplicationTestHelper.kt b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/helpers/ApplicationTestHelper.kt index 2a252ac2..374c6aa0 100644 --- a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/helpers/ApplicationTestHelper.kt +++ b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/helpers/ApplicationTestHelper.kt @@ -7,6 +7,7 @@ import androidx.test.platform.app.InstrumentationRegistry import kotlinx.coroutines.Dispatchers import org.ooni.probe.AndroidApplication import org.ooni.probe.MainActivity +import org.ooni.probe.config.OrganizationConfig val context: Context get() = InstrumentationRegistry.getInstrumentation().targetContext @@ -25,3 +26,9 @@ fun start(intent: Intent): ActivityScenario { dependencies.backgroundContext = Dispatchers.Unconfined return ActivityScenario.launch(intent) } + +val isOoni + get() = OrganizationConfig.baseSoftwareName.contains("ooni") + +val isNewsMediaScan + get() = OrganizationConfig.baseSoftwareName.contains("news") diff --git a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/helpers/ComposeTestHelpers.kt b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/helpers/ComposeTestHelpers.kt index 43c99cfe..1c1eee3d 100644 --- a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/helpers/ComposeTestHelpers.kt +++ b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/helpers/ComposeTestHelpers.kt @@ -1,8 +1,10 @@ package org.ooni.probe.uitesting.helpers +import androidx.annotation.StringRes import androidx.compose.ui.test.SemanticsNodeInteraction import androidx.compose.ui.test.isDisplayed import androidx.compose.ui.test.junit4.ComposeTestRule +import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText @@ -12,19 +14,32 @@ import androidx.test.espresso.web.sugar.Web.onWebView import androidx.test.espresso.web.webdriver.DriverAtoms.findElement import androidx.test.espresso.web.webdriver.DriverAtoms.getText import androidx.test.espresso.web.webdriver.Locator +import co.touchlab.kermit.Logger import kotlinx.coroutines.runBlocking import org.hamcrest.CoreMatchers.containsString +import org.jetbrains.compose.resources.StringResource +import org.jetbrains.compose.resources.getString import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds +suspend fun ComposeTestRule.clickOnText( + stringRes: StringResource, + timeout: Duration = DEFAULT_WAIT_TIMEOUT, +): SemanticsNodeInteraction = + clickOnText(getString(stringRes), timeout = timeout) + fun ComposeTestRule.clickOnText( text: String, substring: Boolean = false, + timeout: Duration = DEFAULT_WAIT_TIMEOUT, ): SemanticsNodeInteraction { - wait { onNodeWithText(text, substring = substring).isDisplayed() } + wait(timeout) { onNodeWithText(text, substring = substring).isDisplayed() } return onNodeWithText(text, substring = substring).performClick() } +suspend fun ComposeTestRule.clickOnContentDescription(stringRes: StringResource)= + clickOnContentDescription(getString(stringRes)) + fun ComposeTestRule.clickOnContentDescription(contentDescription: String): SemanticsNodeInteraction { wait { onNodeWithContentDescription(contentDescription).isDisplayed() } return onNodeWithContentDescription(contentDescription).performClick() @@ -35,6 +50,15 @@ fun ComposeTestRule.clickOnTag(tag: String): SemanticsNodeInteraction { return onNodeWithTag(tag).performClick() } +suspend fun ComposeTestRule.onNodeWithText(stringRes: StringResource) = + onNodeWithText(getString(stringRes)) + +suspend fun ComposeTestRule.onAllNodesWithText(stringRes: StringResource) = + onAllNodesWithText(getString(stringRes)) + +suspend fun ComposeTestRule.onNodeWithContentDescription(stringRes: StringResource) = + onNodeWithContentDescription(getString(stringRes)) + fun ComposeTestRule.wait( timeout: Duration = DEFAULT_WAIT_TIMEOUT, check: suspend () -> Boolean, @@ -54,7 +78,8 @@ fun ComposeTestRule.waitAssertion( try { assertion() true - } catch (e: AssertionError) { + } catch (e: RuntimeException) { + Logger.w("waitAssertion failure", e) false } } @@ -83,4 +108,4 @@ fun ComposeTestRule.checkLinkInsideWebView( } } -private val WEBSITE_WAIT_TIMEOUT = 10.seconds +private val WEBSITE_WAIT_TIMEOUT = 20.seconds diff --git a/composeApp/src/commonMain/composeResources/drawable-hdpi/onboarding.webp b/composeApp/src/commonMain/composeResources/drawable-hdpi/onboarding.webp new file mode 100644 index 0000000000000000000000000000000000000000..f1eef02bf6287be7336e22b6edae9d13bcc1385c GIT binary patch literal 8032 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 literal 0 HcmV?d00001 diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/config/TestingFlags.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/config/TestingFlags.kt new file mode 100644 index 00000000..c83a2fe0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/config/TestingFlags.kt @@ -0,0 +1,5 @@ +package org.ooni.probe.config + +object TestingFlags { + var webviewJavascriptEnabled = false +} diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/measurement/MeasurementScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/measurement/MeasurementScreen.kt index 8974bba0..a823601a 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/measurement/MeasurementScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/measurement/MeasurementScreen.kt @@ -36,6 +36,7 @@ import ooniprobe.composeapp.generated.resources.measurement import ooniprobe.composeapp.generated.resources.refresh import org.intellij.markdown.html.urlEncode import org.jetbrains.compose.resources.stringResource +import org.ooni.probe.config.TestingFlags import org.ooni.probe.data.models.MeasurementModel import org.ooni.probe.ui.shared.TopBar @@ -50,7 +51,7 @@ fun MeasurementScreen( LaunchedEffect(url) { Logger.i("URL: $url") } val webViewState = rememberWebViewState(url) - webViewState.webSettings.isJavaScriptEnabled = false + webViewState.webSettings.isJavaScriptEnabled = TestingFlags.webviewJavascriptEnabled val webViewNavigator = rememberWebViewNavigator( // Don't allow other links to open requestInterceptor = object : RequestInterceptor { diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunScreen.kt index 8648864f..00e8533d 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunScreen.kt @@ -18,6 +18,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.selection.toggleable +import androidx.compose.foundation.selection.triStateToggleable import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.AlertDialog @@ -39,6 +41,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.Role import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.unit.dp import ooniprobe.composeapp.generated.resources.Dashboard_RunTests_Description @@ -266,12 +269,17 @@ private fun DescriptorItem( Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() - .clickable { onDropdownToggled() }, + .padding(vertical = 4.dp) + .triStateToggleable( + state = descriptorItem.state, + onClick = { onChecked(descriptorItem.state != ToggleableState.On) }, + role = Role.Checkbox + ), ) { TriStateCheckbox( state = descriptorItem.state, - onClick = { onChecked(descriptorItem.state != ToggleableState.On) }, - modifier = Modifier.padding(end = 16.dp), + onClick = null, + modifier = Modifier.padding(start = 16.dp, end = 20.dp), ) TestDescriptorLabel(descriptor) IconButton(onClick = { onDropdownToggled() }) {