Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
17 changes: 17 additions & 0 deletions packages/calling/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,23 @@ export default defineConfig({
testMatch: USER_SETS.SET_2USER.testSuite,
use: {...browserOptions[PW_BROWSER], testEnv: 'int'} as any,
},
// Call History has its own suite and can run in parallel with SET_2USER - PROD
// because it uses USER_1+USER_2 after those single-user suites complete.
{
name: 'SET_CALL_HISTORY - PROD',
dependencies: ['SET_1 - PROD', 'SET_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 Use existing project names for dependencies

This dependency list names projects that are not defined anywhere in this Playwright config: the registration projects above are generated as SET_REGISTRATION_1 - PROD and SET_REGISTRATION_2 - PROD, and the INT block below similarly depends on a nonexistent SET_2USER - INT (I checked the config with rg "SET_1|SET_2|SET_2USER|SET_REGISTRATION"). Playwright resolves dependencies by project name, so the new call-history projects make the config invalid before the suite can run.

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. Updated SET_CALL_HISTORY dependencies to use existing Playwright project names: SET_REGISTRATION_1 - PROD and SET_REGISTRATION_2 - PROD for PROD, and SET_CALL - INT for INT.

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_2USER - 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 2-user (shared USER_4+USER_5)
// {
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;
24 changes: 23 additions & 1 deletion packages/calling/playwright/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,26 @@ export const DEVELOPER_PORTAL_INT_GETTING_STARTED_URL =
'https://developer-portal-intb.ciscospark.com/docs/getting-started';

export {CALLING_SELECTORS} from './selectors';
export {AWAIT_TIMEOUT, SDK_INIT_TIMEOUT, REGISTRATION_TIMEOUT, OPERATION_TIMEOUT} from './timeouts';
export {
AWAIT_TIMEOUT,
SDK_INIT_TIMEOUT,
REGISTRATION_TIMEOUT,
OPERATION_TIMEOUT,
UI_SETTLE_MS,
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',
};
8 changes: 8 additions & 0 deletions packages/calling/playwright/constants/timeouts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,11 @@ export const AWAIT_TIMEOUT = 10000; // General UI interactions
export const SDK_INIT_TIMEOUT = 65000; // RETRY_TIMER_UPPER_LIMIT (60s) + 5s
export const REGISTRATION_TIMEOUT = 35000; // BASE_REG_RETRY_TIMER_VAL_IN_SEC (30s) + 5s
export const OPERATION_TIMEOUT = 15000; // SUPPLEMENTARY_SERVICES_TIMEOUT (10s) + 5s

// Pacing waits used by multi-step UI flows (transfer, hold/resume) to let the
// sample app and SDK settle between consecutive actions.
export const UI_SETTLE_MS = 3000;
export const POST_ACTION_SETTLE_MS = 5000;

// Suite-level timeout for multi-user transfer describe blocks.
export const TRANSFER_SUITE_TIMEOUT = 240000;
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 @@ -113,6 +113,14 @@ export const USER_SETS: Record<string, UserSet> = {
testSuite: 'set-2user.spec.ts',
},

// Call History uses USER_1+USER_2 after their single-user suites complete,
// so it can run alongside the SET_2USER 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_3USER: {
name: 'SET_3USER',
Expand Down
249 changes: 249 additions & 0 deletions packages/calling/playwright/test-groups/call-history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
import {test, expect} from '@playwright/test';
import {TestManager} from '../test-manager';
import {getPhoneNumber} from '../test-data';
import {
cleanupActiveCalls,
endCall,
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 {
expectHistoryTiming,
expectUiShowsHistoryRecord,
expectUiShowsHistoryRecords,
openCallHistoryList,
} from '../utils/call-history';
import {
attachCallHistorySummary,
endCallerIfStillActive,
expectDisposition,
getDisplayHistoryRecords,
runBidirectionalHistoryJourney,
waitForCallHistoryCase,
} from '../utils/call-history-journey';

export function callHistoryTests() {
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'
);

expectDisposition(calleeMissedRecord, ['MISSED']);
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 / CH-ALL-001 / CH-ALL-002: Bidirectional journey renders in Call History UI', 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.

Why does the index have to be CH-LIST-001 / CH-ALL-001 / CH-ALL-002 ?

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.

The test title should map to one clear test case ID. I renamed it from CH-LIST-001 / CH-ALL-001 / CH-ALL-002 to only CH-LIST-001.

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