Refactored the ZimHostFragmentTest according to compose.

This commit is contained in:
MohitMaliFtechiz 2025-03-15 16:10:36 +05:30
parent 6e370a1e74
commit a72d021e95
5 changed files with 134 additions and 95 deletions

View File

@ -53,12 +53,14 @@ class ErrorActivityRobot : BaseRobot() {
}
fun assertCheckBoxesDisplayed(composeTestRule: ComposeContentTestRule) {
composeTestRule.onNodeWithText(context.getString(R.string.crash_checkbox_language))
.assertIsDisplayed()
composeTestRule.onNodeWithText(context.getString(R.string.crash_checkbox_logs))
.assertIsDisplayed()
composeTestRule.onNodeWithText(context.getString(R.string.crash_checkbox_zimfiles))
.assertIsDisplayed()
composeTestRule.apply {
onNodeWithText(context.getString(R.string.crash_checkbox_language))
.assertIsDisplayed()
onNodeWithText(context.getString(R.string.crash_checkbox_logs))
.assertIsDisplayed()
onNodeWithText(context.getString(R.string.crash_checkbox_zimfiles))
.assertIsDisplayed()
}
}
fun clickOnSendDetailsButton(composeTestRule: ComposeContentTestRule) {

View File

@ -21,6 +21,7 @@ package org.kiwix.kiwixmobile.webserver
import android.Manifest
import android.content.Context
import android.os.Build
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.accessibility.AccessibilityChecks
@ -31,7 +32,6 @@ import androidx.test.uiautomator.UiDevice
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils.matchesCheck
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils.matchesViews
import com.google.android.apps.common.testing.accessibility.framework.checks.DuplicateClickableBoundsCheck
import leakcanary.LeakAssertions
import org.hamcrest.Matchers.allOf
import org.junit.After
import org.junit.Before
@ -54,6 +54,9 @@ class ZimHostFragmentTest {
@JvmField
var retryRule = RetryRule()
@get:Rule
val composeTestRule = createComposeRule()
private lateinit var sharedPreferenceUtil: SharedPreferenceUtil
private lateinit var activityScenario: ActivityScenario<KiwixMainActivity>
@ -150,53 +153,52 @@ class ZimHostFragmentTest {
openZimHostFragment()
// Check if server is already started
stopServerIfAlreadyStarted()
stopServerIfAlreadyStarted(composeTestRule)
// Check if both zim file are selected or not to properly run our test case
selectZimFileIfNotAlreadySelected()
selectZimFileIfNotAlreadySelected(composeTestRule)
clickOnTestZim()
clickOnTestZim(composeTestRule)
// Start the server with one ZIM file
startServer()
assertServerStarted()
startServer(composeTestRule)
assertServerStarted(composeTestRule)
// Check that only one ZIM file is hosted on the server
assertItemHostedOnServer(1)
assertItemHostedOnServer(1, composeTestRule)
// Check QR code shown
assertQrShown()
assertQrShown(composeTestRule)
// Stop the server
stopServer()
assertServerStopped()
stopServer(composeTestRule)
assertServerStopped(composeTestRule)
// Check QR code not shown after stopping the server
assertQrNotShown()
assertQrNotShown(composeTestRule)
// Select the test ZIM file to host on the server
clickOnTestZim()
clickOnTestZim(composeTestRule)
// Start the server with two ZIM files
startServer()
assertServerStarted()
startServer(composeTestRule)
assertServerStarted(composeTestRule)
// Check that both ZIM files are hosted on the server
assertItemHostedOnServer(2)
assertItemHostedOnServer(2, composeTestRule)
// Unselect the test ZIM to test restarting server functionality
clickOnTestZim()
clickOnTestZim(composeTestRule)
// Check if the server is running
assertServerStarted()
assertServerStarted(composeTestRule)
// Check that only one ZIM file is hosted on the server after unselecting
assertItemHostedOnServer(1)
assertItemHostedOnServer(1, composeTestRule)
// finally close the server at the end of test case
stopServer()
stopServer(composeTestRule)
}
LeakAssertions.assertNoLeaks()
}
}

View File

@ -18,30 +18,28 @@
package org.kiwix.kiwixmobile.webserver
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import applyWithViewHierarchyPrinting
import com.adevinta.android.barista.interaction.BaristaSleepInteractions
import com.adevinta.android.barista.interaction.BaristaSwipeRefreshInteractions.refresh
import junit.framework.AssertionFailedError
import org.hamcrest.CoreMatchers
import org.kiwix.kiwixmobile.BaseRobot
import org.kiwix.kiwixmobile.Findable.StringId.TextId
import org.kiwix.kiwixmobile.Findable.Text
import org.kiwix.kiwixmobile.Findable.ViewId
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.R.id
import org.kiwix.kiwixmobile.core.utils.files.Log
import org.kiwix.kiwixmobile.testutils.TestUtils
import org.kiwix.kiwixmobile.testutils.TestUtils.testFlakyView
import org.kiwix.kiwixmobile.utils.RecyclerViewItemCount
import org.kiwix.kiwixmobile.utils.RecyclerViewMatcher
import org.kiwix.kiwixmobile.utils.RecyclerViewSelectedCheckBoxCountAssertion
import org.kiwix.kiwixmobile.ui.BOOK_ITEM_CHECKBOX_TESTING_TAG
import org.kiwix.kiwixmobile.utils.StandardActions.openDrawer
fun zimHost(func: ZimHostRobot.() -> Unit) = ZimHostRobot().applyWithViewHierarchyPrinting(func)
@ -66,15 +64,20 @@ class ZimHostRobot : BaseRobot() {
clickOn(TextId(R.string.menu_wifi_hotspot))
}
fun clickOnTestZim() {
pauseForBetterTestPerformance()
clickOn(Text("Test_Zim"))
fun clickOnTestZim(composeTestRule: ComposeContentTestRule) {
testFlakyView({
composeTestRule.apply {
waitForIdle()
onNodeWithTag("${BOOK_ITEM_CHECKBOX_TESTING_TAG}2").performClick()
}
})
}
fun startServer() {
fun startServer(composeTestRule: ComposeContentTestRule) {
// stop the server if it is already running.
stopServerIfAlreadyStarted()
clickOn(ViewId(id.startServerButton))
stopServerIfAlreadyStarted(composeTestRule)
composeTestRule.onNodeWithTag(START_SERVER_BUTTON_TESTING_TAG)
.performClick()
assetWifiDialogDisplayed()
testFlakyView({ onView(withText("PROCEED")).perform(click()) })
}
@ -83,24 +86,30 @@ class ZimHostRobot : BaseRobot() {
testFlakyView({ isVisible(Text("WiFi connection detected")) })
}
fun assertServerStarted() {
fun assertServerStarted(composeTestRule: ComposeContentTestRule) {
pauseForBetterTestPerformance()
// starting server takes a bit so sometimes it fails to find this view.
// which makes this view flaky so we are testing this with FlakyView.
testFlakyView({ isVisible(Text("STOP SERVER")) })
testFlakyView({
composeTestRule.apply {
waitForIdle()
onNodeWithTag(START_SERVER_BUTTON_TESTING_TAG)
.assertTextEquals(context.getString(R.string.stop_server_label).uppercase())
}
})
}
fun stopServerIfAlreadyStarted() {
fun stopServerIfAlreadyStarted(composeTestRule: ComposeContentTestRule) {
try {
// Check if the "START SERVER" button is visible because, in most scenarios,
// this button will appear when the server is already stopped.
// This will expedite our test case, as verifying the visibility of
// non-visible views takes more time due to the try mechanism needed
// to properly retrieve the view.
assertServerStopped()
} catch (exception: Exception) {
assertServerStopped(composeTestRule)
} catch (_: Exception) {
// if "START SERVER" button is not visible it means server is started so close it.
stopServer()
stopServer(composeTestRule)
Log.i(
"ZIM_HOST_FRAGMENT",
"Stopped the server to perform our test case since it was already running"
@ -108,66 +117,76 @@ class ZimHostRobot : BaseRobot() {
}
}
fun selectZimFileIfNotAlreadySelected() {
fun selectZimFileIfNotAlreadySelected(composeTestRule: ComposeContentTestRule) {
try {
// check both files are selected.
assertItemHostedOnServer(2)
} catch (assertionFailedError: AssertionFailedError) {
assertItemHostedOnServer(2, composeTestRule)
} catch (_: AssertionFailedError) {
try {
val recyclerViewItemsCount =
RecyclerViewItemCount(id.recyclerViewZimHost).checkRecyclerViewCount()
(0 until recyclerViewItemsCount)
.asSequence()
.filter { it != 0 }
.forEach(::selectZimFile)
} catch (assertionFailedError: AssertionFailedError) {
selectZimFile(1, composeTestRule)
selectZimFile(2, composeTestRule)
} catch (_: AssertionFailedError) {
Log.i("ZIM_HOST_FRAGMENT", "Failed to select the zim file, probably it is already selected")
}
}
}
private fun selectZimFile(position: Int) {
private fun selectZimFile(position: Int, composeTestRule: ComposeContentTestRule) {
try {
onView(
RecyclerViewMatcher(id.recyclerViewZimHost).atPositionOnView(
position,
R.id.itemBookCheckbox
)
).check(matches(ViewMatchers.isChecked()))
} catch (assertionError: AssertionFailedError) {
onView(
RecyclerViewMatcher(id.recyclerViewZimHost).atPositionOnView(
position,
R.id.itemBookCheckbox
)
).perform(click())
composeTestRule.onNodeWithTag("$BOOK_ITEM_CHECKBOX_TESTING_TAG$position")
.assertIsOn()
} catch (_: AssertionFailedError) {
composeTestRule.onNodeWithTag("$BOOK_ITEM_CHECKBOX_TESTING_TAG$position")
.performClick()
}
}
fun assertItemHostedOnServer(itemCount: Int) {
val checkedCheckboxCount =
RecyclerViewSelectedCheckBoxCountAssertion(
id.recyclerViewZimHost,
R.id.itemBookCheckbox
).countCheckedCheckboxes()
assertThat(checkedCheckboxCount, CoreMatchers.`is`(itemCount))
fun assertItemHostedOnServer(itemCount: Int, composeTestRule: ComposeContentTestRule) {
for (i in 0 until itemCount) {
composeTestRule.onNodeWithTag("$BOOK_ITEM_CHECKBOX_TESTING_TAG${i + 1}")
.assertIsOn()
}
}
fun stopServer() {
testFlakyView({ onView(withId(id.startServerButton)).perform(click()) })
fun stopServer(composeTestRule: ComposeContentTestRule) {
testFlakyView(
{
composeTestRule.apply {
waitForIdle()
onNodeWithTag(START_SERVER_BUTTON_TESTING_TAG).performClick()
}
}
)
}
fun assertServerStopped() {
pauseForBetterTestPerformance()
isVisible(Text("START SERVER"))
fun assertServerStopped(composeTestRule: ComposeContentTestRule) {
testFlakyView({
composeTestRule.apply {
waitForIdle()
onNodeWithTag(START_SERVER_BUTTON_TESTING_TAG)
.assertTextEquals(context.getString(R.string.start_server_label).uppercase())
}
})
}
fun assertQrShown() {
isVisible(ViewId(id.serverQrCode))
fun assertQrShown(composeTestRule: ComposeContentTestRule) {
testFlakyView({
composeTestRule.apply {
waitForIdle()
onNodeWithTag(QR_IMAGE_TESTING_TAG)
.assertIsDisplayed()
}
})
}
fun assertQrNotShown() {
isNotVisible(ViewId(id.serverQrCode))
fun assertQrNotShown(composeTestRule: ComposeContentTestRule) {
testFlakyView({
composeTestRule.apply {
waitForIdle()
onNodeWithTag(QR_IMAGE_TESTING_TAG)
.assertIsNotDisplayed()
}
})
}
private fun pauseForBetterTestPerformance() {

View File

@ -40,6 +40,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import org.kiwix.kiwixmobile.core.R
@ -56,9 +57,12 @@ import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.ArticleCount
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.SelectionMode
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
const val BOOK_ITEM_CHECKBOX_TESTING_TAG = "bookItemCheckboxTestingTag"
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun BookItem(
index: Int,
bookOnDisk: BookOnDisk,
onClick: ((BookOnDisk) -> Unit)? = null,
onLongClick: ((BookOnDisk) -> Unit)? = null,
@ -87,7 +91,7 @@ fun BookItem(
elevation = CardDefaults.elevatedCardElevation(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer)
) {
BookContent(bookOnDisk, selectionMode, onMultiSelect, onClick)
BookContent(bookOnDisk, selectionMode, onMultiSelect, onClick, index)
}
}
}
@ -98,6 +102,7 @@ private fun BookContent(
selectionMode: SelectionMode,
onMultiSelect: ((BookOnDisk) -> Unit)?,
onClick: ((BookOnDisk) -> Unit)?,
index: Int,
) {
Row(
modifier = Modifier
@ -106,7 +111,7 @@ private fun BookContent(
verticalAlignment = Alignment.CenterVertically
) {
if (selectionMode == SelectionMode.MULTI) {
BookCheckbox(bookOnDisk, selectionMode, onMultiSelect, onClick)
BookCheckbox(bookOnDisk, selectionMode, onMultiSelect, onClick, index)
}
BookIcon(bookOnDisk.book.faviconToPainter())
BookDetails(Modifier.weight(1f), bookOnDisk)
@ -118,7 +123,8 @@ private fun BookCheckbox(
bookOnDisk: BookOnDisk,
selectionMode: SelectionMode,
onMultiSelect: ((BookOnDisk) -> Unit)?,
onClick: ((BookOnDisk) -> Unit)?
onClick: ((BookOnDisk) -> Unit)?,
index: Int
) {
Checkbox(
checked = bookOnDisk.isSelected,
@ -127,7 +133,8 @@ private fun BookCheckbox(
SelectionMode.MULTI -> onMultiSelect?.invoke(bookOnDisk)
SelectionMode.NORMAL -> onClick?.invoke(bookOnDisk)
}
}
},
modifier = Modifier.testTag("$BOOK_ITEM_CHECKBOX_TESTING_TAG$index")
)
}

View File

@ -43,6 +43,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.viewinterop.AndroidView
@ -65,6 +66,9 @@ import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDis
import org.kiwix.kiwixmobile.ui.BookItem
import org.kiwix.kiwixmobile.ui.ZimFilesLanguageHeader
const val START_SERVER_BUTTON_TESTING_TAG = "startServerButtonTestingTag"
const val QR_IMAGE_TESTING_TAG = "qrImageTestingTag"
@Suppress("ComposableLambdaParameterNaming", "LongParameterList")
@Composable
fun ZimHostScreen(
@ -104,7 +108,10 @@ fun ZimHostScreen(
KiwixButton(
startServerButtonItem.first,
startServerButtonItem.third,
modifier = Modifier.fillMaxWidth().padding(FOUR_DP),
modifier = Modifier
.fillMaxWidth()
.padding(FOUR_DP)
.testTag(START_SERVER_BUTTON_TESTING_TAG),
buttonBackgroundColor = startServerButtonItem.second
)
}
@ -157,7 +164,8 @@ private fun QRImage(qrImageItem: Pair<Boolean, IconItem>) {
modifier = Modifier
.fillMaxWidth()
.heightIn(min = MINIMUM_HEIGHT_OF_QR_CODE, max = MAXIMUM_HEIGHT_OF_QR_CODE)
.padding(horizontal = SIXTEEN_DP),
.padding(horizontal = SIXTEEN_DP)
.testTag(QR_IMAGE_TESTING_TAG),
contentScale = ContentScale.Fit
)
}
@ -181,7 +189,7 @@ private fun BookItemList(
item {
QRImage(qrImageItem)
}
itemsIndexed(booksList) { _, bookItem ->
itemsIndexed(booksList) { index, bookItem ->
when (bookItem) {
is BooksOnDiskListItem.LanguageItem -> {
ZimFilesLanguageHeader(bookItem)
@ -189,6 +197,7 @@ private fun BookItemList(
is BookOnDisk -> {
BookItem(
index = index,
bookOnDisk = bookItem,
selectionMode = selectionMode,
onClick = onClick,