-
Notifications
You must be signed in to change notification settings - Fork 397
test(calling): add call history e2e flow #4994
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
base: next
Are you sure you want to change the base?
Changes from all commits
1bf0733
f9babde
239129e
cf8efe9
9faf23f
13e8b50
933f23b
702337c
a31052a
5ad2eae
0fa9d01
d00fa1a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| import {callHistoryTests} from '../test-groups/call-history'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
| 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) => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 */ | ||
| }); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In PROD this project can start as soon as the USER_1/USER_2 registration suites finish, while
SET_CALL_TRANSFER_CONSULT - PRODstill uses USER_1/USER_2/USER_3 and only depends onSET_CALL - PROD. Withworkers: 10, Playwright can therefore run the new call-history contexts for USER_1/USER_2 at the same time as the transfer suite afterSET_CALLcompletes, violating the account constraint intest-data.tsand causing registration/call collisions. Please add explicit ordering betweenSET_CALL_HISTORYandSET_CALL_TRANSFER_CONSULTand mirror the same serialization for INT.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
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.