Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Leave call immediately without a promise #1163

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
150 changes: 150 additions & 0 deletions internal/e2e-js/SDKReporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import type {
Reporter,
FullConfig,
Suite,
TestCase,
TestResult,
FullResult,
TestError,
TestStep,
} from '@playwright/test/reporter'

/**
* A custom SDK reporter that implements Playwright Reporter interface methods.
*/
export default class SDKReporter implements Reporter {
private totalTests = 0
private completedTests = 0
private failedTests = 0
private passedTests = 0
private skippedTests = 0
private timedOutTests = 0

/**
* Called once before running tests.
*/
onBegin(_config: FullConfig, suite: Suite): void {
this.totalTests = suite.allTests().length
console.log('============================================')
console.log(`Starting the run with ${this.totalTests} tests...`)
console.log('============================================')
}

/**
* Called after all tests have run, or the run was interrupted.
*/
async onEnd(result: FullResult): Promise<void> {
console.log('\n\n')
console.log('============================================')
console.log(`Test run finished with status: ${result.status.toUpperCase()}`)
console.log('--------------------------------------------')
console.log(`Total Tests: ${this.totalTests}`)
console.log(`Passed: ${this.passedTests}`)
console.log(`Failed: ${this.failedTests}`)
console.log(`Skipped: ${this.skippedTests}`)
console.log(`Timed Out: ${this.timedOutTests}`)
console.log('============================================')
console.log('\n\n')
}

/**
* Called on a global error, for example an unhandled exception in the test.
*/
onError(error: TestError): void {
console.log('============================================')
console.log(`Global Error: ${error.message}`)
console.log(error)
console.log('============================================')
}

/**
* Called immediately before the test runner exits, after onEnd() and all
* reporters have finished.
* If required: upload logs to a server here.
*/
async onExit(): Promise<void> {
console.log('[SDKReporter] Exit')
}

/**
* Called when a test step (i.e., `test.step(...)`) begins in the worker.
*/
onStepBegin(_test: TestCase, _result: TestResult, step: TestStep): void {
/**
* Playwright creates some internal steps as well.
* We do not care about those steps.
* We only log our own custom test steps.
*/
if (step.category === 'test.step') {
console.log(`--- STEP BEGIN: "${step.title}"`)
}
}

/**
* Called when a test step finishes.
*/
onStepEnd(_test: TestCase, _result: TestResult, step: TestStep): void {
if (step.category === 'test.step') {
if (step.error) {
console.log(`--- STEP FAILED: "${step.title}"`)
console.log(step.error)
} else {
console.log(`--- STEP FINISHED: "${step.title}"`)
}
}
}

/**
* Called when a test begins in the worker process.
*/
onTestBegin(test: TestCase, _result: TestResult): void {
console.log('--------------------------------------------')
console.log(`⏯️ Test Started: ${test.title}`)
console.log('--------------------------------------------')
}

/**
* Called when a test ends (pass, fail, timeout, etc.).
*/
onTestEnd(test: TestCase, result: TestResult): void {
console.log('--------------------------------------------')
this.completedTests += 1
switch (result.status) {
case 'passed':
this.passedTests += 1
console.log(`✅ Test Passed: ${test.title}`)
break
case 'failed':
this.failedTests += 1
console.log(`❌ Test Failed: ${test.title}`)
if (result.error) {
console.log(`📧 Error: ${result.error.message}`)
if (result.error.stack) {
console.log(`📚 Stack: ${result.error.stack}`)
}
}
break
case 'timedOut':
this.timedOutTests += 1
console.log(`⏰ Test Timed Out: ${test.title}`)
break
case 'skipped':
this.skippedTests += 1
console.log(`↩️ Test Skipped: ${test.title}`)
break
default:
console.log(`Test Ended with status "${result.status}": ${test.title}`)
break
}
console.log('--------------------------------------------')
console.log('\n\n')
}

/**
* Indicates this reporter does not handle stdout and stderr printing.
* So that Playwright print those logs.
*/
printsToStdio(): boolean {
return false
}
}
8 changes: 8 additions & 0 deletions internal/e2e-js/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ const test = baseTest.extend<CustomFixture>({
try {
await use(maker)
} finally {
console.log('====================================')
console.log('Cleaning up pages..')
console.log('====================================')

/**
* If we have a __roomObj in the page means we tested the Video/Fabric APIs
* so we must leave the room.
Expand All @@ -75,6 +78,8 @@ const test = baseTest.extend<CustomFixture>({
* Make sure we cleanup the client as well.
*/
await Promise.all(context.pages().map(disconnectClient))

await context.close()
}
},
createCustomVanillaPage: async ({ context }, use) => {
Expand Down Expand Up @@ -112,7 +117,10 @@ const test = baseTest.extend<CustomFixture>({
try {
await use(resource)
} finally {
console.log('====================================')
console.log('Cleaning up resources..')
console.log('====================================')

// Clean up resources after use
const deleteResources = resources.map(async (resource) => {
try {
Expand Down
5 changes: 3 additions & 2 deletions internal/e2e-js/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,19 @@ const useDesktopChrome = {

const config: PlaywrightTestConfig = {
testDir: 'tests',
reporter: process.env.CI ? 'github' : 'list',
reporter: [[process.env.CI ? 'github' : 'list'], ['./SDKReporter.ts']],
globalSetup: require.resolve('./global-setup'),
testMatch: undefined,
testIgnore: undefined,
timeout: 120_000,
workers: 1,
maxFailures: 1,
expect: {
// Default is 5000
timeout: 10_000,
},
// Forbid test.only on CI
forbidOnly: !!process.env.CI,
workers: 1,
projects: [
{
name: 'default',
Expand Down
2 changes: 1 addition & 1 deletion internal/e2e-js/tests/roomSessionDemotePromote.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test } from '../fixtures'

Check failure on line 1 in internal/e2e-js/tests/roomSessionDemotePromote.spec.ts

View workflow job for this annotation

GitHub Actions / Browser SDK production / Run E2E tests (20.x, demote)

[demote] › roomSessionDemotePromote.spec.ts:15:7 › RoomSession demote participant and then promote again › should demote participant and then promote again

1) [demote] › roomSessionDemotePromote.spec.ts:15:7 › RoomSession demote participant and then promote again › should demote participant and then promote again Test timeout of 120000ms exceeded.
import type { Video } from '@signalwire/js'
import {
SERVER_URL,
Expand Down Expand Up @@ -137,7 +137,7 @@

await pageTwo.waitForTimeout(1000)

// --------------- Promote audience from pageOne and resolve on `member.joined` ---------------
// --------------- Promote audience from pageOne and resolve on `member.joined` and `room.joined` ---------------
const promiseMemberWaitingForMemberJoin = pageOne.evaluate(
async ({ promoteMemberId }) => {
// @ts-expect-error
Expand Down
4 changes: 2 additions & 2 deletions packages/js/src/utils/interfaces/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ export interface BaseRoomSessionContract {
*/
screenShareList: RoomSessionScreenShare[]
/**
* Leaves the room. This detaches all the locally originating streams from the room.
* Leaves the room immediately. This detaches all the locally originating streams from the room.
*/
leave(): Promise<void>
leave(): void
/**
* Return the member overlay on top of the root element
*/
Expand Down
58 changes: 48 additions & 10 deletions packages/webrtc/src/BaseConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,19 +195,34 @@ export class BaseConnection<
this.logger.debug('Set RTCPeer', rtcPeer.uuid, rtcPeer)
this.rtcPeerMap.set(rtcPeer.uuid, rtcPeer)

const setActivePeer = (peerId: string) => {
this.logger.debug('>>> Replace active RTCPeer with', peerId)
this.activeRTCPeerId = peerId
}

/**
* In case of the promote/demote, a new peer is created.
* Hence, we hangup the old peer.
*/
if (this.peer && this.peer.instance && this.callId !== rtcPeer.uuid) {
const oldPeerId = this.peer.uuid
this.logger.debug('>>> Stop old RTCPeer', oldPeerId)
// Hangup the previous RTCPeer
this.hangup(oldPeerId).catch(console.error)

// Stop transceivers and then the Peer
this.peer.detachAndStop()

// Set the new peer as active peer
setActivePeer(rtcPeer.uuid)

// Send "verto.bye" to the server
this.hangup(oldPeerId)

// Remove RTCPeer from local cache to stop answering to ping/pong
// this.rtcPeerMap.delete(oldPeerId)
} else {
// Set the new peer as active peer
setActivePeer(rtcPeer.uuid)
}

this.logger.debug('>>> Replace RTCPeer with', rtcPeer.uuid)
this.activeRTCPeerId = rtcPeer.uuid
}

// Overload for BaseConnection events
Expand Down Expand Up @@ -357,8 +372,18 @@ export class BaseConnection<
try {
this.logger.debug('Build a new RTCPeer')
const rtcPeer = this._buildPeer('offer')
this.logger.debug('Trigger start for the new RTCPeer!')
this.logger.debug('Trigger start for the new RTCPeer!', rtcPeer.uuid)
await rtcPeer.start()

/**
* Ideally, the SDK set the active peer when the `room.subscribed` or
* `verto.display` event is received. However, in some cases, while
* promoting/demoting, the RTC Peer negotiates successfully but then
* starts the negotiation again.
* So, without waiting for the events, we can safely set the active
* Peer once the initial negotiation succeeds.
*/
this.setActiveRTCPeer(rtcPeer.uuid)
} catch (error) {
this.logger.error('Error building new RTCPeer to promote/demote', error)
}
Expand Down Expand Up @@ -814,26 +839,39 @@ export class BaseConnection<
}

this.logger.debug('UpdateMedia response', response)
if (!this.peer) {

/**
* At a time, there can be multiple RTC Peers.
* The {@link executeUpdateMedia} is called with a Peer ID
* We need to make sure we set the remote SDP coming from the server
* on the appropriate Peer.
* The appropriate Peer may or may not be the current/active (this.peer) one.
*/
const peer = this.getRTCPeerById(rtcPeerId)
if (!peer) {
return this.logger.error('Invalid RTCPeer to updateMedia')
}
await this.peer.onRemoteSdp(response.sdp)
await peer.onRemoteSdp(response.sdp)
} catch (error) {
this.logger.error('UpdateMedia error', error)
// this.setState('hangup')
throw error
}
}

async hangup(id?: string) {
hangup(id?: string) {
const rtcPeerId = id ?? this.callId
if (!rtcPeerId) {
throw new Error('Invalid RTCPeer ID to hangup')
}

try {
const message = VertoBye(this.dialogParams(rtcPeerId))
await this.vertoExecute({
/**
* Fire-and-Forget
* For privacy reasons, the user should be allowed to leave the call immediately.
*/
this.vertoExecute({
message,
callID: rtcPeerId,
node_id: this.nodeId,
Expand Down
Loading