Skip to content

Commit

Permalink
Merge branch 'main' of github.com:ooni/probe-multiplatform into issue…
Browse files Browse the repository at this point in the history
…s/174
  • Loading branch information
aanorbel committed Dec 3, 2024
2 parents e79880a + 11ccc98 commit b246b77
Show file tree
Hide file tree
Showing 74 changed files with 1,011 additions and 671 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

Multiplatform (Android and iOS currently) version of the Probe app.

![Validate](https://github.com/ooni/probe-multiplatform/actions/workflows/validate.yml/badge.svg)
![Android Instrumented Tests](https://github.com/ooni/probe-multiplatform/actions/workflows/instrumented-tests.yml/badge.svg)

## Project structure

* `composeApp` is for code that will be shared across your Compose Multiplatform applications.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.isDisplayed
import androidx.compose.ui.test.junit4.createEmptyComposeRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onLast
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.ooni.probe.uitesting

import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.isDisplayed
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.junit4.createEmptyComposeRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithTag
Expand All @@ -14,6 +15,7 @@ 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.Measurement_Title
import ooniprobe.composeapp.generated.resources.OONIRun_Run
import ooniprobe.composeapp.generated.resources.Res
import ooniprobe.composeapp.generated.resources.Test_Circumvention_Fullname
Expand All @@ -22,16 +24,15 @@ 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.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.ooni.probe.data.models.SettingsKey
import org.ooni.probe.uitesting.helpers.checkLinkInsideWebView
import org.ooni.probe.shared.getPluralStringResourceItem
import org.ooni.probe.uitesting.helpers.checkSummaryInsideWebView
import org.ooni.probe.uitesting.helpers.checkTextAnywhereInsideWebView
import org.ooni.probe.uitesting.helpers.clickOnText
import org.ooni.probe.uitesting.helpers.isNewsMediaScan
import org.ooni.probe.uitesting.helpers.isOoni
Expand Down Expand Up @@ -64,13 +65,13 @@ class RunningTestsTest {

clickOnText(Res.string.Dashboard_RunTests_SelectNone)
clickOnText(Res.string.Test_Signal_Fullname)
clickOnText(getPluralString(Res.plurals.Dashboard_RunTests_RunButton_Label, 1, 1))
clickOnRunButton(1)

clickOnText(Res.string.Dashboard_RunV2_RunFinished, timeout = TEST_WAIT_TIMEOUT)

clickOnText(Res.string.Test_InstantMessaging_Fullname)
clickOnText(Res.string.Test_Signal_Fullname)
wait { onNodeWithText(Res.string.measurement).isDisplayed() }
wait { onNodeWithText(Res.string.Measurement_Title).isDisplayed() }
checkSummaryInsideWebView("Signal")
}
}
Expand All @@ -84,13 +85,13 @@ class RunningTestsTest {

clickOnText(Res.string.Dashboard_RunTests_SelectNone)
clickOnText(Res.string.Test_Psiphon_Fullname)
clickOnText(getPluralString(Res.plurals.Dashboard_RunTests_RunButton_Label, 1, 1))
clickOnRunButton(1)

clickOnText(Res.string.Dashboard_RunV2_RunFinished, timeout = TEST_WAIT_TIMEOUT)

clickOnText(Res.string.Test_Circumvention_Fullname)
clickOnText(Res.string.Test_Psiphon_Fullname)
wait { onNodeWithText(Res.string.measurement).isDisplayed() }
wait { onNodeWithText(Res.string.Measurement_Title).isDisplayed() }
checkSummaryInsideWebView("Psiphon")
}
}
Expand All @@ -107,13 +108,13 @@ class RunningTestsTest {
.performScrollToNode(hasText("HTTP Header", substring = true))
.performTouchInput { swipeUp() }
clickOnText("HTTP Header", substring = true)
clickOnText(getPluralString(Res.plurals.Dashboard_RunTests_RunButton_Label, 1, 1))
clickOnRunButton(1)

clickOnText(Res.string.Dashboard_RunV2_RunFinished, timeout = TEST_WAIT_TIMEOUT)

clickOnText(Res.string.Test_Performance_Fullname)
clickOnText("HTTP Header", substring = true)
wait { onNodeWithText(Res.string.measurement).isDisplayed() }
wait { onNodeWithText(Res.string.Measurement_Title).isDisplayed() }
checkSummaryInsideWebView("middleboxes")
}
}
Expand All @@ -130,17 +131,14 @@ class RunningTestsTest {
.performScrollToNode(hasText("stunreachability"))
.performTouchInput { swipeUp() }
clickOnText("stunreachability", substring = true)
clickOnText(getPluralString(Res.plurals.Dashboard_RunTests_RunButton_Label, 1, 1))
clickOnRunButton(1)

clickOnText(Res.string.Dashboard_RunV2_RunFinished, timeout = TEST_WAIT_TIMEOUT)

clickOnText(Res.string.Test_Experimental_Fullname)
compose.onAllNodesWithText("stunreachability")[0].performClick()
wait { onNodeWithText(Res.string.measurement).isDisplayed() }
checkLinkInsideWebView(
"https://ooni.org/nettest/http-requests/",
"STUN Reachability",
)
wait { onNodeWithText(Res.string.Measurement_Title).isDisplayed() }
checkTextAnywhereInsideWebView("stunreachability")
}
}

Expand All @@ -154,17 +152,27 @@ class RunningTestsTest {

clickOnText(Res.string.Dashboard_RunTests_SelectNone)
clickOnText("Trusted International Media")
clickOnText(getPluralString(Res.plurals.Dashboard_RunTests_RunButton_Label, 1, 1))
clickOnRunButton(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() }
wait { onNodeWithText(Res.string.Measurement_Title).isDisplayed() }
checkSummaryInsideWebView("https://www.dw.com")
}
}

private suspend fun ComposeTestRule.clickOnRunButton(quantity: Int) {
clickOnText(
getPluralStringResourceItem(
Res.plurals.Dashboard_RunTests_RunButton_Label,
quantity,
quantity,
),
)
}

companion object {
private val TEST_WAIT_TIMEOUT = 3.minutes
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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.Common_Back
import ooniprobe.composeapp.generated.resources.Res
import ooniprobe.composeapp.generated.resources.Settings_Advanced_DebugLogs
import ooniprobe.composeapp.generated.resources.Settings_Advanced_Label
Expand All @@ -25,7 +26,6 @@ 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
Expand Down Expand Up @@ -126,7 +126,7 @@ class SettingsTest {
clickOnText(Res.string.Settings_Websites_Categories_Label)

clickOnText(Res.string.CategoryCode_ANON_Name)
clickOnContentDescription(Res.string.back)
clickOnContentDescription(Res.string.Common_Back)

onNodeWithText(getString(Res.string.Settings_Websites_Categories_Description, 1))
.assertIsDisplayed()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ 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.Measurement_Title
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
Expand Down Expand Up @@ -75,7 +75,7 @@ class UploadResultTest {

clickOnText(Res.string.Test_Signal_Fullname)

wait { onNodeWithText(Res.string.measurement).isDisplayed() }
wait { onNodeWithText(Res.string.Measurement_Title).isDisplayed() }
checkSummaryInsideWebView("Signal")
}
}
Expand Down Expand Up @@ -105,7 +105,7 @@ class UploadResultTest {

clickOnText("https://www.dw.com")

wait { onNodeWithText(Res.string.measurement).isDisplayed() }
wait { onNodeWithText(Res.string.Measurement_Title).isDisplayed() }
checkSummaryInsideWebView("https://www.dw.com")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,10 @@ fun ComposeTestRule.checkSummaryInsideWebView(text: String) {
}
}

fun ComposeTestRule.checkLinkInsideWebView(
link: String,
text: String,
) {
fun ComposeTestRule.checkTextAnywhereInsideWebView(text: String) {
waitAssertion(WEBSITE_WAIT_TIMEOUT) {
onWebView()
.withElement(findElement(Locator.CSS_SELECTOR, "a[href=\"$link\""))
.withElement(findElement(Locator.CSS_SELECTOR, "*"))
.check(webMatches(getText(), containsString(text)))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ class AndroidOonimkallBridge : OonimkallBridge {
val response = session.httpDo(context, request.toMk())
return OonimkallBridge.HTTPResponse(body = response.body)
}

override fun close() {
session.close()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class AppWorkerManager(
suspend fun configureDescriptorAutoUpdate(): Boolean {
return withContext(backgroundDispatcher) {
val request = PeriodicWorkRequestBuilder<DescriptorUpdateWorker>(1, TimeUnit.DAYS)
.setInitialDelay(1, TimeUnit.DAYS) // avoid immediate start
.build()
workManager.enqueueUniquePeriodicWork(
DescriptorUpdateWorker.AutoUpdateWorkerName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import co.touchlab.kermit.Logger
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.first
import kotlinx.serialization.SerializationException
import kotlinx.serialization.encodeToString
import ooniprobe.composeapp.generated.resources.Dashboard_Running_Running
import ooniprobe.composeapp.generated.resources.Notification_ChannelName
import ooniprobe.composeapp.generated.resources.Res
import ooniprobe.composeapp.generated.resources.notification_channel_name
import org.jetbrains.compose.resources.getString
import org.ooni.probe.AndroidApplication
import org.ooni.probe.R
Expand All @@ -41,21 +42,31 @@ class DescriptorUpdateWorker(
}

override suspend fun doWork(): Result {
return coroutineScope {
val descriptors = getDescriptors() ?: return@coroutineScope Result.failure()
if (descriptors.isEmpty()) return@coroutineScope Result.success(buildWorkData(descriptors.map { it.id }))
try {
val descriptors = getDescriptors() ?: return Result.failure()
if (descriptors.isEmpty()) {
Logger.i("Skipping DescriptorUpdateWorker: no descriptors to update")
return Result.success(buildWorkData(descriptors.map { it.id }))
}
dependencies.getDescriptorUpdate.invoke(descriptors)
return@coroutineScope Result.success(buildWorkData(descriptors.map { it.id }))
return Result.success(buildWorkData(descriptors.map { it.id }))
} catch (e: CancellationException) {
Logger.w("DescriptorUpdateWorker: cancelled", e)
return Result.failure()
}
}

private suspend fun getDescriptors(): List<InstalledTestDescriptorModel>? {
val descriptorsJson = inputData.getString(DATA_KEY_DESCRIPTORS)
if (descriptorsJson != null) {
try {
val ids = json.decodeFromString<List<InstalledTestDescriptorModel.Id>>(descriptorsJson)
val ids =
json.decodeFromString<List<InstalledTestDescriptorModel.Id>>(descriptorsJson)
return testDescriptorRepository.selectByRunIds(ids).first()
} catch (e: Exception) {
} catch (e: SerializationException) {
Logger.w("Could not start update worker: invalid configuration", e)
return null
} catch (e: IllegalArgumentException) {
Logger.w("Could not start update worker: invalid configuration", e)
return null
}
Expand All @@ -68,7 +79,7 @@ class DescriptorUpdateWorker(
notificationManager.createNotificationChannel(
NotificationChannel(
NOTIFICATION_CHANNEL_ID,
getString(Res.string.notification_channel_name),
getString(Res.string.Notification_ChannelName),
NotificationManager.IMPORTANCE_DEFAULT,
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ import kotlinx.serialization.encodeToString
import ooniprobe.composeapp.generated.resources.Dashboard_Running_Running
import ooniprobe.composeapp.generated.resources.Dashboard_Running_Stopping_Notice
import ooniprobe.composeapp.generated.resources.Dashboard_Running_Stopping_Title
import ooniprobe.composeapp.generated.resources.Modal_ResultsNotUploaded_Uploading
import ooniprobe.composeapp.generated.resources.Notification_ChannelName
import ooniprobe.composeapp.generated.resources.Notification_StopTest
import ooniprobe.composeapp.generated.resources.Res
import ooniprobe.composeapp.generated.resources.notification_channel_name
import ooniprobe.composeapp.generated.resources.Results_UploadingMissing
import org.jetbrains.compose.resources.getString
import org.ooni.probe.AndroidApplication
import org.ooni.probe.MainActivity
Expand Down Expand Up @@ -130,7 +130,7 @@ class RunWorker(
notificationManager.createNotificationChannel(
NotificationChannel(
NOTIFICATION_CHANNEL_ID,
getString(Res.string.notification_channel_name),
getString(Res.string.Notification_ChannelName),
NotificationManager.IMPORTANCE_LOW,
),
)
Expand All @@ -143,7 +143,7 @@ class RunWorker(
val progress = state.uploaded + state.failedToUpload + 1
setContentText(
getString(
Res.string.Modal_ResultsNotUploaded_Uploading,
Res.string.Results_UploadingMissing,
"$progress/${state.total}",
),
)
Expand Down
Loading

0 comments on commit b246b77

Please sign in to comment.