diff --git a/.gemini/styleguide.md b/.gemini/styleguide.md index a8dbf4c01..6b70feefa 100644 --- a/.gemini/styleguide.md +++ b/.gemini/styleguide.md @@ -43,6 +43,9 @@ When reviewing a pull request, focus on the following key areas: * **Descriptive Test Names:** Test function names must be clear, descriptive, and follow a consistent pattern. * **Use Truth Assertions:** Always prefer [Truth](https://truth.dev/) assertions (`assertThat(...)`) over JUnit assertions (`assertEquals`, `assertTrue`, etc.). Truth provides more readable assertion chains and more informative failure messages. Avoid functions from `org.junit.Assert` such as `assertEquals`, `assertTrue`, `assertFalse`, `assertNull`, and `assertNotNull`. * **Explicit Test Runners:** All test classes must be annotated with `@RunWith(...)` to explicitly declare which test runner should be used (e.g., `@RunWith(AndroidJUnit4::class)`, `@RunWith(RobolectricTestRunner::class)`, or `@RunWith(JUnit4::class)` for host tests with no Android dependencies). + * **Test Stability & Timeouts:** + * **Explicit Timeouts:** Avoid using `waitUntil` (or similar synchronization) without explicitly defining a `timeoutMillis`. Default timeouts are often too short for slower emulators (like API 28) or low-end devices, leading to flakiness. + * **Helper Functions for Waits:** If a wait condition is repeated (e.g., waiting for a specific UI element), extract it into a helper function (e.g., `waitForNodeWithTag`). This consolidates the logic and allows the timeout duration to be tuned centrally for that specific scenario. 6. **Documentation Sync** * **Check for necessary updates:** Analyze if the PR's changes (e.g., adding a new feature, changing build logic, deprecating functionality) require updates to `README.md` or other documentation files. diff --git a/app/src/androidTest/java/com/google/jetpackcamera/CachedImageCaptureDeviceTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/CachedImageCaptureDeviceTest.kt index 13cdc1145..20bd7076b 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/CachedImageCaptureDeviceTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/CachedImageCaptureDeviceTest.kt @@ -51,6 +51,7 @@ import com.google.jetpackcamera.utils.getTestUri import com.google.jetpackcamera.utils.runMainActivityScenarioTest import com.google.jetpackcamera.utils.runMainActivityScenarioTestForResult import com.google.jetpackcamera.utils.waitForCaptureButton +import com.google.jetpackcamera.utils.waitForNodeWithTag import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -79,12 +80,11 @@ class CachedImageCaptureDeviceTest { .performClick() // navigate to postcapture screen - composeTestRule.waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(VIEWER_POST_CAPTURE_IMAGE).isDisplayed() - } - composeTestRule.waitUntil { - composeTestRule.onNodeWithTag(BUTTON_POST_CAPTURE_EXIT).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + VIEWER_POST_CAPTURE_IMAGE, + timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS + ) + composeTestRule.waitForNodeWithTag(BUTTON_POST_CAPTURE_EXIT) composeTestRule.onNodeWithTag(BUTTON_POST_CAPTURE_EXIT).performClick() composeTestRule.waitForCaptureButton() @@ -135,9 +135,10 @@ class CachedImageCaptureDeviceTest { .assertExists() .performClick() - composeTestRule.waitUntil(timeoutMillis = IMAGE_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(IMAGE_CAPTURE_FAILURE_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + IMAGE_CAPTURE_FAILURE_TAG, + timeoutMillis = IMAGE_CAPTURE_TIMEOUT_MILLIS + ) uiDevice.pressBack() } Truth.assertThat(result.resultCode).isEqualTo(Activity.RESULT_CANCELED) diff --git a/app/src/androidTest/java/com/google/jetpackcamera/CachedVideoRecordingDeviceTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/CachedVideoRecordingDeviceTest.kt index fe00e076a..4728de0f6 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/CachedVideoRecordingDeviceTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/CachedVideoRecordingDeviceTest.kt @@ -46,6 +46,7 @@ import com.google.jetpackcamera.utils.longClickForVideoRecordingCheckingElapsedT import com.google.jetpackcamera.utils.runMainActivityScenarioTest import com.google.jetpackcamera.utils.runScenarioTestForResult import com.google.jetpackcamera.utils.waitForCaptureButton +import com.google.jetpackcamera.utils.waitForNodeWithTag import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -70,14 +71,11 @@ class CachedVideoRecordingDeviceTest { composeTestRule.longClickForVideoRecordingCheckingElapsedTime() // navigate to postcapture screen - composeTestRule.waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(VIEWER_POST_CAPTURE_VIDEO).isDisplayed() - } - composeTestRule.waitUntil { - composeTestRule.onNodeWithTag( - BUTTON_POST_CAPTURE_EXIT - ).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + VIEWER_POST_CAPTURE_VIDEO, + VIDEO_CAPTURE_TIMEOUT_MILLIS + ) + composeTestRule.waitForNodeWithTag(BUTTON_POST_CAPTURE_EXIT) composeTestRule.onNodeWithTag(BUTTON_POST_CAPTURE_EXIT).performClick() composeTestRule.waitForCaptureButton() @@ -116,9 +114,10 @@ class CachedVideoRecordingDeviceTest { composeTestRule.onNodeWithTag(CAPTURE_BUTTON).isDisplayed() } composeTestRule.longClickForVideoRecording() - composeTestRule.waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(VIDEO_CAPTURE_FAILURE_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + VIDEO_CAPTURE_FAILURE_TAG, + VIDEO_CAPTURE_TIMEOUT_MILLIS + ) uiDevice.pressBack() } Truth.assertThat(result.resultCode).isEqualTo(Activity.RESULT_CANCELED) diff --git a/app/src/androidTest/java/com/google/jetpackcamera/CaptureModeSettingsTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/CaptureModeSettingsTest.kt index 3ece418b0..e8599e37a 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/CaptureModeSettingsTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/CaptureModeSettingsTest.kt @@ -20,8 +20,6 @@ import android.provider.MediaStore import androidx.compose.ui.geometry.Offset import androidx.compose.ui.test.assertIsEnabled import androidx.compose.ui.test.assertIsNotEnabled -import androidx.compose.ui.test.isDisplayed -import androidx.compose.ui.test.isNotDisplayed import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.compose.ui.test.junit4.createEmptyComposeRule import androidx.compose.ui.test.onNodeWithTag @@ -63,7 +61,9 @@ import com.google.jetpackcamera.utils.unFocusQuickSetting import com.google.jetpackcamera.utils.visitQuickSettings import com.google.jetpackcamera.utils.wait import com.google.jetpackcamera.utils.waitForCaptureButton +import com.google.jetpackcamera.utils.waitForCaptureModeToggleState import com.google.jetpackcamera.utils.waitForNodeWithTag +import com.google.jetpackcamera.utils.waitForNodeWithTagToDisappear import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -108,13 +108,20 @@ internal class CaptureModeSettingsTest { setCaptureMode(captureMode) } - waitUntil(DEFAULT_TIMEOUT_MILLIS) { - onNodeWithTag(CAPTURE_MODE_TOGGLE_BUTTON).isDisplayed() - } + waitForNodeWithTag(CAPTURE_MODE_TOGGLE_BUTTON, DEFAULT_TIMEOUT_MILLIS) onNodeWithTag(CAPTURE_MODE_TOGGLE_BUTTON).assertExists() } + private fun flip(mode: CaptureMode): CaptureMode { + require(mode == CaptureMode.IMAGE_ONLY || mode == CaptureMode.VIDEO_ONLY) + return if (mode == CaptureMode.IMAGE_ONLY) { + CaptureMode.VIDEO_ONLY + } else { + CaptureMode.IMAGE_ONLY + } + } + @Test fun can_set_capture_mode_in_quick_settings() { runMainActivityScenarioTest { @@ -393,18 +400,15 @@ internal class CaptureModeSettingsTest { composeTestRule.initializeCaptureSwitch() val initialCaptureMode = composeTestRule.getCaptureModeToggleState() + val targetCaptureMode = flip(initialCaptureMode) // should be different from initial capture mode composeTestRule.onNodeWithTag(CAPTURE_MODE_TOGGLE_BUTTON).performClick() - composeTestRule.waitUntil { - composeTestRule.getCaptureModeToggleState() != initialCaptureMode - } + composeTestRule.waitForCaptureModeToggleState(targetCaptureMode) // should now be she same as the initial capture mode. composeTestRule.onNodeWithTag(CAPTURE_MODE_TOGGLE_BUTTON).performClick() - composeTestRule.waitUntil { - composeTestRule.getCaptureModeToggleState() == initialCaptureMode - } + composeTestRule.waitForCaptureModeToggleState(initialCaptureMode) } @Test @@ -413,6 +417,7 @@ internal class CaptureModeSettingsTest { composeTestRule.waitForCaptureButton() composeTestRule.initializeCaptureSwitch() val initialCaptureMode = composeTestRule.getCaptureModeToggleState() + val targetCaptureMode = flip(initialCaptureMode) val captureToggleNode = composeTestRule.onNodeWithTag(CAPTURE_MODE_TOGGLE_BUTTON) val toggleNodeWidth = captureToggleNode.fetchSemanticsNode().size.width.toFloat() val offsetToSwitch = when (initialCaptureMode) { @@ -442,9 +447,7 @@ internal class CaptureModeSettingsTest { captureToggleNode.performTouchInput { up() } - composeTestRule.waitUntil { - initialCaptureMode != composeTestRule.getCaptureModeToggleState() - } + composeTestRule.waitForCaptureModeToggleState(targetCaptureMode) } @Test @@ -459,17 +462,13 @@ internal class CaptureModeSettingsTest { // start recording composeTestRule.tapStartLockedVideoRecording() // check that recording - composeTestRule.waitUntil { - composeTestRule.onNodeWithTag(CAPTURE_MODE_TOGGLE_BUTTON).isNotDisplayed() - } + composeTestRule.waitForNodeWithTagToDisappear(CAPTURE_MODE_TOGGLE_BUTTON) // stop recording composeTestRule.onNodeWithTag(CAPTURE_BUTTON).assertExists().performClick() - composeTestRule.waitUntil { - composeTestRule.onNodeWithTag(CAPTURE_MODE_TOGGLE_BUTTON).isDisplayed() && - composeTestRule.getCaptureModeToggleState() == CaptureMode.VIDEO_ONLY - } + composeTestRule.waitForNodeWithTag(CAPTURE_MODE_TOGGLE_BUTTON) + composeTestRule.waitForCaptureModeToggleState(CaptureMode.VIDEO_ONLY) deleteFilesInDirAfterTimestamp(MOVIES_DIR_PATH, instrumentation, timeStamp) } diff --git a/app/src/androidTest/java/com/google/jetpackcamera/ConcurrentCameraTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/ConcurrentCameraTest.kt index 2658934f8..53d2292dc 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/ConcurrentCameraTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/ConcurrentCameraTest.kt @@ -20,7 +20,6 @@ import androidx.compose.ui.semantics.SemanticsProperties import androidx.compose.ui.test.SemanticsNodeInteraction import androidx.compose.ui.test.assert import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.isDisplayed import androidx.compose.ui.test.isEnabled import androidx.compose.ui.test.isNotEnabled import androidx.compose.ui.test.junit4.createEmptyComposeRule @@ -33,7 +32,6 @@ import com.google.common.truth.Truth.assertThat import com.google.jetpackcamera.MainActivity import com.google.jetpackcamera.model.ConcurrentCameraMode import com.google.jetpackcamera.ui.components.capture.BTN_QUICK_SETTINGS_FOCUS_CAPTURE_MODE -import com.google.jetpackcamera.ui.components.capture.CAPTURE_BUTTON import com.google.jetpackcamera.ui.components.capture.FLIP_CAMERA_BUTTON import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_CONCURRENT_CAMERA_MODE_BUTTON import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_DROP_DOWN @@ -44,7 +42,6 @@ import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_RATIO_BUTTO import com.google.jetpackcamera.ui.components.capture.QUICK_SETTINGS_STREAM_CONFIG_BUTTON import com.google.jetpackcamera.ui.components.capture.R import com.google.jetpackcamera.ui.components.capture.VIDEO_CAPTURE_SUCCESS_TAG -import com.google.jetpackcamera.utils.APP_START_TIMEOUT_MILLIS import com.google.jetpackcamera.utils.TEST_REQUIRED_PERMISSIONS import com.google.jetpackcamera.utils.VIDEO_CAPTURE_TIMEOUT_MILLIS import com.google.jetpackcamera.utils.assume @@ -53,6 +50,8 @@ import com.google.jetpackcamera.utils.longClickForVideoRecordingCheckingElapsedT import com.google.jetpackcamera.utils.runMainActivityMediaStoreAutoDeleteScenarioTest import com.google.jetpackcamera.utils.runMainActivityScenarioTest import com.google.jetpackcamera.utils.stateDescriptionMatches +import com.google.jetpackcamera.utils.waitForCaptureButton +import com.google.jetpackcamera.utils.waitForNodeWithTag import kotlinx.coroutines.runBlocking import org.junit.Rule import org.junit.Test @@ -280,9 +279,7 @@ class ConcurrentCameraTest { longClickForVideoRecordingCheckingElapsedTime() - waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(VIDEO_CAPTURE_SUCCESS_TAG).isDisplayed() - } + waitForNodeWithTag(VIDEO_CAPTURE_SUCCESS_TAG, VIDEO_CAPTURE_TIMEOUT_MILLIS) } } @@ -296,9 +293,7 @@ class ConcurrentCameraTest { ) { val wrappedBlock: ActivityScenario.() -> Unit = { // Wait for the capture button to be displayed - composeTestRule.waitUntil(timeoutMillis = APP_START_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(CAPTURE_BUTTON).isDisplayed() - } + composeTestRule.waitForCaptureButton() // /////////////////////////////////////////////////// // Check that the device supports concurrent camera // diff --git a/app/src/androidTest/java/com/google/jetpackcamera/DebugHideComponentsTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/DebugHideComponentsTest.kt index 8b9bc1688..d908b2f56 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/DebugHideComponentsTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/DebugHideComponentsTest.kt @@ -15,8 +15,6 @@ */ package com.google.jetpackcamera -import androidx.compose.ui.test.isDisplayed -import androidx.compose.ui.test.isNotDisplayed import androidx.compose.ui.test.junit4.createEmptyComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick @@ -39,6 +37,8 @@ import com.google.jetpackcamera.utils.TEST_REQUIRED_PERMISSIONS import com.google.jetpackcamera.utils.debugExtra import com.google.jetpackcamera.utils.runMainActivityScenarioTest import com.google.jetpackcamera.utils.waitForCaptureButton +import com.google.jetpackcamera.utils.waitForNodeWithTag +import com.google.jetpackcamera.utils.waitForNodeWithTagToDisappear import org.junit.Before import org.junit.Rule import org.junit.Test @@ -73,9 +73,7 @@ class DebugHideComponentsTest { composeTestRule.onNodeWithTag(BTN_DEBUG_HIDE_COMPONENTS_TAG).performClick() - composeTestRule.waitUntil { - composeTestRule.onNodeWithTag(CAPTURE_BUTTON).isNotDisplayed() - } + composeTestRule.waitForNodeWithTagToDisappear(CAPTURE_BUTTON) composeTestRule.onNodeWithTag(ZOOM_BUTTON_ROW_TAG).assertDoesNotExist() composeTestRule.onNodeWithTag(FLIP_CAMERA_BUTTON).assertDoesNotExist() composeTestRule.onNodeWithTag(AMPLITUDE_NONE_TAG).assertDoesNotExist() @@ -87,9 +85,7 @@ class DebugHideComponentsTest { composeTestRule.onNodeWithTag(BTN_DEBUG_HIDE_COMPONENTS_TAG).performClick() - composeTestRule.waitUntil { - composeTestRule.onNodeWithTag(CAPTURE_BUTTON).isDisplayed() - } + composeTestRule.waitForNodeWithTag(CAPTURE_BUTTON) composeTestRule.onNodeWithTag(FLIP_CAMERA_BUTTON).assertExists() composeTestRule.onNodeWithTag(DEBUG_OVERLAY_BUTTON).assertExists() composeTestRule.onNodeWithTag(LOGICAL_CAMERA_ID_TAG).assertExists() diff --git a/app/src/androidTest/java/com/google/jetpackcamera/FlashDeviceTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/FlashDeviceTest.kt index e418a23f3..90758b0a8 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/FlashDeviceTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/FlashDeviceTest.kt @@ -46,6 +46,7 @@ import com.google.jetpackcamera.utils.longClickForVideoRecordingCheckingElapsedT import com.google.jetpackcamera.utils.runMainActivityMediaStoreAutoDeleteScenarioTest import com.google.jetpackcamera.utils.runMainActivityScenarioTest import com.google.jetpackcamera.utils.setFlashMode +import com.google.jetpackcamera.utils.waitForNodeWithTag import org.junit.Before import org.junit.Rule import org.junit.Test @@ -141,9 +142,7 @@ internal class FlashDeviceTest { .assertExists() .performClick() - composeTestRule.waitUntil(timeoutMillis = IMAGE_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG, IMAGE_CAPTURE_TIMEOUT_MILLIS) } @Test @@ -172,13 +171,15 @@ internal class FlashDeviceTest { .assertExists() .performClick() - composeTestRule.waitUntil(timeoutMillis = SCREEN_FLASH_OVERLAY_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(SCREEN_FLASH_OVERLAY).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + SCREEN_FLASH_OVERLAY, + SCREEN_FLASH_OVERLAY_TIMEOUT_MILLIS + ) - composeTestRule.waitUntil(timeoutMillis = IMAGE_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + IMAGE_CAPTURE_SUCCESS_TAG, + IMAGE_CAPTURE_TIMEOUT_MILLIS + ) } @Test @@ -209,8 +210,9 @@ internal class FlashDeviceTest { composeTestRule.setFlashMode(FlashMode.ON) composeTestRule.longClickForVideoRecordingCheckingElapsedTime() - composeTestRule.waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(VIDEO_CAPTURE_SUCCESS_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + VIDEO_CAPTURE_SUCCESS_TAG, + VIDEO_CAPTURE_TIMEOUT_MILLIS + ) } } diff --git a/app/src/androidTest/java/com/google/jetpackcamera/FocusMeteringTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/FocusMeteringTest.kt index f55e341ca..f72ebbd16 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/FocusMeteringTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/FocusMeteringTest.kt @@ -18,8 +18,6 @@ package com.google.jetpackcamera import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.ViewConfiguration import androidx.compose.ui.test.click -import androidx.compose.ui.test.isDisplayed -import androidx.compose.ui.test.isNotDisplayed import androidx.compose.ui.test.junit4.createEmptyComposeRule import androidx.compose.ui.test.onAllNodesWithTag import androidx.compose.ui.test.onNodeWithTag @@ -37,6 +35,8 @@ import com.google.jetpackcamera.utils.debugExtra import com.google.jetpackcamera.utils.runMainActivityScenarioTest import com.google.jetpackcamera.utils.wait import com.google.jetpackcamera.utils.waitForCaptureButton +import com.google.jetpackcamera.utils.waitForNodeWithTag +import com.google.jetpackcamera.utils.waitForNodeWithTagToDisappear import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -58,9 +58,7 @@ class FocusMeteringTest { // Hide all components so we don't accidentally tap on them composeTestRule.onNodeWithTag(BTN_DEBUG_HIDE_COMPONENTS_TAG).performClick() - composeTestRule.waitUntil { - composeTestRule.onNodeWithTag(CAPTURE_BUTTON).isNotDisplayed() - } + composeTestRule.waitForNodeWithTagToDisappear(CAPTURE_BUTTON) // Define the four quadrants of the screen val quadrants = listOf( @@ -79,11 +77,10 @@ class FocusMeteringTest { performTouchInput { click(position = percentOffset(x, y)) } // Wait for the focus metering indicator to be visible - composeTestRule.waitUntil(FOCUS_METERING_INDICATOR_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag( - FOCUS_METERING_INDICATOR_TAG - ).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + FOCUS_METERING_INDICATOR_TAG, + FOCUS_METERING_INDICATOR_TIMEOUT_MILLIS + ) composeTestRule.waitUntil(FOCUS_METERING_INDICATOR_TIMEOUT_MILLIS) { composeTestRule.onAllNodesWithTag(FOCUS_METERING_INDICATOR_TAG).run { diff --git a/app/src/androidTest/java/com/google/jetpackcamera/ImageCaptureDeviceTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/ImageCaptureDeviceTest.kt index cf9b896d7..0139e3f8c 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/ImageCaptureDeviceTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/ImageCaptureDeviceTest.kt @@ -53,6 +53,7 @@ import com.google.jetpackcamera.utils.getTestUri import com.google.jetpackcamera.utils.longClickForVideoRecording import com.google.jetpackcamera.utils.runMainActivityMediaStoreAutoDeleteScenarioTest import com.google.jetpackcamera.utils.runMainActivityScenarioTestForResult +import com.google.jetpackcamera.utils.waitForNodeWithTag import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -84,9 +85,7 @@ internal class ImageCaptureDeviceTest { composeTestRule.onNodeWithTag(CAPTURE_BUTTON) .assertExists() .performClick() - composeTestRule.waitUntil(timeoutMillis = IMAGE_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG, IMAGE_CAPTURE_TIMEOUT_MILLIS) } @Test @@ -101,9 +100,7 @@ internal class ImageCaptureDeviceTest { uiDevice.pressKeyCode(KeyEvent.KEYCODE_VOLUME_UP) - composeTestRule.waitUntil(timeoutMillis = IMAGE_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG, IMAGE_CAPTURE_TIMEOUT_MILLIS) } @Test @@ -117,9 +114,7 @@ internal class ImageCaptureDeviceTest { } uiDevice.pressKeyCode(KeyEvent.KEYCODE_VOLUME_DOWN) - composeTestRule.waitUntil(timeoutMillis = IMAGE_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG, IMAGE_CAPTURE_TIMEOUT_MILLIS) } @Test @@ -164,9 +159,10 @@ internal class ImageCaptureDeviceTest { .assertExists() .performClick() - composeTestRule.waitUntil(timeoutMillis = IMAGE_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(IMAGE_CAPTURE_FAILURE_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + IMAGE_CAPTURE_FAILURE_TAG, + IMAGE_CAPTURE_TIMEOUT_MILLIS + ) uiDevice.pressBack() } Truth.assertThat(result.resultCode).isEqualTo(Activity.RESULT_CANCELED) diff --git a/app/src/androidTest/java/com/google/jetpackcamera/NavigationTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/NavigationTest.kt index 4ec476485..db57373c0 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/NavigationTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/NavigationTest.kt @@ -17,7 +17,6 @@ package com.google.jetpackcamera import androidx.compose.ui.test.isDisplayed import androidx.compose.ui.test.isEnabled -import androidx.compose.ui.test.isNotDisplayed import androidx.compose.ui.test.junit4.createEmptyComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick @@ -41,6 +40,8 @@ import com.google.jetpackcamera.utils.assume import com.google.jetpackcamera.utils.onNodeWithText import com.google.jetpackcamera.utils.runMainActivityScenarioTest import com.google.jetpackcamera.utils.searchForQuickSetting +import com.google.jetpackcamera.utils.waitForNodeWithTag +import com.google.jetpackcamera.utils.waitForNodeWithTagToDisappear import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -141,9 +142,7 @@ class NavigationTest { .performClick() // Wait for the quick settings to be displayed - composeTestRule.waitUntil { - composeTestRule.onNodeWithTag(QUICK_SETTINGS_RATIO_BUTTON).isDisplayed() - } + composeTestRule.waitForNodeWithTag(QUICK_SETTINGS_RATIO_BUTTON) // Press the device's back button uiDevice.pressBack() @@ -172,16 +171,15 @@ class NavigationTest { .performClick() // Wait for the 1:1 ratio button to be displayed - composeTestRule.waitUntil { - composeTestRule.onNodeWithTag(QUICK_SETTINGS_RATIO_1_1_BUTTON).isDisplayed() - } + composeTestRule.waitForNodeWithTag(QUICK_SETTINGS_RATIO_1_1_BUTTON) // Press the device's back button uiDevice.pressBack() // Assert bottom sheet closed - composeTestRule.waitUntil(DEFAULT_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(QUICK_SETTINGS_BOTTOM_SHEET).isNotDisplayed() - } + composeTestRule.waitForNodeWithTagToDisappear( + QUICK_SETTINGS_BOTTOM_SHEET, + DEFAULT_TIMEOUT_MILLIS + ) } } diff --git a/app/src/androidTest/java/com/google/jetpackcamera/PermissionsTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/PermissionsTest.kt index a79828813..e829e1d8e 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/PermissionsTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/PermissionsTest.kt @@ -18,7 +18,6 @@ package com.google.jetpackcamera import android.Manifest.permission.CAMERA import android.Manifest.permission.RECORD_AUDIO import androidx.compose.ui.test.isDisplayed -import androidx.compose.ui.test.isNotDisplayed import androidx.compose.ui.test.junit4.createEmptyComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick @@ -47,6 +46,9 @@ import com.google.jetpackcamera.utils.grantPermissionDialog import com.google.jetpackcamera.utils.onNodeWithText import com.google.jetpackcamera.utils.runMainActivityScenarioTest import com.google.jetpackcamera.utils.waitForCaptureButton +import com.google.jetpackcamera.utils.waitForNodeWithTag +import com.google.jetpackcamera.utils.waitForNodeWithTagToDisappear +import com.google.jetpackcamera.utils.waitForNodeWithText import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -94,9 +96,10 @@ class PermissionsTest { @Test fun cameraPermission_granted_closesPage() = runMainActivityScenarioTest { // Wait for the camera permission screen to be displayed - composeTestRule.waitUntil(timeoutMillis = APP_START_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(CAMERA_PERMISSION_BUTTON).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + CAMERA_PERMISSION_BUTTON, + timeoutMillis = APP_START_TIMEOUT_MILLIS + ) // Click button to request permission composeTestRule.onNodeWithTag(REQUEST_PERMISSION_BUTTON) @@ -117,9 +120,10 @@ class PermissionsTest { uiDevice.waitForIdle() runMainActivityScenarioTest { // Wait for the camera permission screen to be displayed - composeTestRule.waitUntil(timeoutMillis = APP_START_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(CAMERA_PERMISSION_BUTTON).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + CAMERA_PERMISSION_BUTTON, + timeoutMillis = APP_START_TIMEOUT_MILLIS + ) // Click button to request permission composeTestRule.onNodeWithTag(REQUEST_PERMISSION_BUTTON) @@ -139,9 +143,10 @@ class PermissionsTest { // required permissions should persist on screen // Wait for the permission screen to be displayed runMainActivityScenarioTest { - composeTestRule.waitUntil(timeoutMillis = APP_START_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(CAMERA_PERMISSION_BUTTON).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + CAMERA_PERMISSION_BUTTON, + timeoutMillis = APP_START_TIMEOUT_MILLIS + ) // Click button to request permission composeTestRule.onNodeWithTag(REQUEST_PERMISSION_BUTTON) @@ -157,13 +162,11 @@ class PermissionsTest { composeTestRule.onNodeWithTag(CAMERA_PERMISSION_BUTTON).isDisplayed() // text changed after permission denied - composeTestRule.waitUntil(timeoutMillis = APP_START_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithText( - com.google.jetpackcamera.permissions.R.string - .camera_permission_declined_rationale - ) - .isDisplayed() - } + composeTestRule.waitForNodeWithText( + com.google.jetpackcamera.permissions.R.string + .camera_permission_declined_rationale, + timeoutMillis = APP_START_TIMEOUT_MILLIS + ) // request permissions button should now say to navigate to settings composeTestRule.onNodeWithText( com.google.jetpackcamera.permissions @@ -176,9 +179,10 @@ class PermissionsTest { fun recordAudioPermission_granted_closesPage() { // optional permissions should close the screen after declining runMainActivityScenarioTest { - composeTestRule.waitUntil(timeoutMillis = APP_START_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(RECORD_AUDIO_PERMISSION_BUTTON).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + RECORD_AUDIO_PERMISSION_BUTTON, + timeoutMillis = APP_START_TIMEOUT_MILLIS + ) // Click button to request permission composeTestRule.onNodeWithTag(REQUEST_PERMISSION_BUTTON) @@ -190,9 +194,10 @@ class PermissionsTest { uiDevice.waitForIdle() // Assert we're on a different page - composeTestRule.waitUntil(timeoutMillis = APP_START_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(RECORD_AUDIO_PERMISSION_BUTTON).isNotDisplayed() - } + composeTestRule.waitForNodeWithTagToDisappear( + RECORD_AUDIO_PERMISSION_BUTTON, + timeoutMillis = APP_START_TIMEOUT_MILLIS + ) } } @@ -200,9 +205,10 @@ class PermissionsTest { fun recordAudioPermission_denied_closesPage() { // optional permissions should close the screen after declining runMainActivityScenarioTest { - composeTestRule.waitUntil(timeoutMillis = APP_START_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(RECORD_AUDIO_PERMISSION_BUTTON).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + RECORD_AUDIO_PERMISSION_BUTTON, + timeoutMillis = APP_START_TIMEOUT_MILLIS + ) // Click button to request permission composeTestRule.onNodeWithTag(REQUEST_PERMISSION_BUTTON) @@ -214,9 +220,10 @@ class PermissionsTest { uiDevice.waitForIdle() // Assert we're on a different page - composeTestRule.waitUntil(timeoutMillis = APP_START_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(RECORD_AUDIO_PERMISSION_BUTTON).isNotDisplayed() - } + composeTestRule.waitForNodeWithTagToDisappear( + RECORD_AUDIO_PERMISSION_BUTTON, + timeoutMillis = APP_START_TIMEOUT_MILLIS + ) } } @@ -229,11 +236,10 @@ class PermissionsTest { val timeStamp = System.currentTimeMillis() runMainActivityScenarioTest { // Wait for the camera permission screen to be displayed - composeTestRule.waitUntil(timeoutMillis = APP_START_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag( - WRITE_EXTERNAL_STORAGE_PERMISSION_BUTTON - ).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + WRITE_EXTERNAL_STORAGE_PERMISSION_BUTTON, + timeoutMillis = APP_START_TIMEOUT_MILLIS + ) // Click button to request permission composeTestRule.onNodeWithTag(REQUEST_PERMISSION_BUTTON) @@ -244,11 +250,10 @@ class PermissionsTest { uiDevice.grantPermissionDialog() // permission screen should close - composeTestRule.waitUntil(timeoutMillis = 5_000) { - composeTestRule - .onNodeWithTag(WRITE_EXTERNAL_STORAGE_PERMISSION_BUTTON) - .isNotDisplayed() - } + composeTestRule.waitForNodeWithTagToDisappear( + WRITE_EXTERNAL_STORAGE_PERMISSION_BUTTON, + timeoutMillis = 5_000 + ) composeTestRule.waitForCaptureButton() @@ -258,9 +263,10 @@ class PermissionsTest { composeTestRule.onNodeWithTag(CAPTURE_BUTTON) .assertExists() .performClick() - composeTestRule.waitUntil(timeoutMillis = IMAGE_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + IMAGE_CAPTURE_SUCCESS_TAG, + timeoutMillis = IMAGE_CAPTURE_TIMEOUT_MILLIS + ) } deleteFilesInDirAfterTimestamp(PICTURES_DIR_PATH, instrumentation, timeStamp) @@ -272,11 +278,10 @@ class PermissionsTest { uiDevice.waitForIdle() runMainActivityScenarioTest { // Wait for the camera permission screen to be displayed - composeTestRule.waitUntil(timeoutMillis = APP_START_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag( - WRITE_EXTERNAL_STORAGE_PERMISSION_BUTTON - ).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + WRITE_EXTERNAL_STORAGE_PERMISSION_BUTTON, + timeoutMillis = APP_START_TIMEOUT_MILLIS + ) // Click button to request permission composeTestRule.onNodeWithTag(REQUEST_PERMISSION_BUTTON) @@ -287,20 +292,19 @@ class PermissionsTest { uiDevice.denyPermissionDialog() // storage permission is optional and the screen should close - composeTestRule.waitUntil { - composeTestRule - .onNodeWithTag(WRITE_EXTERNAL_STORAGE_PERMISSION_BUTTON) - .isNotDisplayed() - } + composeTestRule.waitForNodeWithTagToDisappear( + WRITE_EXTERNAL_STORAGE_PERMISSION_BUTTON + ) composeTestRule.waitForCaptureButton() // check for image capture failure composeTestRule.onNodeWithTag(CAPTURE_BUTTON).assertExists().performClick() - composeTestRule.waitUntil(timeoutMillis = IMAGE_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(IMAGE_CAPTURE_FAILURE_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + IMAGE_CAPTURE_FAILURE_TAG, + timeoutMillis = IMAGE_CAPTURE_TIMEOUT_MILLIS + ) // imageWell shouldn't appear composeTestRule.ensureTagNotAppears(IMAGE_WELL_TAG) } diff --git a/app/src/androidTest/java/com/google/jetpackcamera/PostCaptureTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/PostCaptureTest.kt index 0ea61cbf3..a80529f3a 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/PostCaptureTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/PostCaptureTest.kt @@ -16,7 +16,6 @@ package com.google.jetpackcamera import android.provider.MediaStore -import androidx.compose.ui.test.isDisplayed import androidx.compose.ui.test.junit4.createEmptyComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick @@ -50,6 +49,7 @@ import com.google.jetpackcamera.utils.mediaStoreEntryExistsAfterTimestamp import com.google.jetpackcamera.utils.runMainActivityScenarioTest import com.google.jetpackcamera.utils.wait import com.google.jetpackcamera.utils.waitForCaptureButton +import com.google.jetpackcamera.utils.waitForNodeWithTag import org.junit.After import org.junit.Before import org.junit.Rule @@ -96,20 +96,14 @@ class PostCaptureTest { private fun enterImageWellAndDelete(recentCaptureViewerTag: String) { // enter postcapture via imagewell - composeTestRule.waitUntil(IMAGE_WELL_LOAD_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(IMAGE_WELL_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag(IMAGE_WELL_TAG, IMAGE_WELL_LOAD_TIMEOUT_MILLIS) composeTestRule.onNodeWithTag(IMAGE_WELL_TAG).assertExists().performClick() // most recent capture tag - composeTestRule.waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(recentCaptureViewerTag).isDisplayed() - } + composeTestRule.waitForNodeWithTag(recentCaptureViewerTag, VIDEO_CAPTURE_TIMEOUT_MILLIS) // delete most recent capture - composeTestRule.waitUntil { - composeTestRule.onNodeWithTag(BUTTON_POST_CAPTURE_DELETE).isDisplayed() - } + composeTestRule.waitForNodeWithTag(BUTTON_POST_CAPTURE_DELETE) composeTestRule.onNodeWithTag(BUTTON_POST_CAPTURE_DELETE).assertExists().performClick() // wait for capture button after automatically exiting post capture @@ -118,20 +112,14 @@ class PostCaptureTest { private fun enterImageWellAndSave(recentCaptureViewerTag: String) { // enter postcapture via imagewell - composeTestRule.waitUntil(IMAGE_WELL_LOAD_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(IMAGE_WELL_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag(IMAGE_WELL_TAG, IMAGE_WELL_LOAD_TIMEOUT_MILLIS) composeTestRule.onNodeWithTag(IMAGE_WELL_TAG).assertExists().performClick() // most recent capture tag - composeTestRule.waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(recentCaptureViewerTag).isDisplayed() - } + composeTestRule.waitForNodeWithTag(recentCaptureViewerTag, VIDEO_CAPTURE_TIMEOUT_MILLIS) // delete most recent capture - composeTestRule.waitUntil { - composeTestRule.onNodeWithTag(BUTTON_POST_CAPTURE_DELETE).isDisplayed() - } + composeTestRule.waitForNodeWithTag(BUTTON_POST_CAPTURE_DELETE) composeTestRule.onNodeWithTag(BUTTON_POST_CAPTURE_SAVE).assertExists().performClick() } @@ -146,20 +134,19 @@ class PostCaptureTest { composeTestRule.onNodeWithTag(CAPTURE_BUTTON).assertExists().performClick() // navigate to postcapture screen - composeTestRule.waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(VIEWER_POST_CAPTURE_IMAGE).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + VIEWER_POST_CAPTURE_IMAGE, + VIDEO_CAPTURE_TIMEOUT_MILLIS + ) - composeTestRule.waitUntil { - composeTestRule.onNodeWithTag(BUTTON_POST_CAPTURE_SAVE).isDisplayed() - } + composeTestRule.waitForNodeWithTag(BUTTON_POST_CAPTURE_SAVE) composeTestRule.onNodeWithTag(BUTTON_POST_CAPTURE_SAVE).performClick() // Wait for image save success message - composeTestRule.waitUntil(timeoutMillis = SAVE_MEDIA_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(SNACKBAR_POST_CAPTURE_IMAGE_SAVE_SUCCESS) - .isDisplayed() - } + composeTestRule.waitForNodeWithTag( + SNACKBAR_POST_CAPTURE_IMAGE_SAVE_SUCCESS, + SAVE_MEDIA_TIMEOUT_MILLIS + ) assertThat(newImageMediaExists()).isTrue() } @@ -172,25 +159,22 @@ class PostCaptureTest { composeTestRule.longClickForVideoRecordingCheckingElapsedTime() // navigate to postcapture screen - composeTestRule.waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(VIEWER_POST_CAPTURE_VIDEO).isDisplayed() - } - composeTestRule.waitUntil { - composeTestRule.onNodeWithTag(BUTTON_POST_CAPTURE_EXIT).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + VIEWER_POST_CAPTURE_VIDEO, + VIDEO_CAPTURE_TIMEOUT_MILLIS + ) + composeTestRule.waitForNodeWithTag(BUTTON_POST_CAPTURE_EXIT) assertThat(newVideoMediaExists()).isFalse() // save video - composeTestRule.waitUntil { - composeTestRule.onNodeWithTag(BUTTON_POST_CAPTURE_SAVE).isDisplayed() - } + composeTestRule.waitForNodeWithTag(BUTTON_POST_CAPTURE_SAVE) composeTestRule.onNodeWithTag(BUTTON_POST_CAPTURE_SAVE).performClick() // Wait for video save success message - composeTestRule.waitUntil(timeoutMillis = SAVE_MEDIA_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(SNACKBAR_POST_CAPTURE_VIDEO_SAVE_SUCCESS) - .isDisplayed() - } + composeTestRule.waitForNodeWithTag( + SNACKBAR_POST_CAPTURE_VIDEO_SAVE_SUCCESS, + SAVE_MEDIA_TIMEOUT_MILLIS + ) assertThat(newVideoMediaExists()).isTrue() composeTestRule.onNodeWithTag(BUTTON_POST_CAPTURE_EXIT).performClick() @@ -203,9 +187,7 @@ class PostCaptureTest { composeTestRule.waitForCaptureButton() assertThat(newImageMediaExists()).isFalse() composeTestRule.onNodeWithTag(CAPTURE_BUTTON).assertExists().performClick() - composeTestRule.waitUntil(IMAGE_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG, IMAGE_CAPTURE_TIMEOUT_MILLIS) assertThat(newImageMediaExists()).isTrue() // enter postcapture via imagewell and delete recent capture enterImageWellAndDelete(VIEWER_POST_CAPTURE_IMAGE) @@ -222,9 +204,7 @@ class PostCaptureTest { assertThat(newVideoMediaExists()).isFalse() composeTestRule.longClickForVideoRecordingCheckingElapsedTime() - composeTestRule.waitUntil(VIDEO_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(VIDEO_CAPTURE_SUCCESS_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag(VIDEO_CAPTURE_SUCCESS_TAG, VIDEO_CAPTURE_TIMEOUT_MILLIS) assertThat(newVideoMediaExists()).isTrue() // enter postcapture via imagewell and delete recent capture enterImageWellAndDelete(VIEWER_POST_CAPTURE_VIDEO) @@ -239,17 +219,16 @@ class PostCaptureTest { composeTestRule.waitForCaptureButton() assertThat(newImageMediaExists()).isFalse() composeTestRule.onNodeWithTag(CAPTURE_BUTTON).assertExists().performClick() - composeTestRule.waitUntil(IMAGE_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG, IMAGE_CAPTURE_TIMEOUT_MILLIS) assertThat(newImageMediaExists()).isTrue() // enter postcapture via imagewell and save recent capture val newTimestamp = System.currentTimeMillis() enterImageWellAndSave(VIEWER_POST_CAPTURE_IMAGE) - composeTestRule.waitUntil(SAVE_MEDIA_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(SNACKBAR_POST_CAPTURE_IMAGE_SAVE_SUCCESS).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + SNACKBAR_POST_CAPTURE_IMAGE_SAVE_SUCCESS, + SAVE_MEDIA_TIMEOUT_MILLIS + ) composeTestRule.waitUntil(timeoutMillis = SAVE_MEDIA_TIMEOUT_MILLIS) { newImageMediaExists(newTimestamp) } @@ -262,16 +241,15 @@ class PostCaptureTest { assertThat(newVideoMediaExists()).isFalse() composeTestRule.longClickForVideoRecordingCheckingElapsedTime() - composeTestRule.waitUntil(VIDEO_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(VIDEO_CAPTURE_SUCCESS_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag(VIDEO_CAPTURE_SUCCESS_TAG, VIDEO_CAPTURE_TIMEOUT_MILLIS) assertThat(newVideoMediaExists()).isTrue() // enter postcapture via imagewell and save recent capture val newTimestamp = System.currentTimeMillis() enterImageWellAndSave(VIEWER_POST_CAPTURE_VIDEO) - composeTestRule.waitUntil(SAVE_MEDIA_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(SNACKBAR_POST_CAPTURE_VIDEO_SAVE_SUCCESS).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + SNACKBAR_POST_CAPTURE_VIDEO_SAVE_SUCCESS, + SAVE_MEDIA_TIMEOUT_MILLIS + ) composeTestRule.waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) { newVideoMediaExists(newTimestamp) } diff --git a/app/src/androidTest/java/com/google/jetpackcamera/SwitchCameraTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/SwitchCameraTest.kt index f66e4fda5..f56d5e80d 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/SwitchCameraTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/SwitchCameraTest.kt @@ -16,7 +16,6 @@ package com.google.jetpackcamera import androidx.compose.ui.test.doubleClick -import androidx.compose.ui.test.isDisplayed import androidx.compose.ui.test.isEnabled import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.compose.ui.test.junit4.createEmptyComposeRule @@ -37,6 +36,7 @@ import com.google.jetpackcamera.utils.TEST_REQUIRED_PERMISSIONS import com.google.jetpackcamera.utils.assume import com.google.jetpackcamera.utils.getCurrentLensFacing import com.google.jetpackcamera.utils.runMainActivityScenarioTest +import com.google.jetpackcamera.utils.waitForNodeWithTag import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -141,9 +141,7 @@ inline fun runFlipCameraTest( crossinline block: ActivityScenario.() -> Unit ) = runMainActivityScenarioTest { // Wait for the preview display to be visible - composeTestRule.waitUntil(APP_START_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(PREVIEW_DISPLAY).isDisplayed() - } + composeTestRule.waitForNodeWithTag(PREVIEW_DISPLAY, APP_START_TIMEOUT_MILLIS) // If flipping the camera is available, flip it. Otherwise skip test. composeTestRule.onNodeWithTag(FLIP_CAMERA_BUTTON).assume(isEnabled()) { diff --git a/app/src/androidTest/java/com/google/jetpackcamera/VideoRecordingDeviceTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/VideoRecordingDeviceTest.kt index a37e62623..bd5557934 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/VideoRecordingDeviceTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/VideoRecordingDeviceTest.kt @@ -46,6 +46,7 @@ import com.google.jetpackcamera.utils.pressAndDragToLockVideoRecording import com.google.jetpackcamera.utils.runMainActivityMediaStoreAutoDeleteScenarioTest import com.google.jetpackcamera.utils.runScenarioTestForResult import com.google.jetpackcamera.utils.tapStartLockedVideoRecording +import com.google.jetpackcamera.utils.waitForNodeWithTag import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -72,9 +73,7 @@ internal class VideoRecordingDeviceTest { composeTestRule.onNodeWithTag(CAPTURE_BUTTON).isDisplayed() } composeTestRule.longClickForVideoRecordingCheckingElapsedTime() - composeTestRule.waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(VIDEO_CAPTURE_SUCCESS_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag(VIDEO_CAPTURE_SUCCESS_TAG, VIDEO_CAPTURE_TIMEOUT_MILLIS) deleteFilesInDirAfterTimestamp(MOVIES_DIR_PATH, instrumentation, timeStamp) } @@ -95,9 +94,10 @@ internal class VideoRecordingDeviceTest { composeTestRule.onNodeWithTag(CAPTURE_BUTTON).assertExists().performClick() composeTestRule.onNodeWithTag(CAPTURE_BUTTON).assertExists().performClick() - composeTestRule.waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(VIDEO_CAPTURE_SUCCESS_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + VIDEO_CAPTURE_SUCCESS_TAG, + VIDEO_CAPTURE_TIMEOUT_MILLIS + ) deleteFilesInDirAfterTimestamp(MOVIES_DIR_PATH, instrumentation, timeStamp) } @@ -157,9 +157,10 @@ internal class VideoRecordingDeviceTest { composeTestRule.onNodeWithTag(CAPTURE_BUTTON).isDisplayed() } composeTestRule.longClickForVideoRecording() - composeTestRule.waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) { - composeTestRule.onNodeWithTag(VIDEO_CAPTURE_FAILURE_TAG).isDisplayed() - } + composeTestRule.waitForNodeWithTag( + VIDEO_CAPTURE_FAILURE_TAG, + VIDEO_CAPTURE_TIMEOUT_MILLIS + ) uiDevice.pressBack() } Truth.assertThat(result.resultCode).isEqualTo(Activity.RESULT_CANCELED) diff --git a/app/src/androidTest/java/com/google/jetpackcamera/utils/ComposeTestRuleExt.kt b/app/src/androidTest/java/com/google/jetpackcamera/utils/ComposeTestRuleExt.kt index b6f6501bf..4a9e56f72 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/utils/ComposeTestRuleExt.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/utils/ComposeTestRuleExt.kt @@ -154,6 +154,24 @@ fun ComposeTestRule.waitForNodeWithTag(tag: String, timeoutMillis: Long = DEFAUL waitUntil(timeoutMillis = timeoutMillis) { onNodeWithTag(tag).isDisplayed() } } +fun ComposeTestRule.waitForNodeWithTagToDisappear( + tag: String, + timeoutMillis: Long = DEFAULT_TIMEOUT_MILLIS +) { + waitUntil(timeoutMillis = timeoutMillis) { + onNodeWithTag(tag).isNotDisplayed() + } +} + +fun ComposeTestRule.waitForNodeWithText( + @StringRes textResId: Int, + timeoutMillis: Long = DEFAULT_TIMEOUT_MILLIS +) { + waitUntil(timeoutMillis = timeoutMillis) { + onNodeWithText(textResId).isDisplayed() + } +} + private fun ComposeTestRule.idleForVideoDuration( durationMillis: Long = VIDEO_DURATION_MILLIS, earlyExitPredicate: () -> Boolean = { @@ -342,6 +360,15 @@ fun ComposeTestRule.getCaptureModeToggleState(): CaptureMode = } } +fun ComposeTestRule.waitForCaptureModeToggleState( + targetState: CaptureMode, + timeoutMillis: Long = DEFAULT_TIMEOUT_MILLIS +) { + waitUntil(timeoutMillis = timeoutMillis) { + getCaptureModeToggleState() == targetState + } +} + // ////////////////////// // // check current quick settings state diff --git a/app/src/androidTest/java/com/google/jetpackcamera/utils/UiTestUtil.kt b/app/src/androidTest/java/com/google/jetpackcamera/utils/UiTestUtil.kt index c80b971c7..f6721e203 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/utils/UiTestUtil.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/utils/UiTestUtil.kt @@ -74,13 +74,13 @@ val compatMainActivityExtras: Bundle? val debugExtra: Bundle = Bundle().apply { putBoolean("KEY_DEBUG_MODE", true) } val cacheExtra: Bundle = Bundle().apply { putBoolean("KEY_REVIEW_AFTER_CAPTURE", true) } -const val DEFAULT_TIMEOUT_MILLIS = 1_000L -const val APP_START_TIMEOUT_MILLIS = 10_000L +const val DEFAULT_TIMEOUT_MILLIS = 5_000L +const val APP_START_TIMEOUT_MILLIS = 20_000L const val ELAPSED_TIME_TEXT_TIMEOUT_MILLIS = 45_000L const val SCREEN_FLASH_OVERLAY_TIMEOUT_MILLIS = 5_000L const val IMAGE_CAPTURE_TIMEOUT_MILLIS = 45_000L -const val VIDEO_CAPTURE_TIMEOUT_MILLIS = 5_000L -const val SAVE_MEDIA_TIMEOUT_MILLIS = 5_000L +const val VIDEO_CAPTURE_TIMEOUT_MILLIS = 15_000L +const val SAVE_MEDIA_TIMEOUT_MILLIS = 15_000L const val IMAGE_WELL_LOAD_TIMEOUT_MILLIS = 10_000L const val VIDEO_DURATION_MILLIS = 3_000L