add screen comparison for incoming call uitests

This commit is contained in:
Quentin Monnier 2022-12-07 08:47:01 -05:00 committed by Sylvain Berfini
parent 33627faca8
commit 945d444c47
20 changed files with 166 additions and 90 deletions

View file

@ -326,8 +326,7 @@ def embedScreenshotsTask = task('embedScreenshots', group: 'reporting') {
def name = failedTestCaseDescription.substring(pt1+1, pt2)
def pt3 = failedTestCaseDescription.indexOf(".", pt2+1)
if (pt3 != -1) {
def pt4 = failedTestCaseDescription.indexOf(".", pt3+1)
name += " (${failedTestCaseDescription.substring(pt3+1,pt4)})"
name += " (${failedTestCaseDescription.substring(pt2+1,pt3)})"
}
def failedTestClassJunitReportFile = new File(reportsDirectory, "${failedTestClassName}.html")

View file

@ -0,0 +1,71 @@
package org.linphone.call
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.GrantPermissionRule
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.linphone.methods.*
import org.linphone.methods.UITestsScreenshots.takeScreenshot
@RunWith(AndroidJUnit4::class)
class IncomingCallPushUITests {
lateinit var methods: CallViewUITestsMethods
@get:Rule
val screenshotsRule = ScreenshotsRule(true)
@get:Rule
var mGrantPermissionRule = GrantPermissionRule.grant(*LinphonePermissions.CALL)
@Before
fun setUp() {
UITestsUtils.testAppSetup()
methods = CallViewUITestsMethods()
takeScreenshot("dialer_view")
methods.startIncomingCall()
takeScreenshot("dialer_view", "incoming_call_push")
}
@After
fun tearDown() {
methods.endCall()
}
@Test
fun testDisplayCallPush() {
methods.endCall()
takeScreenshot("dialer_view")
}
@Test
fun testNoAnswerCallPush() {
methods.noAnswerCallFromPush()
takeScreenshot("dialer_view")
}
@Test
fun testClickOnCallPush() {
methods.openIncomingCallViewFromPush()
takeScreenshot("incoming_call_view")
methods.endCall()
takeScreenshot("dialer_view")
}
@Test
fun testDeclineCallPush() {
methods.declineCallFromPush()
takeScreenshot("dialer_view")
}
@Test
fun testAnswerCallPush() {
methods.answerCallFromPush()
takeScreenshot("single_call_view")
methods.endCall()
takeScreenshot("dialer_view")
}
}

View file

@ -1,77 +1,64 @@
package org.linphone.call
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.GrantPermissionRule
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.linphone.methods.CallViewUITestsMethods
import org.linphone.methods.UITestsUtils
import org.linphone.methods.*
import org.linphone.methods.UITestsScreenshots.takeScreenshot
@RunWith(AndroidJUnit4::class)
class IncomingCallUITests {
lateinit var methods: CallViewUITestsMethods
@get:Rule
val screenshotsRule = ScreenshotsRule(true)
@get:Rule
var mGrantPermissionRule = GrantPermissionRule.grant(*LinphonePermissions.CALL)
@Before
fun setUp() {
UITestsUtils.testAppSetup()
methods = CallViewUITestsMethods()
}
// notification tests
@Test
fun testDisplayCallPush() {
methods.startIncomingCall()
methods.endCall()
}
@Test
fun testNoAnswerCallPush() {
methods.startIncomingCall()
methods.noAnswerCallFromPush()
}
@Test
fun testDeclineCallPush() {
methods.startIncomingCall()
methods.declineCallFromPush()
}
@Test
fun testAnswerCallPush() {
methods.startIncomingCall()
methods.answerCallFromPush()
methods.endCall()
}
// incoming call view tests
@Test
fun testOpenIncomingCallView() {
takeScreenshot("dialer_view")
methods.startIncomingCall()
methods.openIncomingCallViewFromPush()
takeScreenshot("incoming_call_view")
}
@After
fun tearDown() {
methods.endCall()
}
@Test
fun testOpenIncomingCallView() {
methods.endCall()
takeScreenshot("dialer_view")
}
@Test
fun testNoAnswerIncomingCallView() {
methods.startIncomingCall()
methods.openIncomingCallViewFromPush()
methods.noAnswerCallFromIncomingCall()
takeScreenshot("dialer_view")
}
@Test
fun testDeclineIncomingCallView() {
methods.startIncomingCall()
methods.openIncomingCallViewFromPush()
methods.declineCallFromIncomingCallView()
methods.endCall()
takeScreenshot("dialer_view")
}
@Test
fun testAcceptIncomingCallView() {
methods.startIncomingCall()
methods.openIncomingCallViewFromPush()
methods.answerCallFromIncomingCallView()
takeScreenshot("single_call_view")
methods.endCall()
takeScreenshot("dialer_view")
}
}

View file

@ -25,14 +25,7 @@ class OutgoingCallUITests {
val screenshotsRule = ScreenshotsRule(true)
@get:Rule
var mGrantPermissionRule = GrantPermissionRule.grant(
"android.permission.READ_PHONE_NUMBERS",
"android.permission.MANAGE_OWN_CALLS",
"android.permission.POST_NOTIFICATIONS",
"android.permission.READ_PHONE_STATE",
"android.permission.BLUETOOTH_CONNECT",
"android.permission.RECORD_AUDIO"
)
var mGrantPermissionRule = GrantPermissionRule.grant(*LinphonePermissions.CALL)
@Before
fun setUp() {

View file

@ -3,7 +3,9 @@ package org.linphone.methods
import android.app.Activity
import android.app.NotificationManager
import android.content.Context
import android.widget.Chronometer
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
@ -42,6 +44,7 @@ class CallViewUITestsMethods {
onView(withContentDescription(R.string.content_description_start_call)).perform(click())
onView(withId(R.id.outgoing_call_layout)).checkWithTimeout(matches(isDisplayed()), 5.0)
checkCallTime(onView(withId(R.id.outgoing_call_timer)))
}
fun endCall() {
@ -50,7 +53,24 @@ class CallViewUITestsMethods {
ghostAccount.terminateCall()
ghostAccount.waitForCallState(Call.State.Released, 5.0)
onView(withId(R.id.outgoing_call_layout)).checkWithTimeout(doesNotExist(), 5.0)
// onView(withId(com.google.android.material.R.id.snackbar_text)).checkWithTimeout(doesNotExist(), 5.0)
waitForCallNotification(false, 5.0)
}
fun checkCallTime(view: ViewInteraction) = runBlocking {
view.checkWithTimeout(matches(isDisplayed()), 2.0)
launch(Dispatchers.Default) {
val timerArray = arrayListOf<Int>()
repeat(3) {
view.check { view, _ ->
val value = (view as Chronometer).text.toString()
timerArray.add((value.split(":").last()).toInt())
}
delay(1000)
}
assert(timerArray.distinct().size >= 2) { "[UITests] Call Time is not correctly incremented, less than 2 differents values are displayed in 3 seconds" }
assert(timerArray == timerArray.sorted()) { "[UITests] Call Time is not correctly incremented, it is not increasing" }
assert(timerArray.first() <= 4) { "[UITests] Call Time is not correctly initialized, it is more than 4 right after the start (found: ${timerArray.first()}))" }
}
}
fun noAnswerCallFromPush() {
@ -58,7 +78,7 @@ class CallViewUITestsMethods {
}
fun declineCallFromPush() {
val declineLabel = getString(R.string.incoming_call_notification_answer_action_label)
val declineLabel = "Decline" // getString(R.string.incoming_call_notification_hangup_action_label)
try {
val decline = device.findObject(By.textContains(declineLabel))
@ -66,6 +86,7 @@ class CallViewUITestsMethods {
} catch (e: java.lang.NullPointerException) {
throw AssertionError("[UITests] Enable to find the \"$declineLabel\" button in the incoming call notification")
}
waitForCallNotification(false, 5.0)
}
fun answerCallFromPush() {
@ -76,6 +97,7 @@ class CallViewUITestsMethods {
} catch (e: java.lang.NullPointerException) {
throw AssertionError("[UITests] Enable to find the \"$answerLabel\" button in the incoming call notification")
}
waitForCallNotification(false, 5.0)
onView(withId(R.id.single_call_layout)).checkWithTimeout(matches(isDisplayed()), 5.0)
}
@ -87,6 +109,7 @@ class CallViewUITestsMethods {
throw AssertionError("[UITests] Enable to find the incoming call notification")
}
onView(withId(R.id.incoming_call_layout)).checkWithTimeout(matches(isDisplayed()), 5.0)
checkCallTime(onView(withId(R.id.outgoing_call_timer)))
}
fun declineCallFromIncomingCallView() {
@ -113,28 +136,15 @@ class CallViewUITestsMethods {
fun noAnswerCallFromOutgoingCall() {
onView(withId(R.id.outgoing_call_layout)).checkWithTimeout(doesNotExist(), 30.0)
/*
val snackbar = onView(withId(com.google.android.material.R.id.snackbar_text))
snackbar.checkWithTimeout(
matches(
allOf(
withText(containsString("Error")),
withText(containsString("Request Timeout"))
)
),
5.0
)
snackbar.checkWithTimeout(doesNotExist(), 5.0)
*/
}
private fun waitForCallNotification(exist: Boolean, timeout: Double) = runBlocking {
var result = false
var result = !exist
val wait = launch(Dispatchers.Default) {
lateinit var activity: Activity
activityScenario!!.onActivity { act -> activity = act }
val manager = activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
repeat(timeout.toInt() * 10) {
repeat((timeout * 10).toInt()) {
for (notif in manager.activeNotifications) {
if (notif.notification.channelId == getString(R.string.notification_channel_incoming_call_id)) {
result = true
@ -142,12 +152,13 @@ class CallViewUITestsMethods {
}
result = false
}
if (manager.activeNotifications.isEmpty()) result = false
if (result == exist) { cancel() }
delay(100)
}
}
wait.join()
delay(500)
delay(1000)
assert(result == exist) { "[UITests] Incoming call Notification still ${if (exist) "not " else ""}displayed after $timeout seconds" }
}
}

View file

@ -309,33 +309,25 @@ class UITestsRegisteredLinphoneCore(authInfo: AuthInfo) {
assert(result) { "[UITests] $registrationState registration state still not verified after $timeout seconds" }
}
fun waitForCallState(callState: Call.State, timeout: Double) = runBlocking {
fun waitForCallState(call_state: Call.State, timeout: Double) = runBlocking {
var result = false
val wait = launch { delay(timeout.toLong() * 1000) }
val listener = object : CoreListenerStub() {
override fun onCallStateChanged(
core: Core,
call: Call,
state: Call.State?,
message: String
) {
super.onCallStateChanged(core, call, state, message)
if (callState == state) {
val wait = launch(Dispatchers.Default) {
repeat((timeout * 10).toInt()) {
if (call_state == callState) {
result = true
wait.cancel()
cancel()
}
delay(100)
}
}
mCore.addListener(listener)
wait.join()
mCore.removeListener(listener)
assert(result) { "[UITests] $callState call state still not verified after $timeout seconds" }
assert(result) { "[UITests] $call_state call state still not verified after $timeout seconds (last known state: $callState)" }
}
fun waitForRecordingState(recording: Boolean, onRemote: Boolean = false, timeout: Double) = runBlocking {
var result = false
val wait = launch(Dispatchers.Default) {
repeat(timeout.toInt() * 10) { i ->
repeat((timeout * 10).toInt()) {
if (!onRemote && recording == mCore.currentCall?.params?.isRecording) {
result = true
cancel()

View file

@ -2,10 +2,13 @@ package org.linphone.methods
import android.os.Environment.DIRECTORY_PICTURES
import android.os.Environment.getExternalStoragePublicDirectory
import android.view.View
import androidx.test.runner.screenshot.BasicScreenCaptureProcessor
import androidx.test.runner.screenshot.Screenshot
import java.io.File
import java.io.IOException
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.linphone.core.tools.Log
object UITestsScreenshots {
@ -23,9 +26,9 @@ object UITestsScreenshots {
)
}
private fun screenshot(screenShotName: String) {
private fun screenshot(screenShotName: String, view: View? = null) {
Log.i("[UITests] Taking screenshot of '$screenShotName'")
val screenCapture = Screenshot.capture()
val screenCapture = if (view == null) Screenshot.capture() else Screenshot.capture(view)
val processors = setOf(MyScreenCaptureProcessor())
try {
screenCapture.apply {
@ -41,13 +44,16 @@ object UITestsScreenshots {
fun takeScreenshot(
name: String,
variant: String? = null,
delay: Double = 0.5,
view: View? = null,
line: Int = Throwable().stackTrace[1].lineNumber
) {
if (!screenshotComparison) return
if (name.contains(".") || variant?.contains(".") == true) {
throw Exception("[UITests] \".\" character is forbidden for takeScreenshot methods arguments name and variant")
}
screenshot(line.toString() + ".$name" + if (variant != null) ".$variant" else "")
runBlocking { delay((delay * 1000).toLong()) }
screenshot(line.toString() + ".$name" + if (variant != null) ".$variant" else "", view)
}
}

View file

@ -39,6 +39,21 @@ class ScreenshotsRule(active: Boolean) : TestWatcher() {
}
}
object LinphonePermissions {
val LAUNCH = arrayListOf(
"android.permission.READ_PHONE_NUMBERS",
"android.permission.MANAGE_OWN_CALLS",
"android.permission.POST_NOTIFICATIONS",
"android.permission.READ_PHONE_STATE"
).toTypedArray()
val CALL = LAUNCH + arrayListOf(
"android.permission.BLUETOOTH_CONNECT",
"android.permission.RECORD_AUDIO"
).toTypedArray()
}
object UITestsUtils {
private var mainActivityIntent = Intent(getApplicationContext(), MainActivity::class.java)

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

View file

@ -2,12 +2,14 @@ package org.linphone.testsuites
import org.junit.runner.RunWith
import org.junit.runners.Suite
import org.linphone.call.IncomingCallPushUITests
import org.linphone.call.IncomingCallUITests
import org.linphone.call.OutgoingCallUITests
@RunWith(Suite::class)
@Suite.SuiteClasses(
OutgoingCallUITests::class,
IncomingCallUITests::class
IncomingCallPushUITests::class,
IncomingCallUITests::class,
OutgoingCallUITests::class
)
class CallTestSuite

@ -1 +1 @@
Subproject commit 1d7df53aece8df01de28eb680d03d6ffab37fce0
Subproject commit edf429c2d535a64e9b31bbfad7411190c0669086