Skip to content

test(calling): add call history e2e flow#4994

Open
Jahnavi314 wants to merge 7 commits into
webex:nextfrom
Jahnavi314:callhistorye2e
Open

test(calling): add call history e2e flow#4994
Jahnavi314 wants to merge 7 commits into
webex:nextfrom
Jahnavi314:callhistorye2e

Conversation

@Jahnavi314
Copy link
Copy Markdown
Contributor

@Jahnavi314 Jahnavi314 commented May 19, 2026

COMPLETES #https://jira-eng-sjc12.cisco.com/jira/browse/CAI-7876

This pull request addresses

Adds automated Playwright e2e coverage for the Webex Calling Call History flow in the two-user calling suite. The tests validate that call history records are created and rendered correctly for answered, missed, and rejected call scenarios.

by making the following changes

  • Added Call History selectors for the calling sample UI.
  • Added Call History Playwright utilities to fetch history records, poll for eventual consistency, normalize direction/disposition values, validate timing, and assert UI table rows.
  • Added two-user Call History e2e test coverage for:
    • answered calls
    • missed calls
    • rejected calls
    • bidirectional outgoing call history journeys
  • Registered the Call History test group in the SET_2USER suite.
  • Added small call utility improvements to make call setup/disconnect waits more reliable for the new e2e flow.

Change Type

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Tooling change
  • Internal code refactor

The following scenarios were tested

  • Added automated Playwright coverage for Call History in SET_2USER.
  • Validated answered call history records for caller and callee.
  • Validated missed call history records for caller and callee.
  • Validated rejected call history records for caller and callee.
  • Validated Call History UI table rendering for returned records.

The GAI Coding Policy And Copyright Annotation Best Practices

  • GAI was not used (or, no additional notation is required)
  • Code was generated entirely by GAI
  • GAI was used to create a draft that was subsequently customized or modified
  • Coder created a draft manually that was non-substantively modified by GAI (e.g., refactoring was performed by GAI on manually written code)
  • Tool used for AI assistance (GitHub Copilot / Other - specify)
    • Github Copilot
    • Other - OpenAI Codex
  • This PR is related to
    • Feature
    • Defect fix
    • Tech Debt
    • Automation

I certified that

  • I have read and followed contributing guidelines
  • I discussed changes with code owners prior to submitting this pull request
  • I have not skipped any automated checks
  • All existing and new tests passed
  • I have updated the documentation accordingly

@Jahnavi314 Jahnavi314 requested a review from a team as a code owner May 19, 2026 04:55
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1bf0733e67

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +647 to +655
const callerRecord = await waitForCallHistoryRecord(
callerPage,
(record) =>
recordMatchesCallCase(record, {
counterpartNumber: calleeNumber,
direction: 'OUTGOING',
startedAt,
dispositions: UNANSWERED_CALLER_DISPOSITIONS,
}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require a new history row for rejected-call assertions

Because this serial suite runs the missed-call test immediately before this rejected-call test, these predicates can return the previous missed call: isRecentRecord() accepts rows up to 120 seconds before startedAt, the counterpart/direction are identical, and the callee-side rejected dispositions include MISSED. If the rejected history row is not created yet, waitForCallHistoryRecord() can pass using the earlier missed-call records, so the test no longer verifies that rejecting the new call wrote history. Snapshot existing record keys before placing this call or use the waitForNewHistoryRecord() helper here as the journey test does.

Useful? React with 👍 / 👎.

@aws-amplify-us-east-2
Copy link
Copy Markdown

This pull request is automatically being deployed by Amplify Hosting (learn more).

Access this pull request here: https://pr-4994.d3m3l2kee0btzx.amplifyapp.com

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f9babdedee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +609 to +617
const callerCanceledRecord = await waitForCallHistoryRecord(
callerPage,
(record) =>
recordMatchesCallCase(record, {
counterpartNumber: calleeNumber,
direction: 'OUTGOING',
startedAt,
dispositions: UNANSWERED_CALLER_DISPOSITIONS,
}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require a fresh row for missed caller history

In this serial suite, the answered-call test runs immediately before this missed-call test, and the caller-side matcher here accepts INITIATED for unanswered calls while the answered caller record can also be INITIATED (ANSWERED_DISPOSITIONS includes it). Because isRecentRecord() allows records up to 120 seconds before this call's startedAt, waitForCallHistoryRecord() can pass by returning the previous answered outgoing row before the new missed-call row exists, so the test may not verify caller history for this missed call. Snapshot existing keys before placing the call, or use the new-record helper as the journey test does.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 239129e5fc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +547 to +557
const callerRecord = await waitForCallHistoryRecord(
callerPage,
(record) =>
recordMatchesCallCase(record, {
counterpartNumber: calleeNumber,
direction: 'OUTGOING',
startedAt,
dispositions: ANSWERED_DISPOSITIONS,
}),
'caller outgoing answered call'
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require a fresh answered history row

In the SET_2USER suite, callHistoryTests() runs immediately after callLifecycleTests()/callLifecycleMediaTests() (see set-2user.spec.ts), which already create answered calls between the same two accounts. Since isRecentRecord() accepts records up to 120 seconds before this test's startedAt and these predicates only match counterpart/direction/answered disposition, this can return a lifecycle record before the new answered call is written, so the test may pass without verifying this call's history. Snapshot existing history keys before placing the call or use the new-record helper used by the journey test.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member

@eigengravy eigengravy left a comment

Choose a reason for hiding this comment

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

Partial review

backup: {region: string; uris: string[]};
};

const CALLING_UMD_BUNDLE_PATH = path.resolve(__dirname, '../../../webex/umd/calling.min.js');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this required to run call history? Does the default sample app not support CallHistory?

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.

This is not required. The default sample app already supports CallHistory through callHistory: true and exposes window.callHistory. I removed the custom UMD bundle routing so the tests use the default sample app bundle.

Comment on lines +29 to +40
await page.route('**/samples/calling.min.js', (route) =>
route.fulfill({
path: CALLING_UMD_BUNDLE_PATH,
contentType: 'application/javascript',
})
);
await page.route('**/samples/calling.min.js.map', (route) =>
route.fulfill({
path: CALLING_UMD_BUNDLE_MAP_PATH,
contentType: 'application/json',
})
);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same comment as above.

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.The tests now use the default sample app bundle directly, since the sample app already supports CallHistory.

Comment on lines +41 to +138
'Run three User 1 to User 2 outgoing call attempts: missed, rejected, and answered.',
'Run three User 2 to User 1 outgoing call attempts: missed, rejected, and answered.',
'Open the sample app Call History list for both users.',
],
expected: [
'User 1 history contains the three recent outgoing User 1 to User 2 records.',
'User 2 history contains the three recent outgoing User 2 to User 1 records.',
'The table header renders the Call History columns and all expected rows are visible.',
],
},
{
id: 'CH-CALL-001',
title: 'Answered call creates exact per-user history records',
preconditions: ['Caller and callee are registered and can place/answer calls.'],
steps: [
'Caller dials callee.',
'Callee answers and the call reaches established state on both pages.',
'Caller ends the call.',
'Poll Call History for caller and callee.',
'Render each returned record through the Call History UI table.',
],
expected: [
'Caller history contains a recent OUTGOING record for the callee.',
'Callee history contains a recent INCOMING record for the caller.',
'Each UI row shows the exact Janus direction, disposition, start time, end time, and session type.',
'Call timing is valid: start/end are ISO dates, end is not before start, and duration is plausible.',
],
},
{
id: 'CH-CALL-002',
title: 'Missed call creates exact callee MISSED history',
preconditions: ['Caller and callee are registered; callee does not answer the incoming call.'],
steps: [
'Caller dials callee.',
'Callee rings and intentionally does not answer.',
'Caller ends the ringing call.',
'Poll Call History for caller and callee.',
'Render the returned records through the Call History UI table.',
],
expected: [
'Callee history contains a recent INCOMING MISSED record for the caller.',
'Caller history contains a recent OUTGOING unanswered record for the callee.',
'The UI reflects the exact backend status and call timing for each user.',
],
},
{
id: 'CH-CALL-003',
title: 'Rejected call creates exact per-user history records',
preconditions: ['Caller and callee are registered; callee can reject the incoming call.'],
steps: [
'Caller dials callee.',
'Callee rejects the ringing call from the UI.',
'Wait until both users have no active calls.',
'Poll Call History for caller and callee.',
'Render the returned records through the Call History UI table.',
],
expected: [
'Caller and callee both receive recent history rows for the rejected call.',
'The UI renders the exact Janus direction, disposition, start time, end time, and session type.',
],
},
{
id: 'CH-ALL-001',
title: 'User 1 login shows missed, rejected, and answered outgoing call history',
preconditions: ['Both users stay initialized, registered, and media-ready in one browser run.'],
steps: [
'User 1 calls User 2 and User 2 does not answer.',
'User 1 calls User 2 again and User 2 rejects.',
'User 1 calls User 2 again and User 2 answers.',
'After both users complete their outgoing call journeys, open Call History for User 1.',
],
expected: [
'User 1 shows three recent OUTGOING records in one login.',
'The missed, rejected, and answered attempts show the backend dispositions returned by Call History.',
'Every record has valid start and end timestamps.',
'The answered call includes valid non-negative duration derived from the timestamps.',
'The sample app Call History table renders all three outgoing records for User 1.',
],
},
{
id: 'CH-ALL-002',
title: 'User 2 login shows missed, rejected, and answered outgoing call history',
preconditions: ['Both users stay initialized, registered, and media-ready in one browser run.'],
steps: [
'User 2 calls User 1 and User 1 does not answer.',
'User 2 calls User 1 again and User 1 rejects.',
'User 2 calls User 1 again and User 1 answers.',
'After both users complete their outgoing call journeys, open Call History for User 2.',
],
expected: [
'User 2 shows three recent OUTGOING records in one login.',
'The missed, rejected, and answered attempts show the backend dispositions returned by Call History.',
'Every record has valid start and end timestamps.',
'The answered call includes valid non-negative duration derived from the timestamps.',
'The sample app Call History table renders all three outgoing records for User 2.',
],
},
] as const;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The specs shouldn't be in the code.

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.

Fixed. I removed the specs metadata from the test group and replaced it with explicit Playwright test cases. Since GitHub marks this thread as outdated, this should be covered by the latest changes.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9faf23f07e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const REJECTED_CALLEE_DISPOSITIONS = ['REJECTED', 'MISSED', 'CANCELED'];
const HISTORY_TIME_LOOKBACK_MS = 5000;
const RECENT_RECORD_TOLERANCE_MS = 120000;
const COUNTERPART_MATCH_MIN_DIGITS = 4;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Match counterpart with at least seven phone digits

Using only the last 4 digits to identify the counterpart can match unrelated call-history rows whenever two destinations share a suffix (common with enterprise extensions/DIDs), so waitForCallHistoryRecord() can resolve the wrong record and let assertions pass without validating the intended call. The helper default is already 7 digits, but this suite overrides it to 4, which materially weakens record identity in serial flows with many recent calls.

Useful? React with 👍 / 👎.

record.other?.callbackAddress,
record.other?.primaryDisplayString,
record.other?.secondaryDisplayString,
record.other?.name,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude display name from numeric phone matching

Including record.other?.name in phoneMatchesRecord() can produce false matches because this field is a display label, not a canonical phone/address field, and it may contain digits (e.g., extensions/team labels). Since matching is suffix-based, a numeric name can satisfy the predicate even when the real callback/phone fields belong to a different call, causing history polling to select the wrong row.

Useful? React with 👍 / 👎.

// Account roles resolved from testInfo.project.name → USER_SETS.
callLifecycleTests();
callLifecycleMediaTests();
callHistoryTests();
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.

We can use a different suite for this. The call history tests can be run in parallel

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 moved Call History into its own suite: set-2user-call-history.spec.ts, and added separate SET_2USER_CALL_HISTORY Playwright projects so it can run independently/parallel where account dependencies allow.

.locator(CALLING_SELECTORS.DESTINATION_INPUT)
.fill(destination, {timeout: AWAIT_TIMEOUT});
await page.locator(CALLING_SELECTORS.MAKE_CALL_BTN).click({timeout: AWAIT_TIMEOUT});
await expect(page.locator(CALLING_SELECTORS.MAKE_CALL_BTN)).toBeDisabled({
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 did we need these changes ? How are these related to call history tests ?

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.

These were not directly related to Call History. I removed the unrelated call.ts changes and kept the Call History-specific waits/assertions inside the Call History utilities/tests.

* Navigate to the calling sample app
*/
export const navigateToCallingApp = async (page: Page): Promise<void> => {
await page.route('**/samples/calling.min.js', (route) =>
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.

Same comment here. What is this change about ?

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.

This override is not needed because the default sample app already supports CallHistory. I removed the page.route bundle override from navigateToCallingApp.

} from '../utils/call-history';
import {CALLING_SELECTORS} from '../constants';

const CALL_HISTORY_AI_SPECS = [
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 do we have these specs added in the testgroup ? Shouldn't this have testcases ?

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.

Those spec objects were redundant metadata and did not belong in the test group. I removed them and changed the file to use explicit Playwright test cases with the test IDs in the test names.

},
] as const;

const ANSWERED_DISPOSITIONS = ['ANSWERED', 'INITIATED'];
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.

These constants should be in constants file

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. I moved the Call History disposition/timing constants into playwright/constants/call-history.ts and export them through playwright/constants/index.ts.


type UserLabel = HistoryDebugRecord['user'];

type CallJourneyLeg = {
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.

These types also shouldn't be pasrt of test group. If we these types for testcases, then move to a separate file and import from there

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. I moved the Call History journey/matcher/debug types into playwright/utils/call-history-types.ts and import them where needed.

return matchingRecord as CallHistoryRecord;
};

const clearCallHistoryTable = async (page: Page): Promise<void> => {
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 do we have so many util methods created ? I see lot of them here as well as in test-group itself

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 cleaned this up. The test group now mostly contains the actual test cases. Shared low-level API/UI helpers remain in utils/call-history.ts, and journey-specific orchestration is isolated in utils/call-history-journey.ts so the test cases stay readable and we avoid duplicating polling, matching, and UI verification logic.

@Kesari3008 Kesari3008 added the validated If the pull request is validated for automation. label May 26, 2026
@@ -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.

@@ -0,0 +1,45 @@
import type {Page} from '@playwright/test';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nitpick: are the types in these files used in multiple other files? im seeing type definitions in packages/calling/playwright/utils/call-history.ts as well? either those can be moved here or these definitions can be moved to the file they are used it.

Comment on lines +65 to +69
const HISTORY_URL_PATTERN = '**/history/userSessions**';
const HISTORY_EVENTUAL_CONSISTENCY_TIMEOUT = 150000;
const HISTORY_POLL_INTERVALS = [5000, 5000, 10000, 10000, 15000];
const TIMING_TOLERANCE_MS = 120000;
const DURATION_TOLERANCE_SECONDS = 120;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

move these to constants as well


// 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_2USER_CALL_HISTORY: {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

you can call it SET_CALL_HISTORY since we are dropping the 2USER/3USER naming convention

// 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_2USER_CALL_HISTORY - PROD',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same here.

you can call it SET_CALL_HISTORY since we are dropping the 2USER/3USER naming convention

},
// INT aliases overlap between USER_1/2 and USER_4/5, so keep INT ordered.
{
name: 'SET_2USER_CALL_HISTORY - INT',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same as above.

Copy link
Copy Markdown
Member

@eigengravy eigengravy left a comment

Choose a reason for hiding this comment

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

Approving with comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

validated If the pull request is validated for automation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants