Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class DirectCallPage {

companion object {
val participantName = By.res("Stream_DirectCallUserName")
val joinAndRingCheckbox = By.res("Stream_JoinAndRingCheckbox")
val audioCallButton = By.res("Stream_AudioCallButton")
val videoCallButton = By.res("Stream_VideoCallButton")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,14 @@ class UserRobot {
return this
}

fun directCall(audioOnly: Boolean): UserRobot {
fun directCall(audioOnly: Boolean, joinAndRing: Boolean = false): UserRobot {
Comment thread
aleksandar-apostolov marked this conversation as resolved.
Resolved
CallDetailsPage.wheelIcon.waitToAppearAndClick()
CallDetailsPage.directCallButton.waitToAppearAndClick()
DirectCallPage.joinAndRingCheckbox.waitToAppear().let { checkbox ->
if (checkbox.isChecked != joinAndRing) {
checkbox.click()
}
}
DirectCallPage.participantName.waitToAppearAndClick()
val callButton = if (audioOnly) audioCallButton else videoCallButton
callButton.waitToAppearAndClick()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,20 @@ fun UserRobot.assertOutgoingCall(audioOnly: Boolean = true, isDisplayed: Boolean
// muted state right after the screen renders), so poll instead of instant asserts.
assertTrue("Call label", RingPage.outgoingCallLabel.waitDisplayed())
assertTrue("Avatar", RingPage.callParticipantAvatar.waitDisplayed())
assertTrue("Microphone", RingPage.microphoneEnabledToggle.waitDisplayed())
assertTrue(
"Microphone",
RingPage.microphoneEnabledToggle.waitDisplayed() ||
RingPage.microphoneDisabledToggle.waitDisplayed(),
)
if (audioOnly) {
assertFalse("Camera enabled toggle", RingPage.cameraEnabledToggle.isDisplayed())
assertFalse("Camera disabled toggle", RingPage.cameraDisabledToggle.isDisplayed())
} else {
assertTrue("Camera", RingPage.cameraEnabledToggle.waitDisplayed())
assertTrue(
"Camera",
RingPage.cameraEnabledToggle.waitDisplayed() ||
RingPage.cameraDisabledToggle.waitDisplayed(),
)
}
} else {
assertFalse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

package io.getstream.video.android.tests

import io.getstream.video.android.core.Call
import io.getstream.video.android.core.RealtimeConnection
import io.getstream.video.android.core.RingingState
import io.getstream.video.android.core.StreamVideo
import io.getstream.video.android.robots.UserControls.DISABLE
import io.getstream.video.android.robots.assertAudioCallControls
import io.getstream.video.android.robots.assertConnectingView
Expand All @@ -24,12 +28,114 @@ import io.getstream.video.android.robots.assertOutgoingCall
import io.getstream.video.android.robots.assertOutgoingCallNotification
import io.getstream.video.android.robots.assertThatCallIsEnded
import io.getstream.video.android.robots.assertVideoCallControls
import io.getstream.video.android.uiautomator.seconds
import io.qameta.allure.kotlin.Allure.step
import io.qameta.allure.kotlin.AllureId
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.withTimeoutOrNull
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test

class RingingTests : StreamTestCase() {

@Test
fun testJoinAndRingRemainsOutgoingAfterFastReconnectBeforeCalleeAccepts() {
startUnansweredJoinAndRingCall()

step("WHEN the caller completes a fast reconnect before the callee accepts") {
reconnectOutgoingCall(expectSessionReplacement = false) {
fastReconnect("E2E: outgoing join-and-ring")
}
}
step("THEN the outgoing call screen is still displayed") {
userRobot.assertOutgoingCall(audioOnly = false, isDisplayed = true)
}

userRobot.declineOutgoingCall()
Comment thread
aleksandar-apostolov marked this conversation as resolved.
Resolved
}

@Test
fun testJoinAndRingRemainsOutgoingAfterRejoinBeforeCalleeAccepts() {
startUnansweredJoinAndRingCall()

step("WHEN the caller completes a rejoin before the callee accepts") {
reconnectOutgoingCall(expectSessionReplacement = true) {
rejoin("E2E: outgoing join-and-ring")
}
}
step("THEN the outgoing call screen is still displayed") {
userRobot.assertOutgoingCall(audioOnly = false, isDisplayed = true)
}

userRobot.declineOutgoingCall()
}

private fun startUnansweredJoinAndRingCall() {
step("GIVEN the caller joins first and rings a callee who has not accepted") {
// Do not start the participant robot: the selected callee remains unanswered.
userRobot
.logout()
.loginAsRandomUser()
.directCall(audioOnly = false, joinAndRing = true)
.assertOutgoingCall(audioOnly = false, isDisplayed = true)
}
}

private fun reconnectOutgoingCall(
expectSessionReplacement: Boolean,
reconnect: suspend Call.() -> Unit,
) {
val call = requireNotNull(StreamVideo.instance().state.ringingCall.value) {
"Expected the join-and-ring call to be registered as the ringing call"
}
assertTrue(call.state.ringingState.value is RingingState.Outgoing)
assertEquals(RealtimeConnection.Connected, call.state.connection.value)
val sessionIdBeforeReconnect = call.sessionId

runBlocking {
// Subscribe before triggering the reconnect so a fast Reconnecting transition
// cannot be missed by a polling assertion.
val enteredReconnecting = async(start = CoroutineStart.UNDISPATCHED) {
call.state.connection.first { it is RealtimeConnection.Reconnecting }
}
val reconnectCompleted = async {
reconnect(call)
}

val reconnectingState = withTimeoutOrNull(30.seconds) {
enteredReconnecting.await()
}
assertNotNull(
"Expected the connection to enter Reconnecting within 30 seconds, " +
"but its current state is ${call.state.connection.value}",
reconnectingState,
)
withTimeout(60.seconds) { reconnectCompleted.await() }
val connectedState = withTimeoutOrNull(30.seconds) {
call.state.connection.first { it is RealtimeConnection.Connected }
}
assertNotNull(
"Expected the connection to recover to Connected within 30 seconds, " +
"but its current state is ${call.state.connection.value}",
connectedState,
)
}

if (expectSessionReplacement) {
assertNotEquals(sessionIdBeforeReconnect, call.sessionId)
} else {
assertEquals(sessionIdBeforeReconnect, call.sessionId)
}
assertTrue(call.state.ringingState.value is RingingState.Outgoing)
}

@AllureId("7774")
@Test
fun testParticipantRejectsTheOutgoingCall() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,9 @@ private fun Body(
Text(stringResource(id = R.string.join_first), color = Color.White)
Checkbox(
callerJoinsFirst,
modifier = Modifier.offset(x = 10.dp),
modifier = Modifier
.offset(x = 10.dp)
.testTag("Stream_JoinAndRingCheckbox"),
colors = CheckboxDefaults.colors(
uncheckedColor = Color.White, // Border color when unchecked
checkedColor = Color.White, // Fill color when checked
Expand Down
Loading