Merge pull request #4439 from vector-im/feature/adm/developer-mode-sanity-check
Developer mode sanity check & failure screenshots
This commit is contained in:
commit
8b655edd34
21
.github/workflows/sanity_test.yml
vendored
21
.github/workflows/sanity_test.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
api-level: [28]
|
api-level: [ 29 ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
@ -56,7 +56,24 @@ 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
|
||||||
api-level: ${{ matrix.api-level }}
|
api-level: ${{ matrix.api-level }}
|
||||||
script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest
|
emulator-build: 7425822 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
|
||||||
|
script: |
|
||||||
|
adb root
|
||||||
|
adb logcat -c
|
||||||
|
touch emulator.log
|
||||||
|
chmod 777 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
|
||||||
|
|
||||||
|
- name: Upload Failing Test Report Log
|
||||||
|
if: failure()
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: sanity-error-results
|
||||||
|
path: |
|
||||||
|
emulator.log
|
||||||
|
failure_screenshots/
|
||||||
|
@ -19,6 +19,7 @@ package im.vector.app
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.test.espresso.Espresso
|
import androidx.test.espresso.Espresso
|
||||||
import androidx.test.espresso.IdlingRegistry
|
import androidx.test.espresso.IdlingRegistry
|
||||||
@ -35,6 +36,11 @@ import androidx.test.runner.lifecycle.ActivityLifecycleCallback
|
|||||||
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
|
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
|
||||||
import androidx.test.runner.lifecycle.Stage
|
import androidx.test.runner.lifecycle.Stage
|
||||||
import com.adevinta.android.barista.interaction.BaristaClickInteractions
|
import com.adevinta.android.barista.interaction.BaristaClickInteractions
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
|
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
|
||||||
|
import im.vector.app.espresso.tools.waitUntilViewVisible
|
||||||
import org.hamcrest.Matcher
|
import org.hamcrest.Matcher
|
||||||
import org.hamcrest.Matchers
|
import org.hamcrest.Matchers
|
||||||
import org.hamcrest.StringDescription
|
import org.hamcrest.StringDescription
|
||||||
@ -52,6 +58,18 @@ object EspressoHelper {
|
|||||||
}
|
}
|
||||||
return currentActivity
|
return currentActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : VectorBaseBottomSheetDialogFragment<*>> getBottomSheetDialog(): BottomSheetDialogFragment? {
|
||||||
|
return (getCurrentActivity() as? FragmentActivity)
|
||||||
|
?.supportFragmentManager
|
||||||
|
?.fragments
|
||||||
|
?.filterIsInstance<T>()
|
||||||
|
?.firstOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getString(@StringRes id: Int): String {
|
||||||
|
return EspressoHelper.getCurrentActivity()!!.resources.getString(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun waitForView(viewMatcher: Matcher<View>, timeout: Long = 10_000, waitForDisplayed: Boolean = true): ViewAction {
|
fun waitForView(viewMatcher: Matcher<View>, timeout: Long = 10_000, waitForDisplayed: Boolean = true): ViewAction {
|
||||||
@ -216,3 +234,46 @@ fun clickOnAndGoBack(@StringRes name: Int, block: () -> Unit) {
|
|||||||
block()
|
block()
|
||||||
Espresso.pressBack()
|
Espresso.pressBack()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : VectorBaseBottomSheetDialogFragment<*>> interactWithSheet(contentMatcher: Matcher<View>, noinline block: () -> Unit = {}) {
|
||||||
|
waitUntilViewVisible(contentMatcher)
|
||||||
|
val behaviour = (EspressoHelper.getBottomSheetDialog<T>()!!.dialog as BottomSheetDialog).behavior
|
||||||
|
withIdlingResource(BottomSheetResource(behaviour, BottomSheetBehavior.STATE_EXPANDED), block)
|
||||||
|
withIdlingResource(BottomSheetResource(behaviour, BottomSheetBehavior.STATE_HIDDEN)) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BottomSheetResource(
|
||||||
|
private val bottomSheetBehavior: BottomSheetBehavior<*>,
|
||||||
|
@BottomSheetBehavior.State private val wantedState: Int
|
||||||
|
) : IdlingResource, BottomSheetBehavior.BottomSheetCallback() {
|
||||||
|
|
||||||
|
private var isIdle: Boolean = false
|
||||||
|
private var resourceCallback: IdlingResource.ResourceCallback? = null
|
||||||
|
|
||||||
|
override fun onSlide(bottomSheet: View, slideOffset: Float) {}
|
||||||
|
|
||||||
|
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||||
|
val wasIdle = isIdle
|
||||||
|
isIdle = newState == BottomSheetBehavior.STATE_EXPANDED
|
||||||
|
if (!wasIdle && isIdle) {
|
||||||
|
bottomSheetBehavior.removeBottomSheetCallback(this)
|
||||||
|
resourceCallback?.onTransitionToIdle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getName() = "BottomSheet awaiting state: $wantedState"
|
||||||
|
|
||||||
|
override fun isIdleNow() = isIdle
|
||||||
|
|
||||||
|
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback) {
|
||||||
|
resourceCallback = callback
|
||||||
|
|
||||||
|
val state = bottomSheetBehavior.state
|
||||||
|
isIdle = state == wantedState
|
||||||
|
if (isIdle) {
|
||||||
|
resourceCallback!!.onTransitionToIdle()
|
||||||
|
} else {
|
||||||
|
bottomSheetBehavior.addBottomSheetCallback(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.espresso.tools
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Environment
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||||
|
import org.junit.rules.TestWatcher
|
||||||
|
import org.junit.runner.Description
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
private val SCREENSHOT_FOLDER_LOCATION = "${Environment.DIRECTORY_PICTURES}/failure_screenshots"
|
||||||
|
private val deviceLanguage = Locale.getDefault().language
|
||||||
|
|
||||||
|
class ScreenshotFailureRule : TestWatcher() {
|
||||||
|
override fun failed(e: Throwable?, description: Description) {
|
||||||
|
val screenShotName = "$deviceLanguage-${description.methodName}-${SimpleDateFormat("EEE-MMMM-dd-HH:mm:ss").format(Date())}"
|
||||||
|
val bitmap = getInstrumentation().uiAutomation.takeScreenshot()
|
||||||
|
storeFailureScreenshot(bitmap, screenShotName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores screenshots in sdcard/Pictures/failure_screenshots
|
||||||
|
*/
|
||||||
|
private fun storeFailureScreenshot(bitmap: Bitmap, screenshotName: String) {
|
||||||
|
val contentResolver = getInstrumentation().targetContext.applicationContext.contentResolver
|
||||||
|
|
||||||
|
val contentValues = ContentValues().apply {
|
||||||
|
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
|
||||||
|
put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
|
||||||
|
}
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= 29) {
|
||||||
|
useMediaStoreScreenshotStorage(
|
||||||
|
contentValues,
|
||||||
|
contentResolver,
|
||||||
|
screenshotName,
|
||||||
|
SCREENSHOT_FOLDER_LOCATION,
|
||||||
|
bitmap
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
usePublicExternalScreenshotStorage(
|
||||||
|
contentValues,
|
||||||
|
contentResolver,
|
||||||
|
screenshotName,
|
||||||
|
SCREENSHOT_FOLDER_LOCATION,
|
||||||
|
bitmap
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun useMediaStoreScreenshotStorage(
|
||||||
|
contentValues: ContentValues,
|
||||||
|
contentResolver: ContentResolver,
|
||||||
|
screenshotName: String,
|
||||||
|
screenshotLocation: String,
|
||||||
|
bitmap: Bitmap
|
||||||
|
) {
|
||||||
|
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "$screenshotName.jpeg")
|
||||||
|
contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, screenshotLocation)
|
||||||
|
val uri: Uri? = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||||
|
if (uri != null) {
|
||||||
|
contentResolver.openOutputStream(uri)?.let { saveScreenshotToStream(bitmap, it) }
|
||||||
|
contentResolver.update(uri, contentValues, null, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun usePublicExternalScreenshotStorage(
|
||||||
|
contentValues: ContentValues,
|
||||||
|
contentResolver: ContentResolver,
|
||||||
|
screenshotName: String,
|
||||||
|
screenshotLocation: String,
|
||||||
|
bitmap: Bitmap
|
||||||
|
) {
|
||||||
|
val directory = File(Environment.getExternalStoragePublicDirectory(screenshotLocation).toString())
|
||||||
|
if (!directory.exists()) {
|
||||||
|
directory.mkdirs()
|
||||||
|
}
|
||||||
|
val file = File(directory, "$screenshotName.jpeg")
|
||||||
|
saveScreenshotToStream(bitmap, FileOutputStream(file))
|
||||||
|
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveScreenshotToStream(bitmap: Bitmap, outputStream: OutputStream) {
|
||||||
|
outputStream.use {
|
||||||
|
try {
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, it)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Timber.e("Screenshot was not stored at this time")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,10 +19,15 @@ package im.vector.app.ui
|
|||||||
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
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.espresso.tools.ScreenshotFailureRule
|
||||||
import im.vector.app.features.MainActivity
|
import im.vector.app.features.MainActivity
|
||||||
|
import im.vector.app.getString
|
||||||
import im.vector.app.ui.robot.ElementRobot
|
import im.vector.app.ui.robot.ElementRobot
|
||||||
|
import im.vector.app.ui.robot.withDeveloperMode
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.junit.rules.RuleChain
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@ -34,7 +39,9 @@ import java.util.UUID
|
|||||||
class UiAllScreensSanityTest {
|
class UiAllScreensSanityTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val activityRule = ActivityScenarioRule(MainActivity::class.java)
|
val testRule = RuleChain
|
||||||
|
.outerRule(ActivityScenarioRule(MainActivity::class.java))
|
||||||
|
.around(ScreenshotFailureRule())
|
||||||
|
|
||||||
private val elementRobot = ElementRobot()
|
private val elementRobot = ElementRobot()
|
||||||
|
|
||||||
@ -69,13 +76,30 @@ class UiAllScreensSanityTest {
|
|||||||
createNewRoom {
|
createNewRoom {
|
||||||
crawl()
|
crawl()
|
||||||
createRoom {
|
createRoom {
|
||||||
postMessage("Hello world!")
|
val message = "Hello world!"
|
||||||
|
postMessage(message)
|
||||||
crawl()
|
crawl()
|
||||||
|
crawlMessage(message)
|
||||||
openSettings { crawl() }
|
openSettings { crawl() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
elementRobot.withDeveloperMode {
|
||||||
|
settings {
|
||||||
|
advancedSettings { crawlDeveloperOptions() }
|
||||||
|
}
|
||||||
|
roomList {
|
||||||
|
openRoom(getString(R.string.room_displayname_empty_room)) {
|
||||||
|
val message = "Test view source"
|
||||||
|
postMessage(message)
|
||||||
|
openMessageMenu(message) {
|
||||||
|
viewSource()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
elementRobot.roomList {
|
elementRobot.roomList {
|
||||||
verifyCreatedRoom()
|
verifyCreatedRoom()
|
||||||
}
|
}
|
||||||
|
@ -141,3 +141,9 @@ class ElementRobot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun Boolean.toWarningType() = if (this) "shown" else "skipped"
|
private fun Boolean.toWarningType() = if (this) "shown" else "skipped"
|
||||||
|
|
||||||
|
fun ElementRobot.withDeveloperMode(block: ElementRobot.() -> Unit) {
|
||||||
|
settings { toggleDeveloperMode() }
|
||||||
|
block()
|
||||||
|
settings { toggleDeveloperMode() }
|
||||||
|
}
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.ui.robot
|
||||||
|
|
||||||
|
import androidx.test.espresso.Espresso.pressBack
|
||||||
|
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
|
||||||
|
import com.adevinta.android.barista.interaction.BaristaListInteractions.clickListItem
|
||||||
|
import im.vector.app.R
|
||||||
|
import java.lang.Thread.sleep
|
||||||
|
|
||||||
|
class MessageMenuRobot(
|
||||||
|
var autoClosed: Boolean = false
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun viewSource() {
|
||||||
|
clickOn(R.string.view_source)
|
||||||
|
// wait for library
|
||||||
|
sleep(1000)
|
||||||
|
pressBack()
|
||||||
|
autoClosed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun editHistory() {
|
||||||
|
clickOn(R.string.message_view_edit_history)
|
||||||
|
pressBack()
|
||||||
|
autoClosed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addQuickReaction(quickReaction: String) {
|
||||||
|
clickOn(quickReaction)
|
||||||
|
autoClosed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addReactionFromEmojiPicker() {
|
||||||
|
clickOn(R.string.message_add_reaction)
|
||||||
|
// Wait for emoji to load, it's async now
|
||||||
|
sleep(2000)
|
||||||
|
clickListItem(R.id.emojiRecyclerView, 4)
|
||||||
|
autoClosed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun edit() {
|
||||||
|
clickOn(R.string.edit)
|
||||||
|
autoClosed = true
|
||||||
|
}
|
||||||
|
}
|
@ -17,21 +17,24 @@
|
|||||||
package im.vector.app.ui.robot
|
package im.vector.app.ui.robot
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.test.espresso.Espresso
|
import androidx.test.espresso.Espresso.onView
|
||||||
import androidx.test.espresso.Espresso.pressBack
|
import androidx.test.espresso.Espresso.pressBack
|
||||||
import androidx.test.espresso.action.ViewActions
|
import androidx.test.espresso.action.ViewActions
|
||||||
import androidx.test.espresso.contrib.RecyclerViewActions
|
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||||
import androidx.test.espresso.matcher.ViewMatchers
|
import androidx.test.espresso.matcher.ViewMatchers
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
import com.adevinta.android.barista.interaction.BaristaClickInteractions
|
import com.adevinta.android.barista.interaction.BaristaClickInteractions
|
||||||
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
|
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
|
||||||
import com.adevinta.android.barista.interaction.BaristaClickInteractions.longClickOn
|
import com.adevinta.android.barista.interaction.BaristaClickInteractions.longClickOn
|
||||||
import com.adevinta.android.barista.interaction.BaristaEditTextInteractions.writeTo
|
import com.adevinta.android.barista.interaction.BaristaEditTextInteractions.writeTo
|
||||||
import com.adevinta.android.barista.interaction.BaristaListInteractions.clickListItem
|
|
||||||
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 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.reactions.data.EmojiDataSource
|
||||||
|
import im.vector.app.interactWithSheet
|
||||||
import im.vector.app.waitForView
|
import im.vector.app.waitForView
|
||||||
import java.lang.Thread.sleep
|
import java.lang.Thread.sleep
|
||||||
|
|
||||||
@ -39,7 +42,9 @@ class RoomDetailRobot {
|
|||||||
|
|
||||||
fun postMessage(content: String) {
|
fun postMessage(content: String) {
|
||||||
writeTo(R.id.composerEditText, content)
|
writeTo(R.id.composerEditText, content)
|
||||||
|
waitUntilViewVisible(withId(R.id.sendButton))
|
||||||
clickOn(R.id.sendButton)
|
clickOn(R.id.sendButton)
|
||||||
|
waitUntilViewVisible(withText(content))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun crawl() {
|
fun crawl() {
|
||||||
@ -55,61 +60,54 @@ class RoomDetailRobot {
|
|||||||
pressBack()
|
pressBack()
|
||||||
clickMenu(R.id.search)
|
clickMenu(R.id.search)
|
||||||
pressBack()
|
pressBack()
|
||||||
// Long click on the message
|
|
||||||
longClickOnMessageTest()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun longClickOnMessageTest() {
|
fun crawlMessage(message: String) {
|
||||||
// Test quick reaction
|
// Test quick reaction
|
||||||
longClickOnMessage()
|
val quickReaction = EmojiDataSource.quickEmojis[0] // 👍
|
||||||
waitUntilViewVisible(withId(R.id.bottomSheetRecyclerView))
|
openMessageMenu(message) {
|
||||||
// Add quick reaction
|
addQuickReaction(quickReaction)
|
||||||
clickOn("\uD83D\uDC4D️") // 👍
|
}
|
||||||
waitUntilViewVisible(withId(R.id.composerEditText))
|
|
||||||
|
|
||||||
// Open reactions
|
// Open reactions
|
||||||
longClickOn("\uD83D\uDC4D️") // 👍
|
longClickOn(quickReaction)
|
||||||
// wait for bottom sheet
|
// wait for bottom sheet
|
||||||
pressBack()
|
pressBack()
|
||||||
|
|
||||||
// Test add reaction
|
// Test add reaction
|
||||||
longClickOnMessage()
|
openMessageMenu(message) {
|
||||||
waitUntilViewVisible(withId(R.id.bottomSheetRecyclerView))
|
addReactionFromEmojiPicker()
|
||||||
clickOn(R.string.message_add_reaction)
|
}
|
||||||
// Filter
|
|
||||||
// TODO clickMenu(R.id.search)
|
|
||||||
// Wait for emoji to load, it's async now
|
|
||||||
sleep(2000)
|
|
||||||
clickListItem(R.id.emojiRecyclerView, 4)
|
|
||||||
waitUntilViewVisible(withId(R.id.composerEditText))
|
|
||||||
|
|
||||||
// Test Edit mode
|
// Test Edit mode
|
||||||
longClickOnMessage()
|
openMessageMenu(message) {
|
||||||
waitUntilViewVisible(withId(R.id.bottomSheetRecyclerView))
|
edit()
|
||||||
clickOn(R.string.edit)
|
}
|
||||||
waitUntilViewVisible(withId(R.id.composerEditText))
|
|
||||||
// TODO Cancel action
|
// TODO Cancel action
|
||||||
writeTo(R.id.composerEditText, "Hello universe!")
|
writeTo(R.id.composerEditText, "Hello universe!")
|
||||||
// Wait a bit for the keyboard layout to update
|
// Wait a bit for the keyboard layout to update
|
||||||
sleep(30)
|
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
|
||||||
sleep(1000)
|
waitUntilViewVisible(withText("Hello universe! (edited)"))
|
||||||
// Open edit history
|
// Open edit history
|
||||||
longClickOnMessage("Hello universe! (edited)")
|
openMessageMenu("Hello universe! (edited)") {
|
||||||
waitUntilViewVisible(withId(R.id.bottomSheetRecyclerView))
|
editHistory()
|
||||||
clickOn(R.string.message_view_edit_history)
|
}
|
||||||
pressBack()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun longClickOnMessage(text: String = "Hello world!") {
|
fun openMessageMenu(message: String, block: MessageMenuRobot.() -> Unit) {
|
||||||
Espresso.onView(withId(R.id.timelineRecyclerView))
|
onView(withId(R.id.timelineRecyclerView))
|
||||||
.perform(
|
.perform(
|
||||||
RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
|
RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
|
||||||
ViewMatchers.hasDescendant(ViewMatchers.withText(text)),
|
ViewMatchers.hasDescendant(ViewMatchers.withText(message)),
|
||||||
ViewActions.longClick()
|
ViewActions.longClick()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
interactWithSheet<MessageActionsBottomSheet>(contentMatcher = withId(R.id.bottomSheetRecyclerView)) {
|
||||||
|
val messageMenuRobot = MessageMenuRobot()
|
||||||
|
block(messageMenuRobot)
|
||||||
|
if (!messageMenuRobot.autoClosed) {
|
||||||
|
pressBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openSettings(block: RoomSettingsRobot.() -> Unit) {
|
fun openSettings(block: RoomSettingsRobot.() -> Unit) {
|
||||||
|
@ -17,38 +17,46 @@
|
|||||||
package im.vector.app.ui.robot
|
package im.vector.app.ui.robot
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.test.espresso.Espresso
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.Espresso.pressBack
|
||||||
import androidx.test.espresso.action.ViewActions
|
import androidx.test.espresso.action.ViewActions
|
||||||
import androidx.test.espresso.contrib.RecyclerViewActions
|
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||||
import androidx.test.espresso.matcher.ViewMatchers
|
import androidx.test.espresso.matcher.ViewMatchers
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions
|
import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions
|
||||||
import com.adevinta.android.barista.interaction.BaristaClickInteractions
|
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
|
||||||
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.features.roomdirectory.RoomDirectoryActivity
|
import im.vector.app.features.roomdirectory.RoomDirectoryActivity
|
||||||
|
|
||||||
class RoomListRobot {
|
class RoomListRobot {
|
||||||
|
|
||||||
|
fun openRoom(roomName: String, block: RoomDetailRobot.() -> Unit) {
|
||||||
|
clickOn(roomName)
|
||||||
|
block(RoomDetailRobot())
|
||||||
|
pressBack()
|
||||||
|
}
|
||||||
|
|
||||||
fun verifyCreatedRoom() {
|
fun verifyCreatedRoom() {
|
||||||
Espresso.onView(ViewMatchers.withId(R.id.roomListView))
|
onView(ViewMatchers.withId(R.id.roomListView))
|
||||||
.perform(
|
.perform(
|
||||||
RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
|
RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
|
||||||
ViewMatchers.hasDescendant(ViewMatchers.withText(R.string.room_displayname_empty_room)),
|
ViewMatchers.hasDescendant(withText(R.string.room_displayname_empty_room)),
|
||||||
ViewActions.longClick()
|
ViewActions.longClick()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
Espresso.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun newRoom(block: NewRoomRobot.() -> Unit) {
|
fun newRoom(block: NewRoomRobot.() -> Unit) {
|
||||||
BaristaClickInteractions.clickOn(R.id.createGroupRoomButton)
|
clickOn(R.id.createGroupRoomButton)
|
||||||
waitUntilActivityVisible<RoomDirectoryActivity> {
|
waitUntilActivityVisible<RoomDirectoryActivity> {
|
||||||
BaristaVisibilityAssertions.assertDisplayed(R.id.publicRoomsList)
|
BaristaVisibilityAssertions.assertDisplayed(R.id.publicRoomsList)
|
||||||
}
|
}
|
||||||
val newRoomRobot = NewRoomRobot()
|
val newRoomRobot = NewRoomRobot()
|
||||||
block(newRoomRobot)
|
block(newRoomRobot)
|
||||||
if (!newRoomRobot.createdRoom) {
|
if (!newRoomRobot.createdRoom) {
|
||||||
Espresso.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,11 @@
|
|||||||
package im.vector.app.ui.robot.settings
|
package im.vector.app.ui.robot.settings
|
||||||
|
|
||||||
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 im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.espresso.tools.clickOnPreference
|
import im.vector.app.espresso.tools.clickOnPreference
|
||||||
|
import im.vector.app.espresso.tools.waitUntilViewVisible
|
||||||
|
|
||||||
class SettingsAdvancedRobot {
|
class SettingsAdvancedRobot {
|
||||||
|
|
||||||
@ -28,20 +31,19 @@ class SettingsAdvancedRobot {
|
|||||||
|
|
||||||
clickOnPreference(R.string.settings_push_rules)
|
clickOnPreference(R.string.settings_push_rules)
|
||||||
pressBack()
|
pressBack()
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO P2 test developer screens
|
fun toggleDeveloperMode() {
|
||||||
// Enable developer mode
|
clickOn(R.string.settings_developer_mode_summary)
|
||||||
clickOnSwitchPreference("SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY")
|
}
|
||||||
|
|
||||||
clickOnPreference(R.string.settings_account_data)
|
fun crawlDeveloperOptions() {
|
||||||
clickOn("m.push_rules")
|
clickOnPreference(R.string.settings_account_data)
|
||||||
pressBack()
|
waitUntilViewVisible(withText("m.push_rules"))
|
||||||
pressBack()
|
clickOn("m.push_rules")
|
||||||
clickOnPreference(R.string.settings_key_requests)
|
pressBack()
|
||||||
pressBack()
|
pressBack()
|
||||||
|
clickOnPreference(R.string.settings_key_requests)
|
||||||
// Disable developer mode
|
pressBack()
|
||||||
clickOnSwitchPreference("SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY")
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,12 @@ import im.vector.app.clickOnAndGoBack
|
|||||||
|
|
||||||
class SettingsRobot {
|
class SettingsRobot {
|
||||||
|
|
||||||
|
fun toggleDeveloperMode() {
|
||||||
|
advancedSettings {
|
||||||
|
toggleDeveloperMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun general(block: SettingsGeneralRobot.() -> Unit) {
|
fun general(block: SettingsGeneralRobot.() -> Unit) {
|
||||||
clickOnAndGoBack(R.string.settings_general_title) { block(SettingsGeneralRobot()) }
|
clickOnAndGoBack(R.string.settings_general_title) { block(SettingsGeneralRobot()) }
|
||||||
}
|
}
|
||||||
@ -50,7 +56,9 @@ class SettingsRobot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun advancedSettings(block: SettingsAdvancedRobot.() -> Unit) {
|
fun advancedSettings(block: SettingsAdvancedRobot.() -> Unit) {
|
||||||
clickOnAndGoBack(R.string.settings_advanced_settings) { block(SettingsAdvancedRobot()) }
|
clickOnAndGoBack(R.string.settings_advanced_settings) {
|
||||||
|
block(SettingsAdvancedRobot())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun helpAndAbout(block: SettingsHelpRobot.() -> Unit) {
|
fun helpAndAbout(block: SettingsHelpRobot.() -> Unit) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user