Skip to content
Open
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
23 changes: 20 additions & 3 deletions packages/calling/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,18 +123,35 @@ export default defineConfig({
testMatch: USER_SETS.SET_CALL.testSuite,
use: {...browserOptions[PW_BROWSER], testEnv: 'int'} as any,
},
// Call History has its own suite and can run in parallel with SET_CALL - PROD
// because it uses USER_1+USER_2 after those single-user suites complete.
{
name: 'SET_CALL_HISTORY - PROD',
dependencies: ['SET_REGISTRATION_1 - PROD', 'SET_REGISTRATION_2 - PROD'],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Serialize call history with transfer suites

In PROD this project can start as soon as the USER_1/USER_2 registration suites finish, while SET_CALL_TRANSFER_CONSULT - PROD still uses USER_1/USER_2/USER_3 and only depends on SET_CALL - PROD. With workers: 10, Playwright can therefore run the new call-history contexts for USER_1/USER_2 at the same time as the transfer suite after SET_CALL completes, violating the account constraint in test-data.ts and causing registration/call collisions. Please add explicit ordering between SET_CALL_HISTORY and SET_CALL_TRANSFER_CONSULT and mirror the same serialization for INT.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed. SET_CALL_TRANSFER_CONSULT now explicitly depends on SET_CALL_HISTORY for both PROD and INT, so the transfer suite cannot run concurrently with call-history while both use USER_1/USER_2.

testDir: './playwright/suites',
testMatch: USER_SETS.SET_CALL_HISTORY.testSuite,
use: browserOptions[PW_BROWSER],
},
// INT aliases overlap between USER_1/2 and USER_4/5, so keep INT ordered.
{
name: 'SET_CALL_HISTORY - INT',
dependencies: ['SET_CALL - INT'],
testDir: './playwright/suites',
testMatch: USER_SETS.SET_CALL_HISTORY.testSuite,
use: {...browserOptions[PW_BROWSER], testEnv: 'int'} as any,
},

// 3-user transfer tests — waits for call tests
// 3-user transfer tests — waits for call history because both suites use USER_1/USER_2.
{
name: 'SET_CALL_TRANSFER_CONSULT - PROD',
dependencies: ['SET_CALL - PROD'],
dependencies: ['SET_CALL - PROD', 'SET_CALL_HISTORY - PROD'],
testDir: './playwright/suites',
testMatch: USER_SETS.SET_CALL_TRANSFER_CONSULT.testSuite,
use: browserOptions[PW_BROWSER],
},
{
name: 'SET_CALL_TRANSFER_CONSULT - INT',
dependencies: ['SET_CALL - INT'],
dependencies: ['SET_CALL - INT', 'SET_CALL_HISTORY - INT'],
testDir: './playwright/suites',
testMatch: USER_SETS.SET_CALL_TRANSFER_CONSULT.testSuite,
use: {...browserOptions[PW_BROWSER], testEnv: 'int'} as any,
Expand Down
13 changes: 13 additions & 0 deletions packages/calling/playwright/constants/call-history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const CALL_HISTORY_ANSWERED_DISPOSITIONS = ['ANSWERED', 'INITIATED'];
export const CALL_HISTORY_MISSED_CALLER_DISPOSITIONS = ['CANCELED', 'INITIATED'];
export const CALL_HISTORY_REJECTED_CALLER_DISPOSITIONS = ['CANCELED', 'INITIATED'];
export const CALL_HISTORY_REJECTED_CALLEE_DISPOSITIONS = ['REJECTED', 'MISSED', 'CANCELED'];

export const CALL_HISTORY_TIME_LOOKBACK_MS = 5000;
export const CALL_HISTORY_RECENT_RECORD_TOLERANCE_MS = 120000;
export const CALL_HISTORY_COUNTERPART_MATCH_MIN_DIGITS = 4;
export const CALL_HISTORY_URL_PATTERN = '**/history/userSessions**';
export const CALL_HISTORY_EVENTUAL_CONSISTENCY_TIMEOUT = 150000;
export const CALL_HISTORY_POLL_INTERVALS = [5000, 5000, 10000, 10000, 15000];
export const CALL_HISTORY_TIMING_TOLERANCE_MS = 120000;
export const CALL_HISTORY_DURATION_TOLERANCE_SECONDS = 120;
14 changes: 14 additions & 0 deletions packages/calling/playwright/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,17 @@ export {
POST_ACTION_SETTLE_MS,
TRANSFER_SUITE_TIMEOUT,
} from './timeouts';
export {
CALL_HISTORY_ANSWERED_DISPOSITIONS,
CALL_HISTORY_MISSED_CALLER_DISPOSITIONS,
CALL_HISTORY_REJECTED_CALLER_DISPOSITIONS,
CALL_HISTORY_REJECTED_CALLEE_DISPOSITIONS,
CALL_HISTORY_TIME_LOOKBACK_MS,
CALL_HISTORY_RECENT_RECORD_TOLERANCE_MS,
CALL_HISTORY_COUNTERPART_MATCH_MIN_DIGITS,
CALL_HISTORY_URL_PATTERN,
CALL_HISTORY_EVENTUAL_CONSISTENCY_TIMEOUT,
CALL_HISTORY_POLL_INTERVALS,
CALL_HISTORY_TIMING_TOLERANCE_MS,
CALL_HISTORY_DURATION_TOLERANCE_SECONDS,
} from './call-history';
5 changes: 5 additions & 0 deletions packages/calling/playwright/constants/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,10 @@ export const CALLING_SELECTORS = {
INCOMING_CALL: '#incoming-call',
CALL_QUALITY_METRICS: '#call-quality-metrics',

// Call History
CALL_HISTORY_BTN: '#Call-history',
CALL_HISTORY_HEADER: '#callHistoryHeaderId',
CALL_HISTORY_TABLE_BODY: '#callHistoryTableId',

END_BTN: '#end',
};
4 changes: 4 additions & 0 deletions packages/calling/playwright/suites/call-history.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import {callHistoryTests} from '../test-groups/call-history';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

name doesnt have to set say set-2. If we have single file for call history tests then stating call-history-spec.ts in the name should be enough

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done, renamed the suite file to call-history.spec.ts and updated the USER_SETS mapping accordingly.


// Account roles resolved from testInfo.project.name -> USER_SETS.
callHistoryTests();
8 changes: 8 additions & 0 deletions packages/calling/playwright/test-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,14 @@ export const USER_SETS: Record<string, UserSet> = {
testSuite: 'set-call.spec.ts',
},

// Call History uses USER_1+USER_2 after their single-user suites complete,
// so it can run alongside the SET_CALL call lifecycle suite in PROD.
SET_CALL_HISTORY: {
name: 'SET_CALL_HISTORY',
accounts: ['USER_1', 'USER_2'],
testSuite: 'call-history.spec.ts',
},

// 3-user transfer tests (PROD — dedicated accounts, parallel with registration)
SET_CALL_TRANSFER_CONSULT: {
name: 'SET_CALL_TRANSFER_CONSULT',
Expand Down
275 changes: 275 additions & 0 deletions packages/calling/playwright/test-groups/call-history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
import {test, expect} from '@playwright/test';
import {TestManager} from '../test-manager';
import {getPhoneNumber} from '../test-data';
import {
cleanupActiveCalls,
endCall,
endCallerIfStillActive,
establishCall,
makeCall,
rejectCall,
waitForCallDisconnect,
waitForIncomingCall,
} from '../utils/call';
import {
CALL_HISTORY_ANSWERED_DISPOSITIONS,
CALL_HISTORY_MISSED_CALLER_DISPOSITIONS,
CALL_HISTORY_REJECTED_CALLEE_DISPOSITIONS,
CALL_HISTORY_REJECTED_CALLER_DISPOSITIONS,
CALL_HISTORY_TIME_LOOKBACK_MS,
} from '../constants';
import {
attachCallHistorySummary,
expectHistoryTiming,
expectUiShowsHistoryRecord,
expectUiShowsHistoryRecords,
getCallHistoryRecords,
getDisplayHistoryRecords,
openCallHistoryList,
waitForCallHistoryCase,
} from '../utils/call-history';
import {runBidirectionalHistoryJourney} from '../utils/call-history-journey';

export function callHistoryTests() {
test.describe('Call History Query', () => {
test('CH-QUERY-001: Call history query helper passes pagination and sorting options', async ({
page,
}) => {
await page.evaluate(() => {
(window as any).__callHistoryQueryArgs = [];
(window as any).callHistory = {
getCallHistoryData: async (...args: unknown[]) => {
(window as any).__callHistoryQueryArgs.push(args);

return {data: {userSessions: [{sessionId: 'query-record'}]}};
},
};
});

const records = await getCallHistoryRecords(page, {
days: 3,
limit: 5,
sort: 'ASC',
sortBy: 'startTime',
});

const queryArgs = await page.evaluate(() => (window as any).__callHistoryQueryArgs[0]);

expect(records).toEqual([{sessionId: 'query-record'}]);
expect(queryArgs).toEqual([3, 5, 'ASC', 'startTime']);
});
});

test.describe('Call History', () => {
test.describe.configure({mode: 'serial', timeout: 300000});

let tm: TestManager;
let callerNumber: string;
let calleeNumber: string;

test.beforeAll(async ({browser}, testInfo) => {
tm = new TestManager(testInfo.project.name);
await tm.setupContext(browser, 0, {
initSDK: true,
service: 'calling',
register: true,
media: true,
});
await tm.setupContext(browser, 1, {
initSDK: true,
service: 'calling',
register: true,
media: true,
});
callerNumber = getPhoneNumber(tm.userSet.accounts[0]);
calleeNumber = getPhoneNumber(tm.userSet.accounts[1]);
});

test.afterEach(async () => {
await Promise.all([
cleanupActiveCalls(tm.getPage(tm.userSet.accounts[0])),
cleanupActiveCalls(tm.getPage(tm.userSet.accounts[1])),
]);
if (!tm.page.isClosed()) {
await tm.page.waitForTimeout(3000);
}
});

test.afterAll(async () => {
await tm.cleanup();
});

/* eslint-disable no-empty-pattern */
test('CH-CALL-001: Answered call creates exact per-user history records', async ({}, testInfo) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we have a test which atleast checks sort/limit/sortBy query parameters to justify their existence ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I added CH-QUERY-001, which verifies that days, limit, sort, and sortBy are passed correctly to getCallHistoryData.

const callerPage = tm.getPage(tm.userSet.accounts[0]);
const calleePage = tm.getPage(tm.userSet.accounts[1]);
const startedAt = new Date(Date.now() - CALL_HISTORY_TIME_LOOKBACK_MS);

await establishCall(callerPage, calleePage, calleeNumber);
await callerPage.waitForTimeout(2000);
await endCall(callerPage);
await Promise.all([waitForCallDisconnect(callerPage), waitForCallDisconnect(calleePage)]);
const endedAt = new Date();

const callerRecord = await waitForCallHistoryCase(
callerPage,
{
counterpartNumber: calleeNumber,
direction: 'OUTGOING',
startedAt,
dispositions: CALL_HISTORY_ANSWERED_DISPOSITIONS,
},
'caller outgoing answered call'
);
const calleeRecord = await waitForCallHistoryCase(
calleePage,
{
counterpartNumber: callerNumber,
direction: 'INCOMING',
startedAt,
dispositions: CALL_HISTORY_ANSWERED_DISPOSITIONS,
},
'callee incoming answered call'
);

expectHistoryTiming(callerRecord, {notBefore: startedAt, notAfter: endedAt});
expectHistoryTiming(calleeRecord, {notBefore: startedAt, notAfter: endedAt});
await attachCallHistorySummary(testInfo, 'answered-picked', [
{user: 'user1', expectedDisposition: 'ANSWERED', record: callerRecord},
{user: 'user2', expectedDisposition: 'ANSWERED', record: calleeRecord},
]);

const callerRows = await openCallHistoryList(callerPage);
expect(callerRows.length).toBeGreaterThan(0);

await expectUiShowsHistoryRecord(callerPage, callerRecord);
await expectUiShowsHistoryRecord(calleePage, calleeRecord);
});

test('CH-CALL-002: Missed call creates exact callee MISSED history', async ({}, testInfo) => {
const callerPage = tm.getPage(tm.userSet.accounts[0]);
const calleePage = tm.getPage(tm.userSet.accounts[1]);
const startedAt = new Date(Date.now() - CALL_HISTORY_TIME_LOOKBACK_MS);

await makeCall(callerPage, calleeNumber);
await waitForIncomingCall(calleePage);
await callerPage.waitForTimeout(5000);
await endCall(callerPage);
await Promise.all([waitForCallDisconnect(callerPage), waitForCallDisconnect(calleePage)]);
const endedAt = new Date();

const calleeMissedRecord = await waitForCallHistoryCase(
calleePage,
{
counterpartNumber: callerNumber,
direction: 'INCOMING',
startedAt,
dispositions: ['MISSED'],
},
'callee incoming missed call'
);
const callerCanceledRecord = await waitForCallHistoryCase(
callerPage,
{
counterpartNumber: calleeNumber,
direction: 'OUTGOING',
startedAt,
dispositions: CALL_HISTORY_MISSED_CALLER_DISPOSITIONS,
},
'caller outgoing unanswered call'
);

expectHistoryTiming(calleeMissedRecord, {notBefore: startedAt, notAfter: endedAt});
expectHistoryTiming(callerCanceledRecord, {notBefore: startedAt, notAfter: endedAt});
await attachCallHistorySummary(testInfo, 'missed-not-picked', [
{user: 'user1', expectedDisposition: 'CANCELED', record: callerCanceledRecord},
{user: 'user2', expectedDisposition: 'MISSED', record: calleeMissedRecord},
]);

await expectUiShowsHistoryRecord(calleePage, calleeMissedRecord);
await expectUiShowsHistoryRecord(callerPage, callerCanceledRecord);
});

test('CH-CALL-003: Rejected call creates exact per-user history records', async ({}, testInfo) => {
const callerPage = tm.getPage(tm.userSet.accounts[0]);
const calleePage = tm.getPage(tm.userSet.accounts[1]);
const startedAt = new Date(Date.now() - CALL_HISTORY_TIME_LOOKBACK_MS);

await makeCall(callerPage, calleeNumber);
await waitForIncomingCall(calleePage);
await rejectCall(calleePage);
await endCallerIfStillActive(callerPage);
await Promise.all([waitForCallDisconnect(callerPage), waitForCallDisconnect(calleePage)]);
const endedAt = new Date();

const callerRecord = await waitForCallHistoryCase(
callerPage,
{
counterpartNumber: calleeNumber,
direction: 'OUTGOING',
startedAt,
dispositions: CALL_HISTORY_REJECTED_CALLER_DISPOSITIONS,
},
'caller outgoing rejected call'
);
const calleeRecord = await waitForCallHistoryCase(
calleePage,
{
counterpartNumber: callerNumber,
direction: 'INCOMING',
startedAt,
dispositions: CALL_HISTORY_REJECTED_CALLEE_DISPOSITIONS,
},
'callee incoming rejected call'
);

expectHistoryTiming(callerRecord, {notBefore: startedAt, notAfter: endedAt});
expectHistoryTiming(calleeRecord, {notBefore: startedAt, notAfter: endedAt});
await attachCallHistorySummary(testInfo, 'rejected', [
{user: 'user1', expectedDisposition: 'CANCELED', record: callerRecord},
{user: 'user2', expectedDisposition: 'REJECTED', record: calleeRecord},
]);

await expectUiShowsHistoryRecord(callerPage, callerRecord);
await expectUiShowsHistoryRecord(calleePage, calleeRecord);
});

test('CH-LIST-001: Bidirectional journey renders in Call History UI', async ({}, testInfo) => {
test.setTimeout(1800000);

const callerPage = tm.getPage(tm.userSet.accounts[0]);
const calleePage = tm.getPage(tm.userSet.accounts[1]);

const journey = await runBidirectionalHistoryJourney(
{
user1Page: callerPage,
user1Number: callerNumber,
user2Page: calleePage,
user2Number: calleeNumber,
},
testInfo
);

expect(journey.user1Records).toHaveLength(6);
expect(journey.user2Records).toHaveLength(6);

await attachCallHistorySummary(
testInfo,
'both-users-call-history-journey',
journey.debugRecords
);

await Promise.all([
expectUiShowsHistoryRecords(
callerPage,
getDisplayHistoryRecords(journey.debugRecords, 'user1')
),
expectUiShowsHistoryRecords(
calleePage,
getDisplayHistoryRecords(journey.debugRecords, 'user2')
),
]);
});
/* eslint-enable no-empty-pattern */
});
}
Loading
Loading