Merge pull request #4516 from vector-im/feature/adm/ui-test-ci-tweaks

UI test CI tweaks
This commit is contained in:
Benoit Marty 2021-11-19 09:32:18 +01:00 committed by GitHub
commit f622468f3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 79 additions and 24 deletions

View File

@ -56,10 +56,10 @@ jobs:
java-version: '11' java-version: '11'
- name: Run sanity tests on API ${{ matrix.api-level }} - name: Run sanity tests on API ${{ matrix.api-level }}
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
continue-on-error: true # allow pipeline to upload failure results
with: with:
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
profile: 24 # Pixel 5
emulator-build: 7425822 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160 emulator-build: 7425822 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
script: | script: |
adb root adb root
@ -67,11 +67,10 @@ jobs:
touch emulator.log touch emulator.log
chmod 777 emulator.log chmod 777 emulator.log
adb logcat >> emulator.log & adb logcat >> emulator.log &
./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || adb pull storage/emulated/0/Pictures/failure_screenshots ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || adb pull storage/emulated/0/Pictures/failure_screenshots && exit 1
- name: Upload Failing Test Report Log - name: Upload Failing Test Report Log
if: failure()
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
if: failure()
with: with:
name: sanity-error-results name: sanity-error-results
path: | path: |

View File

@ -68,6 +68,18 @@ object EspressoHelper {
} }
} }
fun withRetry(attempts: Int = 3, action: () -> Unit) {
runCatching { action() }.onFailure {
val remainingAttempts = attempts - 1
if (remainingAttempts <= 0) {
throw it
} else {
Thread.sleep(500)
withRetry(remainingAttempts, action)
}
}
}
fun getString(@StringRes id: Int): String { fun getString(@StringRes id: Int): String {
return EspressoHelper.getCurrentActivity()!!.resources.getString(id) return EspressoHelper.getCurrentActivity()!!.resources.getString(id)
} }
@ -235,11 +247,16 @@ fun clickOnAndGoBack(@StringRes name: Int, block: () -> Unit) {
Espresso.pressBack() Espresso.pressBack()
} }
inline fun <reified T : VectorBaseBottomSheetDialogFragment<*>> interactWithSheet(contentMatcher: Matcher<View>, noinline block: () -> Unit = {}) { inline fun <reified T : VectorBaseBottomSheetDialogFragment<*>> interactWithSheet(
contentMatcher: Matcher<View>,
@BottomSheetBehavior.State openState: Int = BottomSheetBehavior.STATE_EXPANDED,
@BottomSheetBehavior.State exitState: Int = BottomSheetBehavior.STATE_HIDDEN,
noinline block: () -> Unit = {}
) {
waitUntilViewVisible(contentMatcher) waitUntilViewVisible(contentMatcher)
val behaviour = (EspressoHelper.getBottomSheetDialog<T>()!!.dialog as BottomSheetDialog).behavior val behaviour = (EspressoHelper.getBottomSheetDialog<T>()!!.dialog as BottomSheetDialog).behavior
withIdlingResource(BottomSheetResource(behaviour, BottomSheetBehavior.STATE_EXPANDED), block) withIdlingResource(BottomSheetResource(behaviour, openState), block)
withIdlingResource(BottomSheetResource(behaviour, BottomSheetBehavior.STATE_HIDDEN)) {} withIdlingResource(BottomSheetResource(behaviour, exitState)) {}
} }
class BottomSheetResource( class BottomSheetResource(

View File

@ -18,17 +18,25 @@ package im.vector.app.espresso.tools
import android.app.Activity import android.app.Activity
import android.view.View import android.view.View
import androidx.test.espresso.Espresso import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers
import im.vector.app.activityIdlingResource import im.vector.app.activityIdlingResource
import im.vector.app.waitForView import im.vector.app.waitForView
import im.vector.app.withIdlingResource import im.vector.app.withIdlingResource
import org.hamcrest.Matcher import org.hamcrest.Matcher
import org.hamcrest.Matchers.not
inline fun <reified T : Activity> waitUntilActivityVisible(noinline block: (() -> Unit) = {}) { inline fun <reified T : Activity> waitUntilActivityVisible(noinline block: (() -> Unit) = {}) {
withIdlingResource(activityIdlingResource(T::class.java), block) withIdlingResource(activityIdlingResource(T::class.java), block)
} }
fun waitUntilViewVisible(viewMatcher: Matcher<View>) { fun waitUntilViewVisible(viewMatcher: Matcher<View>) {
Espresso.onView(ViewMatchers.isRoot()).perform(waitForView(viewMatcher)) onView(ViewMatchers.isRoot()).perform(waitForView(viewMatcher))
}
fun waitUntilDialogVisible(viewMatcher: Matcher<View>) {
onView(viewMatcher).inRoot(isDialog()).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
waitUntilViewVisible(viewMatcher)
} }

View File

@ -16,6 +16,7 @@
package im.vector.app.ui package im.vector.app.ui
import androidx.test.espresso.IdlingPolicies
import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
@ -30,6 +31,7 @@ import org.junit.Test
import org.junit.rules.RuleChain import org.junit.rules.RuleChain
import org.junit.runner.RunWith import org.junit.runner.RunWith
import java.util.UUID import java.util.UUID
import java.util.concurrent.TimeUnit
/** /**
* This test aim to open every possible screen of the application * This test aim to open every possible screen of the application
@ -51,6 +53,8 @@ class UiAllScreensSanityTest {
// 2021-04-08 Testing 429 change // 2021-04-08 Testing 429 change
@Test @Test
fun allScreensTest() { fun allScreensTest() {
IdlingPolicies.setMasterPolicyTimeout(120, TimeUnit.SECONDS)
// Create an account // Create an account
val userId = "UiTest_" + UUID.randomUUID().toString() val userId = "UiTest_" + UUID.randomUUID().toString()
elementRobot.signUp(userId) elementRobot.signUp(userId)

View File

@ -28,6 +28,7 @@ import com.adevinta.android.barista.interaction.BaristaDrawerInteractions.openDr
import im.vector.app.EspressoHelper import im.vector.app.EspressoHelper
import im.vector.app.R import im.vector.app.R
import im.vector.app.espresso.tools.waitUntilActivityVisible import im.vector.app.espresso.tools.waitUntilActivityVisible
import im.vector.app.espresso.tools.waitUntilDialogVisible
import im.vector.app.espresso.tools.waitUntilViewVisible import im.vector.app.espresso.tools.waitUntilViewVisible
import im.vector.app.features.createdirect.CreateDirectRoomActivity import im.vector.app.features.createdirect.CreateDirectRoomActivity
import im.vector.app.features.home.HomeActivity import im.vector.app.features.home.HomeActivity
@ -104,17 +105,19 @@ class ElementRobot {
}.isSuccess }.isSuccess
if (expectSignOutWarning != isShowingSignOutWarning) { if (expectSignOutWarning != isShowingSignOutWarning) {
Timber.w("Unexpected sign out flow, expected warning to be: ${expectSignOutWarning.toWarningType()} but was ${isShowingSignOutWarning.toWarningType()}") val expected = expectSignOutWarning.toWarningType()
val actual = isShowingSignOutWarning.toWarningType()
Timber.w("Unexpected sign out flow, expected warning to be: $expected but was $actual")
} }
if (isShowingSignOutWarning) { if (isShowingSignOutWarning) {
// We have sent a message in a e2e room, accept to loose it // We have sent a message in a e2e room, accept to loose it
clickOn(R.id.exitAnywayButton) clickOn(R.id.exitAnywayButton)
// Dark pattern // Dark pattern
waitUntilViewVisible(withId(android.R.id.button2)) waitUntilDialogVisible(withId(android.R.id.button2))
clickDialogNegativeButton() clickDialogNegativeButton()
} else { } else {
waitUntilViewVisible(withId(android.R.id.button1)) waitUntilDialogVisible(withId(android.R.id.button1))
clickDialogPositiveButton() clickDialogPositiveButton()
} }

View File

@ -17,9 +17,13 @@
package im.vector.app.ui.robot package im.vector.app.ui.robot
import androidx.test.espresso.Espresso.pressBack import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
import com.adevinta.android.barista.interaction.BaristaListInteractions.clickListItem import com.adevinta.android.barista.interaction.BaristaListInteractions.clickListItem
import com.google.android.material.bottomsheet.BottomSheetBehavior
import im.vector.app.R import im.vector.app.R
import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
import im.vector.app.interactWithSheet
import java.lang.Thread.sleep import java.lang.Thread.sleep
class MessageMenuRobot( class MessageMenuRobot(
@ -36,7 +40,9 @@ class MessageMenuRobot(
fun editHistory() { fun editHistory() {
clickOn(R.string.message_view_edit_history) clickOn(R.string.message_view_edit_history)
interactWithSheet<ViewEditHistoryBottomSheet>(withText(R.string.message_edits), openState = BottomSheetBehavior.STATE_COLLAPSED) {
pressBack() pressBack()
}
autoClosed = true autoClosed = true
} }

View File

@ -26,6 +26,7 @@ import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions.assert
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
import com.adevinta.android.barista.interaction.BaristaEditTextInteractions.writeTo import com.adevinta.android.barista.interaction.BaristaEditTextInteractions.writeTo
import im.vector.app.R import im.vector.app.R
import im.vector.app.espresso.tools.waitUntilViewVisible
import im.vector.app.waitForView import im.vector.app.waitForView
class OnboardingRobot { class OnboardingRobot {
@ -42,6 +43,7 @@ class OnboardingRobot {
userId: String, userId: String,
password: String, password: String,
homeServerUrl: String) { homeServerUrl: String) {
waitUntilViewVisible(withId(R.id.loginSplashSubmit))
assertDisplayed(R.id.loginSplashSubmit, R.string.login_splash_submit) assertDisplayed(R.id.loginSplashSubmit, R.string.login_splash_submit)
clickOn(R.id.loginSplashSubmit) clickOn(R.id.loginSplashSubmit)
assertDisplayed(R.id.loginServerTitle, R.string.login_server_title) assertDisplayed(R.id.loginServerTitle, R.string.login_server_title)

View File

@ -30,12 +30,15 @@ import com.adevinta.android.barista.interaction.BaristaClickInteractions.longCli
import com.adevinta.android.barista.interaction.BaristaEditTextInteractions.writeTo import com.adevinta.android.barista.interaction.BaristaEditTextInteractions.writeTo
import com.adevinta.android.barista.interaction.BaristaMenuClickInteractions.clickMenu import com.adevinta.android.barista.interaction.BaristaMenuClickInteractions.clickMenu
import com.adevinta.android.barista.interaction.BaristaMenuClickInteractions.openMenu import com.adevinta.android.barista.interaction.BaristaMenuClickInteractions.openMenu
import com.google.android.material.bottomsheet.BottomSheetBehavior
import im.vector.app.R import im.vector.app.R
import im.vector.app.espresso.tools.waitUntilViewVisible import im.vector.app.espresso.tools.waitUntilViewVisible
import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet
import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
import im.vector.app.features.reactions.data.EmojiDataSource import im.vector.app.features.reactions.data.EmojiDataSource
import im.vector.app.interactWithSheet import im.vector.app.interactWithSheet
import im.vector.app.waitForView import im.vector.app.waitForView
import im.vector.app.withRetry
import java.lang.Thread.sleep import java.lang.Thread.sleep
class RoomDetailRobot { class RoomDetailRobot {
@ -68,10 +71,14 @@ class RoomDetailRobot {
openMessageMenu(message) { openMessageMenu(message) {
addQuickReaction(quickReaction) addQuickReaction(quickReaction)
} }
println("Open reactions bottom sheet")
// Open reactions // Open reactions
longClickOn(quickReaction) longClickReaction(quickReaction)
// wait for bottom sheet // wait for bottom sheet
interactWithSheet<ViewReactionsBottomSheet>(withText(R.string.reactions), openState = BottomSheetBehavior.STATE_COLLAPSED) {
pressBack() pressBack()
}
println("Room Detail Robot: Open reaction from emoji picker")
// Test add reaction // Test add reaction
openMessageMenu(message) { openMessageMenu(message) {
addReactionFromEmojiPicker() addReactionFromEmojiPicker()
@ -81,16 +88,24 @@ class RoomDetailRobot {
edit() edit()
} }
// TODO Cancel action // TODO Cancel action
writeTo(R.id.composerEditText, "Hello universe!") val edit = "Hello universe - long message to avoid espresso tapping edited!"
writeTo(R.id.composerEditText, edit)
// Wait a bit for the keyboard layout to update // Wait a bit for the keyboard layout to update
waitUntilViewVisible(withId(R.id.sendButton)) waitUntilViewVisible(withId(R.id.sendButton))
clickOn(R.id.sendButton) clickOn(R.id.sendButton)
// Wait for the UI to update // Wait for the UI to update
waitUntilViewVisible(withText("Hello universe! (edited)")) waitUntilViewVisible(withText("$edit (edited)"))
// Open edit history // Open edit history
openMessageMenu("Hello universe! (edited)") { openMessageMenu("$edit (edited)") {
editHistory() editHistory()
} }
waitUntilViewVisible(withId(R.id.composerEditText))
}
private fun longClickReaction(quickReaction: String) {
withRetry {
longClickOn(quickReaction)
}
} }
fun openMessageMenu(message: String, block: MessageMenuRobot.() -> Unit) { fun openMessageMenu(message: String, block: MessageMenuRobot.() -> Unit) {
@ -111,7 +126,7 @@ class RoomDetailRobot {
} }
fun openSettings(block: RoomSettingsRobot.() -> Unit) { fun openSettings(block: RoomSettingsRobot.() -> Unit) {
clickOn(R.id.roomToolbarTitleView) clickMenu(R.id.timeline_setting)
waitForView(withId(R.id.roomProfileAvatarView)) waitForView(withId(R.id.roomProfileAvatarView))
sleep(1000) sleep(1000)
block(RoomSettingsRobot()) block(RoomSettingsRobot())

View File

@ -26,6 +26,7 @@ import com.adevinta.android.barista.interaction.BaristaDialogInteractions.clickD
import com.adevinta.android.barista.interaction.BaristaListInteractions.clickListItem import com.adevinta.android.barista.interaction.BaristaListInteractions.clickListItem
import im.vector.app.R import im.vector.app.R
import im.vector.app.espresso.tools.waitUntilActivityVisible import im.vector.app.espresso.tools.waitUntilActivityVisible
import im.vector.app.espresso.tools.waitUntilDialogVisible
import im.vector.app.espresso.tools.waitUntilViewVisible import im.vector.app.espresso.tools.waitUntilViewVisible
import im.vector.app.features.roommemberprofile.RoomMemberProfileActivity import im.vector.app.features.roommemberprofile.RoomMemberProfileActivity
@ -78,9 +79,9 @@ class RoomSettingsRobot {
// Room permissions // Room permissions
clickListItem(R.id.matrixProfileRecyclerView, 17) clickListItem(R.id.matrixProfileRecyclerView, 17)
waitUntilViewVisible(withText(R.string.room_permissions_title)) waitUntilViewVisible(withText(R.string.room_permissions_change_room_avatar))
clickOn(R.string.room_permissions_change_room_avatar) clickOn(R.string.room_permissions_change_room_avatar)
waitUntilViewVisible(withId(android.R.id.button2)) waitUntilDialogVisible(withId(android.R.id.button2))
clickDialogNegativeButton() clickDialogNegativeButton()
waitUntilViewVisible(withText(R.string.room_permissions_title)) waitUntilViewVisible(withText(R.string.room_permissions_title))
// Toggle // Toggle
@ -95,7 +96,7 @@ class RoomSettingsRobot {
private fun leaveRoom(block: DialogRobot.() -> Unit) { private fun leaveRoom(block: DialogRobot.() -> Unit) {
clickListItem(R.id.matrixProfileRecyclerView, 13) clickListItem(R.id.matrixProfileRecyclerView, 13)
waitUntilViewVisible(withId(android.R.id.button2)) waitUntilDialogVisible(withId(android.R.id.button2))
val dialogRobot = DialogRobot() val dialogRobot = DialogRobot()
block(dialogRobot) block(dialogRobot)
if (dialogRobot.returnedToPreviousScreen) { if (dialogRobot.returnedToPreviousScreen) {
@ -135,7 +136,7 @@ class RoomSettingsRobot {
// Role // Role
clickListItem(R.id.matrixProfileRecyclerView, 3) clickListItem(R.id.matrixProfileRecyclerView, 3)
waitUntilViewVisible(withId(android.R.id.button2)) waitUntilDialogVisible(withId(android.R.id.button2))
clickDialogNegativeButton() clickDialogNegativeButton()
waitUntilViewVisible(withId(R.id.matrixProfileRecyclerView)) waitUntilViewVisible(withId(R.id.matrixProfileRecyclerView))
pressBack() pressBack()