diff --git a/.gitlab-ci-files/job-android.yml b/.gitlab-ci-files/job-android.yml index 164821a2c..b374fd12e 100644 --- a/.gitlab-ci-files/job-android.yml +++ b/.gitlab-ci-files/job-android.yml @@ -21,15 +21,10 @@ job-android: - ./gradlew assembleDebug - ./gradlew assembleRelease - after_script: - - ln -s ./app/build/outputs/apk/debug/linphone-android-debug-*.apk ./apk/debug - - ln -s ./app/build/outputs/apk/release/linphone-android-release-*.apk ./apk/release artifacts: paths: - ./app/build - - ./apk/debug - - ./apk/release when: always expire_in: 2 hour diff --git a/.gitlab-ci-files/job-uitests.yml b/.gitlab-ci-files/job-uitests.yml index 315678017..81449c19e 100644 --- a/.gitlab-ci-files/job-uitests.yml +++ b/.gitlab-ci-files/job-uitests.yml @@ -17,32 +17,38 @@ variables: job-android-uitests: stage: uitests - tags: [ "macmini-m1-bis-xcode13" ] + tags: [ "macos-xcode13" ] dependencies: - job-android before_script: - git submodule update --init --recursive - - ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --install "$android_system_image" > emulatorSystemImageInstallation.log - - echo no | ${ANDROID_HOME}/cmdline-tools/latest/bin/avdmanager --verbose create avd --force --name $emulator_name --package $android_system_image --tag google_$emulator_type --abi $system_architecture --device $emulator_device > emulatorCreation.log + - mkdir logs && mkdir reports && mkdir apks + - ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --install "$android_system_image" > logs/emulatorSystemImageInstallation.log + - echo no | ${ANDROID_HOME}/cmdline-tools/latest/bin/avdmanager --verbose create avd --force --name $emulator_name --package $android_system_image --tag google_$emulator_type --abi $system_architecture --device $emulator_device > logs/emulatorCreation.log - ${ANDROID_HOME}/platform-tools/adb start-server - ${ANDROID_HOME}/emulator/emulator -avd $emulator_name & - .gitlab-ci-files/wait-for-android-emulator + - ${ANDROID_HOME}/platform-tools/adb logcat -d > logs/logcats.log script: - - ./gradlew -Pandroid.testInstrumentationRunnerArguments.class=org.linphone.testsuites.CallTestSuite -PscreportAutoClose=true connectedAndroidTest --continue + - ./gradlew -Pandroid.testInstrumentationRunnerArguments.class=org.linphone.testsuites.CallTestSuite -PscreportAutoClose=true connectedAndroidTest --continue > logs/build.log - ${ANDROID_HOME}/platform-tools/adb -s emulator-5554 emu kill - ${ANDROID_HOME}/platform-tools/adb -s emulator-5554 emu kill - ${ANDROID_HOME}/platform-tools/adb kill-server after_script: - - mkdir results && mv app/build/reports/androidTests/connected/* results - - python3 .gitlab-ci-files/html2xml-report.py -p results + - mv app/build/reports/androidTests/connected/* reports + - mv app/build/outputs/apk/debug/linphone-android-debug-*.apk ./apks/debug + - mv app/build/outputs/apk/release/linphone-android-release-*.apk ./apks/release + - python3 .gitlab-ci-files/html2xml-report.py -p reports artifacts: paths: - - results/* + - reports/* + - logs/* + - apks/* when: always reports: junit: diff --git a/app/src/androidTest/java/org/linphone/call/IncomingCallPushUITests.kt b/app/src/androidTest/java/org/linphone/call/IncomingCallPushUITests.kt index 34fe8479c..750508508 100644 --- a/app/src/androidTest/java/org/linphone/call/IncomingCallPushUITests.kt +++ b/app/src/androidTest/java/org/linphone/call/IncomingCallPushUITests.kt @@ -1,19 +1,24 @@ package org.linphone.call +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.rule.GrantPermissionRule +import java.util.* import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.linphone.R import org.linphone.methods.* import org.linphone.methods.UITestsScreenshots.takeScreenshot +import org.linphone.utils.AppUtils.Companion.getString @RunWith(AndroidJUnit4::class) class IncomingCallPushUITests { - lateinit var methods: CallViewUITestsMethods + val methods = CallViewUITestsMethods @get:Rule val screenshotsRule = ScreenshotsRule(true) @@ -24,7 +29,7 @@ class IncomingCallPushUITests { @Before fun setUp() { UITestsUtils.testAppSetup() - methods = CallViewUITestsMethods() + methods.refreshAccountInfo() takeScreenshot("dialer_view") methods.startIncomingCall() takeScreenshot("dialer_view", "incoming_call_push") @@ -43,29 +48,34 @@ class IncomingCallPushUITests { @Test fun testNoAnswerCallPush() { - methods.noAnswerCallFromPush() + methods.waitForCallNotification(false, 30.0) takeScreenshot("dialer_view") } @Test fun testClickOnCallPush() { - methods.openIncomingCallViewFromPush() + val time = Date().time + methods.onPushAction(getString(R.string.incoming_call_notification_title), UITestsView.incomingCallView) + methods.checkCallTime(onView(withId(R.id.incoming_call_timer)), time) takeScreenshot("incoming_call_view") - methods.endCall() + methods.endCall(UITestsView.incomingCallView) takeScreenshot("dialer_view") } @Test fun testDeclineCallPush() { - methods.declineCallFromPush() + methods.onPushAction("Decline", null) + methods.waitForCallNotification(false, 5.0) takeScreenshot("dialer_view") } @Test fun testAnswerCallPush() { - methods.answerCallFromPush() + val time = Date().time + methods.onPushAction(getString(R.string.incoming_call_notification_answer_action_label), UITestsView.singleCallView) + methods.checkCallTime(onView(withId(R.id.active_call_timer)), time) takeScreenshot("single_call_view") - methods.endCall() + methods.endCall(UITestsView.singleCallView) takeScreenshot("dialer_view") } } diff --git a/app/src/androidTest/java/org/linphone/call/IncomingCallUITests.kt b/app/src/androidTest/java/org/linphone/call/IncomingCallUITests.kt index 3b8b52cc3..55125a648 100644 --- a/app/src/androidTest/java/org/linphone/call/IncomingCallUITests.kt +++ b/app/src/androidTest/java/org/linphone/call/IncomingCallUITests.kt @@ -1,5 +1,8 @@ package org.linphone.call +import androidx.test.espresso.assertion.ViewAssertions.doesNotExist +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.rule.GrantPermissionRule import org.junit.After @@ -7,13 +10,16 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.linphone.R import org.linphone.methods.* import org.linphone.methods.UITestsScreenshots.takeScreenshot +import org.linphone.methods.UITestsUtils.checkWithTimeout +import org.linphone.utils.AppUtils.Companion.getString @RunWith(AndroidJUnit4::class) class IncomingCallUITests { - lateinit var methods: CallViewUITestsMethods + val methods = CallViewUITestsMethods @get:Rule val screenshotsRule = ScreenshotsRule(true) @@ -24,10 +30,10 @@ class IncomingCallUITests { @Before fun setUp() { UITestsUtils.testAppSetup() - methods = CallViewUITestsMethods() + methods.refreshAccountInfo() takeScreenshot("dialer_view") methods.startIncomingCall() - methods.openIncomingCallViewFromPush() + methods.onPushAction(getString(R.string.incoming_call_notification_title), UITestsView.incomingCallView) takeScreenshot("incoming_call_view") } @@ -38,27 +44,27 @@ class IncomingCallUITests { @Test fun testOpenIncomingCallView() { - methods.endCall() + methods.endCall(UITestsView.incomingCallView) takeScreenshot("dialer_view") } @Test fun testNoAnswerIncomingCallView() { - methods.noAnswerCallFromIncomingCall() + UITestsView.incomingCallView.checkWithTimeout(doesNotExist(), 30.0) takeScreenshot("dialer_view") } @Test fun testDeclineIncomingCallView() { - methods.declineCallFromIncomingCallView() + methods.onCallAction(R.id.hangup, UITestsView.incomingCallView, doesNotExist()) takeScreenshot("dialer_view") } @Test fun testAcceptIncomingCallView() { - methods.answerCallFromIncomingCallView() + methods.onCallAction(R.id.answer, UITestsView.singleCallView, matches(isDisplayed())) takeScreenshot("single_call_view") - methods.endCall() + methods.endCall(UITestsView.singleCallView) takeScreenshot("dialer_view") } } diff --git a/app/src/androidTest/java/org/linphone/call/OutgoingCallUITests.kt b/app/src/androidTest/java/org/linphone/call/OutgoingCallUITests.kt index e932c0e75..cdc021414 100644 --- a/app/src/androidTest/java/org/linphone/call/OutgoingCallUITests.kt +++ b/app/src/androidTest/java/org/linphone/call/OutgoingCallUITests.kt @@ -1,7 +1,8 @@ package org.linphone.call -import androidx.test.espresso.Espresso -import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest @@ -14,12 +15,13 @@ import org.junit.runner.RunWith import org.linphone.R import org.linphone.methods.* import org.linphone.methods.UITestsScreenshots.takeScreenshot +import org.linphone.methods.UITestsUtils.checkWithTimeout @RunWith(AndroidJUnit4::class) @LargeTest class OutgoingCallUITests { - lateinit var methods: CallViewUITestsMethods + val methods = CallViewUITestsMethods @get:Rule val screenshotsRule = ScreenshotsRule(true) @@ -30,7 +32,7 @@ class OutgoingCallUITests { @Before fun setUp() { UITestsUtils.testAppSetup() - methods = CallViewUITestsMethods() + methods.refreshAccountInfo() takeScreenshot("dialer_view") methods.startOutgoingCall() takeScreenshot("outgoing_call_view") @@ -43,21 +45,21 @@ class OutgoingCallUITests { @Test fun testViewDisplay() { - methods.endCall() + methods.endCall(UITestsView.outgoingCallView) takeScreenshot("dialer_view", "declined") } @Test fun testNoAnswer() { - methods.noAnswerCallFromOutgoingCall() + UITestsView.outgoingCallView.checkWithTimeout(doesNotExist(), 30.0) takeScreenshot("dialer_view", "no_answer") } @Test fun testToggleMute() { - Espresso.onView(withId(R.id.microphone)).perform(ViewActions.click()) + onView(withId(R.id.microphone)).perform(click()) takeScreenshot("outgoing_call_view", "mute") - Espresso.onView(withId(R.id.microphone)).perform(ViewActions.click()) + onView(withId(R.id.microphone)).perform(click()) takeScreenshot("outgoing_call_view") methods.endCall() takeScreenshot("dialer_view", "declined") @@ -65,9 +67,9 @@ class OutgoingCallUITests { @Test fun testToggleSpeaker() { - Espresso.onView(withId(R.id.speaker)).perform(ViewActions.click()) + onView(withId(R.id.speaker)).perform(click()) takeScreenshot("outgoing_call_view", "speaker") - Espresso.onView(withId(R.id.speaker)).perform(ViewActions.click()) + onView(withId(R.id.speaker)).perform(click()) takeScreenshot("outgoing_call_view") methods.endCall() takeScreenshot("dialer_view", "declined") @@ -75,7 +77,7 @@ class OutgoingCallUITests { @Test fun testCancel() { - methods.cancelCallFromOutgoingCallView() + methods.onCallAction(R.id.hangup, UITestsView.outgoingCallView, doesNotExist()) takeScreenshot("dialer_view") } } diff --git a/app/src/androidTest/java/org/linphone/methods/CallViewUITestsMethods.kt b/app/src/androidTest/java/org/linphone/methods/CallViewUITestsMethods.kt index 9c973a779..bc86d383f 100644 --- a/app/src/androidTest/java/org/linphone/methods/CallViewUITestsMethods.kt +++ b/app/src/androidTest/java/org/linphone/methods/CallViewUITestsMethods.kt @@ -5,6 +5,7 @@ import android.app.NotificationManager import android.content.Context import android.widget.Chronometer import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.ViewAssertion import androidx.test.espresso.ViewInteraction import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.typeText @@ -13,6 +14,7 @@ import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.* import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.* +import java.util.* import kotlinx.coroutines.* import org.linphone.R import org.linphone.core.AuthInfo @@ -21,12 +23,17 @@ import org.linphone.methods.UITestsUtils.activityScenario import org.linphone.methods.UITestsUtils.checkWithTimeout import org.linphone.utils.AppUtils.Companion.getString -class CallViewUITestsMethods { +object CallViewUITestsMethods { val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) val manager = UITestsCoreManager.instance - val appAccountAuthInfo: AuthInfo = UITestsCoreManager.instance.appAccountAuthInfo - val ghostAccount: UITestsRegisteredLinphoneCore = UITestsCoreManager.instance.ghostAccounts[0] + var appAccountAuthInfo: AuthInfo = UITestsCoreManager.instance.appAccountAuthInfo + var ghostAccount: UITestsRegisteredLinphoneCore = UITestsCoreManager.instance.ghostAccounts[0] + + fun refreshAccountInfo() { + appAccountAuthInfo = UITestsCoreManager.instance.appAccountAuthInfo + ghostAccount = UITestsCoreManager.instance.ghostAccounts[0] + } fun startIncomingCall() { if (ghostAccount.callState != Call.State.Released) { ghostAccount.terminateCall() } @@ -47,17 +54,18 @@ class CallViewUITestsMethods { checkCallTime(onView(withId(R.id.outgoing_call_timer))) } - fun endCall() { + fun endCall(currentView: ViewInteraction? = null) { if (ghostAccount.callState == Call.State.Released) { return } ghostAccount.terminateCall() ghostAccount.waitForCallState(Call.State.Released, 5.0) - onView(withId(R.id.outgoing_call_layout)).checkWithTimeout(doesNotExist(), 5.0) + currentView?.checkWithTimeout(doesNotExist(), 5.0) waitForCallNotification(false, 5.0) } - fun checkCallTime(view: ViewInteraction) = runBlocking { + fun checkCallTime(view: ViewInteraction, launchTime: Long = Date().time) = runBlocking { view.checkWithTimeout(matches(isDisplayed()), 2.0) + val firstValue = ((Date().time - launchTime) / 1000).toInt() + 1 launch(Dispatchers.Default) { val timerArray = arrayListOf() repeat(3) { @@ -69,76 +77,32 @@ class CallViewUITestsMethods { } 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()}))" } + assert(timerArray.first() <= firstValue + 3) { "[UITests] Call Time is not correctly initialized, it is at ${timerArray.first()}, $firstValue seconds after the start)" } } } - fun noAnswerCallFromPush() { - waitForCallNotification(false, 30.0) - } - - fun declineCallFromPush() { - val declineLabel = "Decline" // getString(R.string.incoming_call_notification_hangup_action_label) - + fun onPushAction(label: String, resultingView: ViewInteraction?, timeout: Double = 5.0) { try { - val decline = device.findObject(By.textContains(declineLabel)) - decline.click() + val button = device.findObject(By.textContains(label)) + button.click() } catch (e: java.lang.NullPointerException) { - throw AssertionError("[UITests] Enable to find the \"$declineLabel\" button in the incoming call notification") + throw AssertionError("[UITests] Enable to find the \"$label\" button in the incoming call notification") } - waitForCallNotification(false, 5.0) + resultingView?.checkWithTimeout(matches(isDisplayed()), timeout) } - fun answerCallFromPush() { - val answerLabel = getString(R.string.incoming_call_notification_answer_action_label) - try { - val answer = device.findObject(By.textContains(answerLabel)) - answer.click() - } 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) + fun onCallAction( + id: Int, + resultingView: ViewInteraction?, + assertion: ViewAssertion, + timeout: Double = 5.0 + ) { + onView(withId(id)).checkWithTimeout(matches(isDisplayed()), timeout) + onView(withId(id)).perform(click()) + resultingView?.checkWithTimeout(assertion, 5.0) } - fun openIncomingCallViewFromPush() { - try { - val notif = device.findObject(By.textContains(getString(R.string.incoming_call_notification_title))) - notif.click() - } catch (e: java.lang.NullPointerException) { - 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.incoming_call_timer))) - } - - fun declineCallFromIncomingCallView() { - onView(withId(R.id.hangup)).checkWithTimeout(matches(isDisplayed()), 5.0) - onView(withId(R.id.hangup)).perform(click()) - onView(withId(R.id.incoming_call_layout)).checkWithTimeout(doesNotExist(), 5.0) - } - - fun answerCallFromIncomingCallView() { - onView(withId(R.id.answer)).checkWithTimeout(matches(isDisplayed()), 5.0) - onView(withId(R.id.answer)).perform(click()) - onView(withId(R.id.single_call_layout)).checkWithTimeout(matches(isDisplayed()), 5.0) - } - - fun cancelCallFromOutgoingCallView() { - onView(withId(R.id.hangup)).checkWithTimeout(matches(isDisplayed()), 5.0) - onView(withId(R.id.hangup)).perform(click()) - onView(withId(R.id.outgoing_call_layout)).checkWithTimeout(doesNotExist(), 5.0) - } - - fun noAnswerCallFromIncomingCall() { - onView(withId(R.id.incoming_call_layout)).checkWithTimeout(doesNotExist(), 30.0) - } - - fun noAnswerCallFromOutgoingCall() { - onView(withId(R.id.outgoing_call_layout)).checkWithTimeout(doesNotExist(), 30.0) - } - - private fun waitForCallNotification(exist: Boolean, timeout: Double) = runBlocking { + fun waitForCallNotification(exist: Boolean, timeout: Double) = runBlocking { var result = !exist val wait = launch(Dispatchers.Default) { lateinit var activity: Activity @@ -153,7 +117,7 @@ class CallViewUITestsMethods { result = false } if (manager.activeNotifications.isEmpty()) result = false - if (result == exist) { cancel() } + if (result == exist) cancel() delay(100) } } diff --git a/app/src/androidTest/java/org/linphone/methods/UITestsUtils.kt b/app/src/androidTest/java/org/linphone/methods/UITestsUtils.kt index dccf956c2..c50b3b461 100644 --- a/app/src/androidTest/java/org/linphone/methods/UITestsUtils.kt +++ b/app/src/androidTest/java/org/linphone/methods/UITestsUtils.kt @@ -54,6 +54,13 @@ object LinphonePermissions { ).toTypedArray() } +object UITestsView { + val dialerView = onView(withId(R.id.incoming_call_layout)) + val incomingCallView = onView(withId(R.id.incoming_call_layout)) + val outgoingCallView = onView(withId(R.id.outgoing_call_layout)) + val singleCallView = onView(withId(R.id.single_call_layout)) +} + object UITestsUtils { private var mainActivityIntent = Intent(getApplicationContext(), MainActivity::class.java) diff --git a/app/src/androidTest/java/org/linphone/screenshots/1080x2400/data/dialer_view.incoming_call_push.pkl b/app/src/androidTest/java/org/linphone/screenshots/1080x2400/data/dialer_view.incoming_call_push.pkl index 945abddc2..50d6d02b0 100644 Binary files a/app/src/androidTest/java/org/linphone/screenshots/1080x2400/data/dialer_view.incoming_call_push.pkl and b/app/src/androidTest/java/org/linphone/screenshots/1080x2400/data/dialer_view.incoming_call_push.pkl differ