From 6a18f096095cf072f1fdc91c564cf6a45c30b01b Mon Sep 17 00:00:00 2001 From: Trevor McGuire Date: Mon, 16 Sep 2024 17:14:43 -0700 Subject: [PATCH] Add Concurrent Camera tests Adds tests to ensure Concurrent Camera can be enabled without crashing the app, and appropriate features are disabled when concurrent camera mode is enabled. Also ensures that recordings can be made in concurrent camera mode. --- .../jetpackcamera/ConcurrentCameraTest.kt | 368 ++++++++++++++++++ .../jetpackcamera/VideoRecordingDeviceTest.kt | 34 +- .../jetpackcamera/utils/ComposeTestRuleExt.kt | 77 ++++ .../google/jetpackcamera/utils/UiTestUtil.kt | 54 +-- 4 files changed, 455 insertions(+), 78 deletions(-) create mode 100644 app/src/androidTest/java/com/google/jetpackcamera/ConcurrentCameraTest.kt diff --git a/app/src/androidTest/java/com/google/jetpackcamera/ConcurrentCameraTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/ConcurrentCameraTest.kt new file mode 100644 index 00000000..b4b1556b --- /dev/null +++ b/app/src/androidTest/java/com/google/jetpackcamera/ConcurrentCameraTest.kt @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import android.app.Activity +import android.net.Uri +import android.provider.MediaStore +import androidx.compose.ui.semantics.SemanticsNode +import androidx.compose.ui.semantics.SemanticsProperties +import androidx.compose.ui.test.SemanticsNodeInteraction +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +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 +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick +import androidx.test.core.app.ActivityScenario +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.rule.GrantPermissionRule +import com.google.common.truth.Truth.assertThat +import com.google.jetpackcamera.MainActivity +import com.google.jetpackcamera.feature.preview.R +import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_CAPTURE_MODE_BUTTON +import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_CONCURRENT_CAMERA_MODE_BUTTON +import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_DROP_DOWN +import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_FLIP_CAMERA_BUTTON +import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_HDR_BUTTON +import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_RATIO_1_1_BUTTON +import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_RATIO_BUTTON +import com.google.jetpackcamera.feature.preview.ui.CAPTURE_BUTTON +import com.google.jetpackcamera.feature.preview.ui.CAPTURE_MODE_TOGGLE_BUTTON +import com.google.jetpackcamera.feature.preview.ui.FLIP_CAMERA_BUTTON +import com.google.jetpackcamera.feature.preview.ui.IMAGE_CAPTURE_UNSUPPORTED_CONCURRENT_CAMERA_TAG +import com.google.jetpackcamera.feature.preview.ui.VIDEO_CAPTURE_SUCCESS_TAG +import com.google.jetpackcamera.settings.model.ConcurrentCameraMode +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 +import com.google.jetpackcamera.utils.getResString +import com.google.jetpackcamera.utils.longClickForVideoRecording +import com.google.jetpackcamera.utils.runMediaStoreAutoDeleteScenarioTest +import com.google.jetpackcamera.utils.runScenarioTest +import com.google.jetpackcamera.utils.stateDescriptionMatches +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ConcurrentCameraTest { + @get:Rule + val permissionsRule: GrantPermissionRule = + GrantPermissionRule.grant(*(TEST_REQUIRED_PERMISSIONS).toTypedArray()) + + @get:Rule + val composeTestRule = createEmptyComposeRule() + + @Test + fun concurrentCameraMode_canBeEnabled() = runConcurrentCameraScenarioTest { + val concurrentCameraModes = mutableListOf() + with(composeTestRule) { + onNodeWithTag(QUICK_SETTINGS_CONCURRENT_CAMERA_MODE_BUTTON) + .assertExists().apply { + // Check the original mode + fetchSemanticsNode().let { node -> + concurrentCameraModes.add(node.fetchConcurrentCameraMode()) + } + } + // Enable concurrent camera + .performClick().apply { + // Check the mode has changed + fetchSemanticsNode().let { node -> + concurrentCameraModes.add(node.fetchConcurrentCameraMode()) + } + } + + // Exit quick settings + onNodeWithTag(QUICK_SETTINGS_DROP_DOWN) + .assertExists() + .performClick() + + // Assert that the flip camera button is visible + onNodeWithTag(FLIP_CAMERA_BUTTON) + .assertIsDisplayed() + } + + assertThat(concurrentCameraModes).containsExactly( + ConcurrentCameraMode.OFF, + ConcurrentCameraMode.DUAL + ).inOrder() + } + + @Test + fun concurrentCameraMode_whenEnabled_canBeDisabled() = + runConcurrentCameraScenarioTest { + val concurrentCameraModes = mutableListOf() + with(composeTestRule) { + onNodeWithTag(QUICK_SETTINGS_CONCURRENT_CAMERA_MODE_BUTTON) + .assertExists().apply { + // Check the original mode + fetchSemanticsNode().let { node -> + concurrentCameraModes.add(node.fetchConcurrentCameraMode()) + } + } + // Enable concurrent camera + .performClick().apply { + // Check the mode has changed + fetchSemanticsNode().let { node -> + concurrentCameraModes.add(node.fetchConcurrentCameraMode()) + } + } + + // Exit quick settings + onNodeWithTag(QUICK_SETTINGS_DROP_DOWN) + .assertExists() + .performClick() + + // Assert that the flip camera button is visible + onNodeWithTag(FLIP_CAMERA_BUTTON) + .assertIsDisplayed() + + // Enter quick settings + onNodeWithTag(QUICK_SETTINGS_DROP_DOWN) + .assertExists() + .performClick() + + onNodeWithTag(QUICK_SETTINGS_CONCURRENT_CAMERA_MODE_BUTTON) + .assertExists() + // Disable concurrent camera + .performClick().apply { + // Check the mode is back to OFF + fetchSemanticsNode().let { node -> + concurrentCameraModes.add(node.fetchConcurrentCameraMode()) + } + } + + // Exit quick settings + onNodeWithTag(QUICK_SETTINGS_DROP_DOWN) + .assertExists() + .performClick() + + // Assert that the flip camera button is visible + onNodeWithTag(FLIP_CAMERA_BUTTON) + .assertIsDisplayed() + } + + assertThat(concurrentCameraModes).containsExactly( + ConcurrentCameraMode.OFF, + ConcurrentCameraMode.DUAL, + ConcurrentCameraMode.OFF + ).inOrder() + } + + @Test + fun concurrentCameraMode_whenEnabled_canFlipCamera() = + runConcurrentCameraScenarioTest { + with(composeTestRule) { + // Check device has multiple cameras + onNodeWithTag(QUICK_SETTINGS_FLIP_CAMERA_BUTTON) + .assertExists() + .assume(isEnabled()) { + "Device does not have multiple cameras." + } + + onNodeWithTag(QUICK_SETTINGS_CONCURRENT_CAMERA_MODE_BUTTON) + .assertExists() + .assertConcurrentCameraMode(ConcurrentCameraMode.OFF) + // Enable concurrent camera + .performClick() + .assertConcurrentCameraMode(ConcurrentCameraMode.DUAL) + + onNodeWithTag(QUICK_SETTINGS_FLIP_CAMERA_BUTTON) + .assertExists() + .performClick() + + // Exit quick settings + onNodeWithTag(QUICK_SETTINGS_DROP_DOWN) + .assertExists() + .performClick() + + // Assert that the flip camera button is visible + onNodeWithTag(FLIP_CAMERA_BUTTON) + .assertIsDisplayed() + } + } + + @Test + fun concurrentCameraMode_whenEnabled_canSwitchAspectRatio() = + runConcurrentCameraScenarioTest { + with(composeTestRule) { + onNodeWithTag(QUICK_SETTINGS_CONCURRENT_CAMERA_MODE_BUTTON) + .assertExists() + .assertConcurrentCameraMode(ConcurrentCameraMode.OFF) + // Enable concurrent camera + .performClick() + .assertConcurrentCameraMode(ConcurrentCameraMode.DUAL) + + // Click the ratio button + composeTestRule.onNodeWithTag(QUICK_SETTINGS_RATIO_BUTTON) + .assertExists() + .performClick() + + // Click the 1:1 ratio button + composeTestRule.onNodeWithTag(QUICK_SETTINGS_RATIO_1_1_BUTTON) + .assertExists() + .performClick() + + // Exit quick settings + onNodeWithTag(QUICK_SETTINGS_DROP_DOWN) + .assertExists() + .performClick() + + // Assert that the flip camera button is visible + onNodeWithTag(FLIP_CAMERA_BUTTON) + .assertIsDisplayed() + } + } + + @Test + fun concurrentCameraMode_whenEnabled_disablesOtherSettings() = + runConcurrentCameraScenarioTest { + with(composeTestRule) { + onNodeWithTag(QUICK_SETTINGS_CONCURRENT_CAMERA_MODE_BUTTON) + .assertExists() + .assertConcurrentCameraMode(ConcurrentCameraMode.OFF) + // Enable concurrent camera + .performClick() + .assertConcurrentCameraMode(ConcurrentCameraMode.DUAL) + + // Assert the capture mode button is disabled + onNodeWithTag(QUICK_SETTINGS_CAPTURE_MODE_BUTTON) + .assertExists() + .assert(isNotEnabled()) + + // Assert the HDR button is disabled + onNodeWithTag(QUICK_SETTINGS_HDR_BUTTON) + .assertExists() + .assert(isNotEnabled()) + + // Exit quick settings + onNodeWithTag(QUICK_SETTINGS_DROP_DOWN) + .assertExists() + .performClick() + + onNodeWithTag(CAPTURE_MODE_TOGGLE_BUTTON) + .assertExists() + .assert( + stateDescriptionMatches( + getResString(R.string.capture_mode_video_recording_content_description) + ) + ).performClick() + + waitUntil { + onNodeWithTag(IMAGE_CAPTURE_UNSUPPORTED_CONCURRENT_CAMERA_TAG).isDisplayed() + } + } + } + + @Test + fun concurrentCameraMode_canRecordVideo() = runConcurrentCameraScenarioTest( + mediaUriForSavedFiles = MediaStore.Video.Media.EXTERNAL_CONTENT_URI + ) { + with(composeTestRule) { + onNodeWithTag(QUICK_SETTINGS_CONCURRENT_CAMERA_MODE_BUTTON) + .assertExists() + .assertConcurrentCameraMode(ConcurrentCameraMode.OFF) + // Enable concurrent camera + .performClick() + .assertConcurrentCameraMode(ConcurrentCameraMode.DUAL) + + // Exit quick settings + onNodeWithTag(QUICK_SETTINGS_DROP_DOWN) + .assertExists() + .performClick() + + longClickForVideoRecording() + + waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) { + composeTestRule.onNodeWithTag(VIDEO_CAPTURE_SUCCESS_TAG).isDisplayed() + } + } + } + + // Ensures the app has launched and checks that the device supports concurrent camera before + // running the test. + // This test will start with quick settings visible + private inline fun runConcurrentCameraScenarioTest( + mediaUriForSavedFiles: Uri? = null, + expectedMediaFiles: Int = 1, + crossinline block: ActivityScenario.() -> Unit + ) { + val wrappedBlock: ActivityScenario.() -> Unit = { + // Wait for the capture button to be displayed + composeTestRule.waitUntil(timeoutMillis = APP_START_TIMEOUT_MILLIS) { + composeTestRule.onNodeWithTag(CAPTURE_BUTTON).isDisplayed() + } + + // /////////////////////////////////////////////////// + // Check that the device supports concurrent camera // + // /////////////////////////////////////////////////// + // Navigate to quick settings + composeTestRule.onNodeWithTag(QUICK_SETTINGS_DROP_DOWN) + .assertExists() + .performClick() + + // Check that the concurrent camera button is enabled + composeTestRule.onNodeWithTag(QUICK_SETTINGS_CONCURRENT_CAMERA_MODE_BUTTON) + .assertExists() + .assume(isEnabled()) { + "Device does not support concurrent camera." + } + + // /////////////////////////////////////////////////// + // Run the actual test // + // /////////////////////////////////////////////////// + block() + } + + if (mediaUriForSavedFiles != null) { + runMediaStoreAutoDeleteScenarioTest( + mediaUri = mediaUriForSavedFiles, + expectedNumFiles = expectedMediaFiles, + block = wrappedBlock + ) + } else { + runScenarioTest(wrappedBlock) + } + } + + context(SemanticsNodeInteractionsProvider) + private fun SemanticsNode.fetchConcurrentCameraMode(): ConcurrentCameraMode { + config[SemanticsProperties.ContentDescription].any { description -> + when (description) { + getResString(R.string.quick_settings_concurrent_camera_off_description) -> + return ConcurrentCameraMode.OFF + + getResString(R.string.quick_settings_concurrent_camera_dual_description) -> + return ConcurrentCameraMode.DUAL + + else -> false + } + } + throw AssertionError("Unable to determine concurrent camera mode from quick settings") + } + + context(SemanticsNodeInteractionsProvider) + private fun SemanticsNodeInteraction.assertConcurrentCameraMode( + mode: ConcurrentCameraMode + ): SemanticsNodeInteraction { + assertThat(fetchSemanticsNode().fetchConcurrentCameraMode()) + .isEqualTo(mode) + return this + } +} diff --git a/app/src/androidTest/java/com/google/jetpackcamera/VideoRecordingDeviceTest.kt b/app/src/androidTest/java/com/google/jetpackcamera/VideoRecordingDeviceTest.kt index 41a910e7..a537bb82 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/VideoRecordingDeviceTest.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/VideoRecordingDeviceTest.kt @@ -19,12 +19,10 @@ import android.app.Activity import android.net.Uri import android.os.Environment import android.provider.MediaStore -import androidx.compose.ui.test.ComposeTimeoutException 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 -import androidx.compose.ui.test.performTouchInput import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.GrantPermissionRule @@ -38,11 +36,11 @@ import com.google.jetpackcamera.utils.APP_START_TIMEOUT_MILLIS import com.google.jetpackcamera.utils.IMAGE_CAPTURE_TIMEOUT_MILLIS import com.google.jetpackcamera.utils.TEST_REQUIRED_PERMISSIONS import com.google.jetpackcamera.utils.VIDEO_CAPTURE_TIMEOUT_MILLIS -import com.google.jetpackcamera.utils.VIDEO_DURATION_MILLIS import com.google.jetpackcamera.utils.deleteFilesInDirAfterTimestamp import com.google.jetpackcamera.utils.doesImageFileExist import com.google.jetpackcamera.utils.getIntent import com.google.jetpackcamera.utils.getTestUri +import com.google.jetpackcamera.utils.longClickForVideoRecording import com.google.jetpackcamera.utils.runMediaStoreAutoDeleteScenarioTest import com.google.jetpackcamera.utils.runScenarioTestForResult import org.junit.Rule @@ -70,7 +68,7 @@ internal class VideoRecordingDeviceTest { composeTestRule.waitUntil(timeoutMillis = APP_START_TIMEOUT_MILLIS) { composeTestRule.onNodeWithTag(CAPTURE_BUTTON).isDisplayed() } - longClickForVideoRecording() + composeTestRule.longClickForVideoRecording() composeTestRule.waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) { composeTestRule.onNodeWithTag(VIDEO_CAPTURE_SUCCESS_TAG).isDisplayed() } @@ -88,7 +86,7 @@ internal class VideoRecordingDeviceTest { composeTestRule.waitUntil(timeoutMillis = APP_START_TIMEOUT_MILLIS) { composeTestRule.onNodeWithTag(CAPTURE_BUTTON).isDisplayed() } - longClickForVideoRecording() + composeTestRule.longClickForVideoRecording() } Truth.assertThat(result.resultCode).isEqualTo(Activity.RESULT_OK) Truth.assertThat(doesImageFileExist(uri, "video")).isTrue() @@ -106,7 +104,7 @@ internal class VideoRecordingDeviceTest { composeTestRule.waitUntil(timeoutMillis = APP_START_TIMEOUT_MILLIS) { composeTestRule.onNodeWithTag(CAPTURE_BUTTON).isDisplayed() } - longClickForVideoRecording() + composeTestRule.longClickForVideoRecording() composeTestRule.waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) { composeTestRule.onNodeWithTag(VIDEO_CAPTURE_FAILURE_TAG).isDisplayed() } @@ -143,30 +141,6 @@ internal class VideoRecordingDeviceTest { Truth.assertThat(doesImageFileExist(uri, "image")).isFalse() } - private fun longClickForVideoRecording() { - composeTestRule.onNodeWithTag(CAPTURE_BUTTON) - .assertExists() - .performTouchInput { - down(center) - } - idleForVideoDuration() - composeTestRule.onNodeWithTag(CAPTURE_BUTTON) - .assertExists() - .performTouchInput { - up() - } - } - - private fun idleForVideoDuration() { - // TODO: replace with a check for the timestamp UI of the video duration - try { - composeTestRule.waitUntil(timeoutMillis = VIDEO_DURATION_MILLIS) { - composeTestRule.onNodeWithTag("dummyTagForLongPress").isDisplayed() - } - } catch (e: ComposeTimeoutException) { - } - } - companion object { val DIR_PATH: String = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).path 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 e3be80e9..b6e0388d 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/utils/ComposeTestRuleExt.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/utils/ComposeTestRuleExt.kt @@ -17,13 +17,26 @@ package com.google.jetpackcamera.utils import android.content.Context import androidx.annotation.StringRes +import androidx.compose.ui.semantics.SemanticsProperties +import androidx.compose.ui.test.ComposeTimeoutException import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.SemanticsNodeInteraction import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import androidx.compose.ui.test.isDisplayed +import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTouchInput import androidx.compose.ui.test.printToString +import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ApplicationProvider +import com.google.jetpackcamera.MainActivity +import com.google.jetpackcamera.feature.preview.R +import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_FLIP_CAMERA_BUTTON +import com.google.jetpackcamera.feature.preview.ui.CAPTURE_BUTTON +import com.google.jetpackcamera.settings.model.LensFacing import org.junit.AssumptionViolatedException /** @@ -84,6 +97,70 @@ fun SemanticsNodeInteraction.assume( return this } +fun ComposeTestRule.longClickForVideoRecording() { + onNodeWithTag(CAPTURE_BUTTON) + .assertExists() + .performTouchInput { + down(center) + } + idleForVideoDuration() + onNodeWithTag(CAPTURE_BUTTON) + .assertExists() + .performTouchInput { + up() + } +} + +private fun ComposeTestRule.idleForVideoDuration() { + // TODO: replace with a check for the timestamp UI of the video duration + try { + waitUntil(timeoutMillis = VIDEO_DURATION_MILLIS) { + onNodeWithTag("dummyTagForLongPress").isDisplayed() + } + } catch (e: ComposeTimeoutException) { + } +} + +context(ActivityScenario) +fun ComposeTestRule.getCurrentLensFacing(): LensFacing { + var needReturnFromQuickSettings = false + onNodeWithContentDescription(R.string.quick_settings_dropdown_closed_description).apply { + if (isDisplayed()) { + performClick() + needReturnFromQuickSettings = true + } + } + + onNodeWithContentDescription(R.string.quick_settings_dropdown_open_description).assertExists( + "LensFacing can only be retrieved from PreviewScreen or QuickSettings screen" + ) + + try { + return onNodeWithTag(QUICK_SETTINGS_FLIP_CAMERA_BUTTON).fetchSemanticsNode( + "Flip camera button is not visible when expected." + ).let { node -> + node.config[SemanticsProperties.ContentDescription].any { description -> + when (description) { + getResString(R.string.quick_settings_front_camera_description) -> + return@let LensFacing.FRONT + + getResString(R.string.quick_settings_back_camera_description) -> + return@let LensFacing.BACK + + else -> false + } + } + throw AssertionError("Unable to determine lens facing from quick settings") + } + } finally { + if (needReturnFromQuickSettings) { + onNodeWithContentDescription(R.string.quick_settings_dropdown_open_description) + .assertExists() + .performClick() + } + } +} + internal fun buildGeneralErrorMessage( errorMessage: String, nodeInteraction: SemanticsNodeInteraction 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 c5f973b6..bf5e8e4f 100644 --- a/app/src/androidTest/java/com/google/jetpackcamera/utils/UiTestUtil.kt +++ b/app/src/androidTest/java/com/google/jetpackcamera/utils/UiTestUtil.kt @@ -23,18 +23,11 @@ import android.net.Uri import android.provider.MediaStore import android.util.Log import androidx.compose.ui.semantics.SemanticsProperties -import androidx.compose.ui.test.isDisplayed -import androidx.compose.ui.test.junit4.ComposeTestRule -import androidx.compose.ui.test.onNodeWithTag -import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.SemanticsMatcher import androidx.lifecycle.Lifecycle import androidx.test.core.app.ActivityScenario import androidx.test.platform.app.InstrumentationRegistry import com.google.common.truth.Truth.assertWithMessage -import com.google.jetpackcamera.MainActivity -import com.google.jetpackcamera.feature.preview.R -import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_FLIP_CAMERA_BUTTON -import com.google.jetpackcamera.settings.model.LensFacing import java.io.File import java.net.URLConnection import java.util.concurrent.TimeoutException @@ -154,46 +147,6 @@ suspend inline fun ActivityScenario.pollResult( ) } -context(ActivityScenario) -fun ComposeTestRule.getCurrentLensFacing(): LensFacing { - var needReturnFromQuickSettings = false - onNodeWithContentDescription(R.string.quick_settings_dropdown_closed_description).apply { - if (isDisplayed()) { - performClick() - needReturnFromQuickSettings = true - } - } - - onNodeWithContentDescription(R.string.quick_settings_dropdown_open_description).assertExists( - "LensFacing can only be retrieved from PreviewScreen or QuickSettings screen" - ) - - try { - return onNodeWithTag(QUICK_SETTINGS_FLIP_CAMERA_BUTTON).fetchSemanticsNode( - "Flip camera button is not visible when expected." - ).let { node -> - node.config[SemanticsProperties.ContentDescription].any { description -> - when (description) { - getResString(R.string.quick_settings_front_camera_description) -> - return@let LensFacing.FRONT - - getResString(R.string.quick_settings_back_camera_description) -> - return@let LensFacing.BACK - - else -> false - } - } - throw AssertionError("Unable to determine lens facing from quick settings") - } - } finally { - if (needReturnFromQuickSettings) { - onNodeWithContentDescription(R.string.quick_settings_dropdown_open_description) - .assertExists() - .performClick() - } - } -} - fun getTestUri(directoryPath: String, timeStamp: Long, suffix: String): Uri { return Uri.fromFile( File( @@ -244,3 +197,8 @@ fun getIntent(uri: Uri, action: String): Intent { intent.putExtra(MediaStore.EXTRA_OUTPUT, uri) return intent } + +fun stateDescriptionMatches(expected: String?) = SemanticsMatcher("stateDescription is $expected") { + SemanticsProperties.StateDescription in it.config && + (it.config[SemanticsProperties.StateDescription] == expected) +}