Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
2428fc3
fix waitForFunction bugs with parameters being incorrect
LittorWired Sep 3, 2025
4875112
in expectToPass use the correct intervals param to fix polling issues
LittorWired Sep 3, 2025
766444a
add polling unit tests for the utils expectToPass and expectPageEvalT…
LittorWired Sep 3, 2025
591ab3c
implement the unit tests for the waitForFunction utility with polling…
LittorWired Sep 3, 2025
1835909
refactor address.spec.ts tests to use the expectPageEvalToPass utility
LittorWired Sep 4, 2025
09fec22
refactor expectCFFinalEvents to utilize expectPageEvalToPass and allo…
LittorWired Sep 4, 2025
12663d0
fix type linting issues
LittorWired Sep 4, 2025
b556e92
refactor agent_customer.spec.ts tests to utilize expectPageEvalToPass
LittorWired Sep 4, 2025
7f33331
refactor the first test in audioFlags.spec.ts to utilize expectPageEv…
LittorWired Sep 4, 2025
b429a13
refactor audioFlags.spec.ts to improve test structure and utilize exp…
LittorWired Sep 5, 2025
4e1856c
remove unused type
LittorWired Sep 5, 2025
d0a1b86
refactor cleanup.spec.ts to improve test structure, utilize expectPag…
LittorWired Sep 8, 2025
53bdd00
refactor conversation.spec.ts to utilize expectPageEvalToPass for ass…
LittorWired Sep 8, 2025
c51cd3c
refactor deviceEvent.spec.ts to utilize expectPageEvalToPass
LittorWired Sep 8, 2025
e29bc0e
fix util tests after fixing intervals param
LittorWired Sep 9, 2025
42ee800
refactor deviceState.spec.ts to utilize expectPageEvalToPass
LittorWired Sep 9, 2025
fbad4bb
refactor dialAddress e2e utility to use multiple expectPageEvalToPass…
LittorWired Sep 9, 2025
f5bfb37
increase SW client loglevel to debug
LittorWired Sep 9, 2025
22f15eb
break up tests into multiple projects
LittorWired Sep 10, 2025
e65ad3b
update the list of projects in github workflow matrix
LittorWired Sep 10, 2025
3583d15
update playwright names for better debugging
LittorWired Sep 10, 2025
ac6602b
update the github project matrix names
LittorWired Sep 10, 2025
f327b73
dedupe projects
LittorWired Sep 10, 2025
13872f9
increase the expectCFFinalEvents timeout
LittorWired Sep 10, 2025
52123b3
attach listener before call.start
LittorWired Sep 10, 2025
a910632
attach listener for call.play finished before playback stop
LittorWired Sep 10, 2025
46a380e
add optional timeout parameter to expectCFFinalEvents for optionally …
LittorWired Sep 11, 2025
5faaacd
refactor the tts audio swml e2e test to utilize the expectPageEvalToP…
LittorWired Sep 11, 2025
619fea3
remove import
LittorWired Sep 11, 2025
dabec90
Merge remote-tracking branch 'origin/main' into tl/cp-15995-refactor-…
LittorWired Sep 15, 2025
96a7772
Merge remote-tracking branch 'origin/main' into tl/cp-15995-refactor-…
LittorWired Sep 16, 2025
5e84770
implement TestContext class for tracking SDK and server events during…
LittorWired Sep 17, 2025
b66b5f8
add WebSocket monitoring and test context utilities for the testConte…
LittorWired Sep 17, 2025
abf3c19
add to the Playwright TestNameReporter to include SDK Test Context du…
LittorWired Sep 17, 2025
37b0a23
extend Playwright test fixture to include test context setup and atta…
LittorWired Sep 17, 2025
6f778c3
PR feedback: await client.rettach call
LittorWired Sep 18, 2025
2045e01
Add isSerializable utility function to check for serializable values …
LittorWired Sep 18, 2025
7d35c31
tests for isSerializable utility function for e2e tests
LittorWired Sep 18, 2025
f60f4ca
add serialization check to expectPageEvalToPass
LittorWired Sep 19, 2025
1866875
fix bug on argument check for expectPageEvalToPass
LittorWired Sep 19, 2025
0a76e14
revert the serializable checks
LittorWired Sep 22, 2025
8d1d355
revert the serializable checks
LittorWired Sep 22, 2025
2bcb78a
PR feedback: address PR feedback on call.reattach and check call inst…
LittorWired Sep 22, 2025
4f7a37c
revert the isCallSession checks as they fail
LittorWired Sep 22, 2025
7255603
PR feedback: assertions against the callSession.id
LittorWired Sep 22, 2025
7524aa2
Merge branch 'tl/cp-15995-refactor-e2e-client-tests-new-util-pattern'…
LittorWired Sep 23, 2025
d0e84da
PR feedback: modify categories to be transport/WS, connection, callSe…
LittorWired Sep 23, 2025
1be5820
PR Feedback: use SDKEvent interface
LittorWired Sep 26, 2025
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
17 changes: 16 additions & 1 deletion .github/workflows/browser-client-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,22 @@ jobs:
fail-fast: false
matrix:
node-version: [20.x]
project: [default, callfabric, renegotiation, videoElement]
project:
[
default,
Utilities,
CoreRoom,
AudioVideo,
Device,
Agent,
Connection,
Interaction,
Renegotiation,
Conversation,
IncomingCall,
Websocket,
VideoElement,
]
steps:
- uses: actions/checkout@v4
- name: Install deps
Expand Down
17 changes: 16 additions & 1 deletion .github/workflows/browser-client-staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,22 @@ jobs:
fail-fast: false
matrix:
node-version: [20.x]
project: [default, callfabric, renegotiation, videoElement]
project:
[
default,
Utilities,
CoreRoom,
AudioVideo,
Device,
Agent,
Connection,
Interaction,
Renegotiation,
Conversation,
IncomingCall,
Websocket,
VideoElement,
]
steps:
- uses: actions/checkout@v4
- name: Install deps
Expand Down
307 changes: 307 additions & 0 deletions internal/e2e-client/TestContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
import { randomUUID } from 'crypto'
import type { JSONRPCMethod } from '@signalwire/core/'

type EventCategory =
| 'transport'
| 'connection'
| 'callSession'
| 'conversations'
| 'tests'

type Event =
| 'websocket_open'
| 'websocket_close'
| 'websocket_error'
| 'websocket_message'
| 'websocket_closed'

type Params = {
call_id?: string
room_session?: {
id?: string
}
}

type Result = {
call_id?: string
room_session?: {
id?: string
}
}

type Metadata = Record<string, any>

type Payload = {
error?: any
method?: JSONRPCMethod
event?: Event
params?: Params
result?: Result
metadata?: Metadata
}

export class SDKEvent {
constructor(
public id: string,
public timestamp: number,
public direction: 'send' | 'recv',
public category: 'request' | 'response' | 'notification' | 'connection',
public eventType: string,
public eventCategory: EventCategory,
public payload: Payload,
public context: {
isCallEvent: boolean
isRoomEvent: boolean
isConnectionEvent: boolean
isError: boolean
callId?: string
roomId?: string
},
public method?: JSONRPCMethod,
public metadata?: Metadata
) {}

// Utility methods for common checks
isCallRelated(): boolean {
return this.context.isCallEvent
}

isRoomRelated(): boolean {
return this.context.isRoomEvent
}

isConnectionRelated(): boolean {
return this.context.isConnectionEvent
}

isError(): boolean {
return this.context.isError
}

isSent(): boolean {
return this.direction === 'send'
}

isReceived(): boolean {
return this.direction === 'recv'
}

// Category checks
isTransport(): boolean {
return this.eventCategory === 'transport'
}

isConnection(): boolean {
return this.eventCategory === 'connection'
}

isCallSession(): boolean {
return this.eventCategory === 'callSession'
}

isConversations(): boolean {
return this.eventCategory === 'conversations'
}

isTests(): boolean {
return this.eventCategory === 'tests'
}
}

export interface EventStats {
totalEvents: number
sentEvents: number
receivedEvents: number
errorEvents: number
callEvents: number
roomEvents: number
connectionEvents: number
// Category-based counts
transportCategoryEvents: number
connectionCategoryEvents: number
callSessionCategoryEvents: number
conversationsCategoryEvents: number
testsCategoryEvents: number
}

/**
* TestContext class for tracking SDK events during e2e tests.
*
* Usage:
* - Automatically set up via Playwright fixtures
* - Events are captured via WebSocket monitoring
* - Context dumped to console and attachments on test failures
*/
export class TestContext {
private sdkEvents: SDKEvent[] = []
private startTime: number = Date.now()

addSDKEvent(payload: Payload, direction: 'send' | 'recv') {
const event = this.categorizeSDKEvent(payload, direction)
this.sdkEvents.push(event)
}

/**
* Get comprehensive statistics about captured events.
* Includes both legacy boolean-based counts and new category-based counts.
*/
getStats() {
return {
totalEvents: this.sdkEvents.length,
sentEvents: this.sdkEvents.filter((event) => event.isSent()).length,
receivedEvents: this.sdkEvents.filter((event) => event.isReceived())
.length,
errorEvents: this.sdkEvents.filter((event) => event.isError()).length,
callEvents: this.sdkEvents.filter((event) => event.isCallRelated())
.length,
roomEvents: this.sdkEvents.filter((event) => event.isRoomRelated())
.length,
connectionEvents: this.sdkEvents.filter((event) =>
event.isConnectionRelated()
).length,
// Category-based counts
transportCategoryEvents: this.sdkEvents.filter((event) =>
event.isTransport()
).length,
connectionCategoryEvents: this.sdkEvents.filter((event) =>
event.isConnection()
).length,
callSessionCategoryEvents: this.sdkEvents.filter((event) =>
event.isCallSession()
).length,
conversationsCategoryEvents: this.sdkEvents.filter((event) =>
event.isConversations()
).length,
testsCategoryEvents: this.sdkEvents.filter((event) => event.isTests())
.length,
}
}

getAllEvents() {
return [...this.sdkEvents]
}

getTestDuration() {
return Date.now() - this.startTime
}

private categorizeSDKEvent(
payload: Payload,
direction: 'send' | 'recv'
): SDKEvent {
const timestamp = Date.now()
const id = `${timestamp}-${randomUUID()}`

const method = payload.method || ''

const context = {
isCallEvent: method.includes('call.') || method.includes('calling.'),
isRoomEvent:
method.includes('room.') ||
method.includes('member.') ||
method.includes('layout.'),
isConnectionEvent:
method.startsWith('signalwire.') || method.startsWith('subscriber.'),
isError: Boolean(payload.error),
callId: payload.params?.call_id || payload.result?.call_id,
roomId:
payload.params?.room_session?.id || payload.result?.room_session?.id,
}

return new SDKEvent(
id,
timestamp,
direction,
this.getCategory(payload),
this.getEventType(payload),
this.categorizeMethod(payload),
payload,
context,
payload.method,
payload.metadata
)
}

private getCategory(payload: Payload) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could have a concrete implementation for the SDKEvent interface with these utilities

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the feedback, I implemented the SDKEvent as its own class.

// JSON-RPC 2.0 specification:
// - Response: Has 'id' and ('result' OR 'error')
// - Request: Has 'method' and 'id'
// - Notification: Has 'method' but no 'id'
const hasId = 'id' in payload
const hasMethod = 'method' in payload
const hasResult = 'result' in payload
const hasError = 'error' in payload

// Response: has id and (result or error), typically no method
if (hasId && (hasResult || hasError)) {
return 'response' as const
}

// Request: has both method and id
if (hasMethod && hasId) {
return 'request' as const
}

// Notification: has method but no id
if (hasMethod && !hasId) {
return 'notification' as const
}

// Fallback for connection events (websocket events, etc.)
return 'connection' as const
}

private categorizeMethod(payload: Payload): EventCategory {
const method = payload.method || ''
const event = payload.event || ''

if (
event === 'websocket_open' ||
event === 'websocket_close' ||
event === 'websocket_error' ||
event === 'websocket_message' ||
event === 'websocket_closed'
) {
return 'transport'
}

// SignalWire connection events
if (method.startsWith('signalwire.') || method.startsWith('subscriber.')) {
return 'connection'
}

// Conversations events
if (method.startsWith('conversations.')) {
return 'conversations'
}

// Test events (artificial events)
if (method.startsWith('test.') || method.includes('artificial')) {
return 'tests'
}

// Everything else is callSession (call, room, member, chat, verto, voice, etc.)
// Note: verto.* are call signaling events, not transport events
return 'callSession'
}

private getEventType(payload: {
error?: any
method?: JSONRPCMethod
event?: Event
}) {
if (payload.error) {
return payload.error.message
}

if (payload.method) {
return payload.method
}

if (payload.event) {
return payload.event
}

return 'unknown'
}
}
Loading
Loading