From d2f5f9592dad012917f317cc09fe5794a14c8aba Mon Sep 17 00:00:00 2001 From: Sarang S Date: Wed, 11 Mar 2026 17:47:21 +0530 Subject: [PATCH 1/7] outdial ui automation --- .../widgets/OutdialCall/ARCHITECTURE.md | 75 +++++++++++++ playwright/Utils/outdialUtils.ts | 50 +++++++++ playwright/ai-docs/ARCHITECTURE.md | 13 ++- playwright/global.setup.ts | 22 ++++ .../station-login-user-state-tests.spec.ts | 4 +- playwright/test-manager.ts | 40 +++++++ playwright/tests/outdial-call-test.spec.ts | 104 ++++++++++++++++++ 7 files changed, 304 insertions(+), 4 deletions(-) create mode 100644 playwright/Utils/outdialUtils.ts create mode 100644 playwright/tests/outdial-call-test.spec.ts diff --git a/packages/contact-center/task/ai-docs/widgets/OutdialCall/ARCHITECTURE.md b/packages/contact-center/task/ai-docs/widgets/OutdialCall/ARCHITECTURE.md index 51dd637b5..248ad93f0 100644 --- a/packages/contact-center/task/ai-docs/widgets/OutdialCall/ARCHITECTURE.md +++ b/packages/contact-center/task/ai-docs/widgets/OutdialCall/ARCHITECTURE.md @@ -130,6 +130,81 @@ sequenceDiagram end ``` +### Post-Dial Flow by Login Mode + +After `cc.startOutdial()` succeeds, the platform establishes a first-leg call to the agent before dialing the customer (second leg). How the first leg connects depends on the agent's login mode. + +#### Desktop Mode -- Customer Rings Directly + +In Desktop mode, the agent is auto-connected. The customer's phone rings immediately, and once the customer answers, the agent reaches ENGAGED state. + +```mermaid +sequenceDiagram + participant A as Agent + participant W as Widget / Store + participant P as CC Platform + participant C as Customer + + A->>W: Click dial button + W->>P: cc.startOutdial(destination, origin) + P-->>W: TaskResponse + P->>C: Customer's phone rings (second leg) + C->>P: Customer answers + P->>W: Agent auto-connects → ENGAGED +``` + +The agent never needs to accept the incoming task. The Accept button is visible but disabled during the brief popup. + +#### Extension Mode -- Manual Answer Required + +The first-leg call rings on the agent's Webex Calling extension. The agent must answer it before the platform dials the customer. + +```mermaid +sequenceDiagram + participant A as Agent + participant W as Widget / Store + participant E as Webex Calling Extension + participant P as CC Platform + participant C as Customer + + A->>W: Click dial button + W->>P: cc.startOutdial(destination, origin) + P-->>W: TaskResponse + P->>E: First-leg rings on extension + Note over E: Answer button becomes enabled + A->>E: Answer call on extension + P->>C: Customer's phone rings (second leg) + C->>P: Customer answers + P->>W: Agent state → ENGAGED +``` + +The agent must answer the extension call before the customer is dialed. + +#### Dial Number (DN) Mode -- Manual Answer Required + +The first-leg call rings on the agent's DN phone. The agent must answer it before the platform dials the customer. + +```mermaid +sequenceDiagram + participant A as Agent + participant W as Widget / Store + participant D as Agent DN Phone + participant P as CC Platform + participant C as Customer + + A->>W: Click dial button + W->>P: cc.startOutdial(destination, origin) + P-->>W: TaskResponse + P->>D: First-leg rings on DN phone + Note over D: Answer button becomes enabled + A->>D: Answer call on DN phone + P->>C: Customer's phone rings (second leg) + C->>P: Customer answers + P->>W: Agent state → ENGAGED +``` + +The agent must answer the DN phone call before the customer is dialed. + ### Number Validation ```mermaid diff --git a/playwright/Utils/outdialUtils.ts b/playwright/Utils/outdialUtils.ts new file mode 100644 index 000000000..60cc60859 --- /dev/null +++ b/playwright/Utils/outdialUtils.ts @@ -0,0 +1,50 @@ +import {Page, expect} from '@playwright/test'; +import {ACCEPT_TASK_TIMEOUT, AWAIT_TIMEOUT, UI_SETTLE_TIMEOUT} from '../constants'; + +/** + * Enters a phone number into the outdial number input field. + * Prerequisite: Agent must be logged in and outdial-call-container must be visible. + * @param page Playwright Page object (agent widget page) + * @param number Phone number to dial (e.g., +14698041796) + */ +export async function enterOutdialNumber(page: Page, number: string): Promise { + await page.bringToFront(); + await expect(page.getByTestId('outdial-call-container')).toBeVisible({timeout: AWAIT_TIMEOUT}); + const input = page.getByTestId('outdial-number-input').locator('input'); + await input.fill(number, {timeout: AWAIT_TIMEOUT}); +} + +/** + * Clicks the outdial call button to initiate the outbound call. + * Prerequisite: A valid number must be entered in the outdial input. + * @param page Playwright Page object (agent widget page) + */ +export async function clickOutdialButton(page: Page): Promise { + await page.bringToFront(); + const dialButton = page.getByTestId('outdial-call-button'); + await expect(dialButton).toBeEnabled({timeout: AWAIT_TIMEOUT}); + await dialButton.click({timeout: AWAIT_TIMEOUT}); +} + +/** + * Accepts an incoming call on the customer's Webex Calling web client. + * Used for outdial scenarios where the customer receives the outbound call. + * @param customerPage Playwright Page object (customer's Webex Calling web client) + */ +export async function acceptCustomerCall(customerPage: Page): Promise { + await customerPage.bringToFront(); + await expect(customerPage.locator('#answer').first()).toBeEnabled({timeout: ACCEPT_TASK_TIMEOUT}); + await customerPage.waitForTimeout(UI_SETTLE_TIMEOUT); + await customerPage.locator('#answer').first().click({timeout: AWAIT_TIMEOUT}); +} + +/** + * Ends the call on the customer's Webex Calling web client. + * @param customerPage Playwright Page object (customer's Webex Calling web client) + */ +export async function endCustomerCall(customerPage: Page): Promise { + await customerPage.bringToFront(); + const endBtn = customerPage.locator('#end-call').first(); + await expect(endBtn).toBeEnabled({timeout: AWAIT_TIMEOUT}); + await endBtn.click({timeout: AWAIT_TIMEOUT}); +} diff --git a/playwright/ai-docs/ARCHITECTURE.md b/playwright/ai-docs/ARCHITECTURE.md index 193059aa0..ad40c06c5 100644 --- a/playwright/ai-docs/ARCHITECTURE.md +++ b/playwright/ai-docs/ARCHITECTURE.md @@ -52,6 +52,7 @@ playwright/ │ ├── basic-task-controls-test.spec.ts │ ├── advanced-task-controls-test.spec.ts │ ├── advance-task-control-combinations-test.spec.ts +│ ├── outdial-call-test.spec.ts │ ├── dial-number-task-control-test.spec.ts │ ├── tasklist-test.spec.ts │ ├── multiparty-conference-set-7-test.spec.ts @@ -66,6 +67,7 @@ playwright/ │ ├── userStateUtils.ts │ ├── taskControlUtils.ts │ ├── advancedTaskControlUtils.ts +│ ├── outdialUtils.ts │ └── wrapupUtils.ts ├── test-manager.ts ├── test-data.ts @@ -86,7 +88,7 @@ Keep this section aligned to real repository contents. | ------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------- | | `SET_1` | `digital-incoming-task-tests.spec.ts` | `digital-incoming-task-and-task-controls.spec.ts`, `dial-number-task-control-test.spec.ts` | | `SET_2` | `task-list-multi-session-tests.spec.ts` | `incoming-task-and-controls-multi-session.spec.ts`, `tasklist-test.spec.ts` | -| `SET_3` | `station-login-user-state-tests.spec.ts` | `station-login-test.spec.ts`, `user-state-test.spec.ts`, `incoming-telephony-task-test.spec.ts` | +| `SET_3` | `station-login-user-state-tests.spec.ts` | `station-login-test.spec.ts`, `user-state-test.spec.ts`, `incoming-telephony-task-test.spec.ts`, `outdial-call-test.spec.ts` | | `SET_4` | `basic-advanced-task-controls-tests.spec.ts` | `basic-task-controls-test.spec.ts`, `advance-task-control-combinations-test.spec.ts` | | `SET_5` | `advanced-task-controls-tests.spec.ts` | `advanced-task-controls-test.spec.ts` | | `SET_6` | `dial-number-tests.spec.ts` | `dial-number-task-control-test.spec.ts` | @@ -150,7 +152,8 @@ These flags are part of baseline runtime behavior and should be preserved unless - `[SET_7, SET_8]` - `[SET_9]` 3. Optionally fetches dial-number OAuth token -4. Performs one final `.env` upsert in the same OAuth setup run +4. Optionally fetches customer outdial OAuth token (for outdial E2E tests) +5. Performs one final `.env` upsert in the same OAuth setup run Test files: @@ -232,6 +235,9 @@ When enabled by setup config/method, these page properties are created and avail | `setupForIncomingTaskExtension()` | Calls `setup()` for extension incoming-task flow | | `setupForIncomingTaskMultiSession()` | Calls `setup()` for multi-session incoming-task flow | | `setupForStationLogin()` | Custom path (does not call `setup()`), purpose-built station-login + multi-login bootstrap. Station-login page initialization runs sequentially (main then multi-session) to reduce init contention. | +| `setupForOutdialDesktop()` | Calls `setup()` with desktop agent1 + outdial customer login | +| `setupForOutdialExtension()` | Calls `setup()` with extension agent1 + outdial customer login | +| `setupForOutdialDN()` | Calls `setup()` with dial-number agent1 + dial-number phone login + outdial customer login | | `setupForMultipartyConference()` | Sets up 4 agents + caller for conference tests (agent1–4 pages + callerPage) | | `setupMultiSessionPage()` | Targeted helper to initialize only multi-session page when needed | @@ -263,6 +269,7 @@ When enabled by setup config/method, these page properties are created and avail | `taskControlUtils.ts` | `holdCallToggle`, `recordCallToggle`, `isCallHeld`, `endTask`, `verifyHoldTimer`, `verifyHoldButtonIcon`, `verifyRecordButtonIcon`, `setupConsoleLogging`, `verifyHoldLogs`, `verifyRecordingLogs`, `verifyEndLogs`, `verifyRemoteAudioTracks` | Basic call control actions + callback/event log assertions. `endTask` now stays generic and assumes the caller has already restored the page to a normal endable state. | | `advancedTaskControlUtils.ts` | `consultOrTransfer`, `cancelConsult`, `waitForPrimaryCallAfterConsult`, `setupAdvancedConsoleLogging`, `verifyTransferSuccessLogs`, `verifyConsultStartSuccessLogs`, `verifyConsultEndSuccessLogs`, `verifyConsultTransferredLogs`, `ACTIVE_CONSULT_CONTROL_TEST_IDS` | Consult/transfer operations + advanced callback/event log assertions. Includes consult-state polling and post-consult primary-call restoration before generic end-task operations. | | `incomingTaskUtils.ts` | `createCallTask`, `createChatTask`, `createEmailTask`, `waitForIncomingTask`, `acceptIncomingTask`, `declineIncomingTask`, `acceptExtensionCall`, `loginExtension`, `submitRonaPopup` | Incoming task creation/acceptance/decline and extension helpers | +| `outdialUtils.ts` | `enterOutdialNumber`, `clickOutdialButton`, `acceptCustomerCall`, `endCustomerCall` | Outdial call helpers for entering number, clicking dial, accepting/ending customer-side calls | | `wrapupUtils.ts` | `submitWrapup` | Wrapup submission | | `helperUtils.ts` | `handleStrayTasks`, `pageSetup`, `waitForState`, `waitForStateLogs`, `waitForWebSocketDisconnection`, `waitForWebSocketReconnection`, `clearPendingCallAndWrapup`, `dismissOverlays` | Shared setup/cleanup/state polling/network-watch helpers. `waitForState` polls visible state text (`state-name`) to align with `verifyCurrentState`. `pageSetup` includes one bounded station logout/re-login recovery if `state-select` is still missing after login. `handleStrayTasks` handles exit-conference, dual call control groups (iterates all end-call buttons to find enabled one), cancel-consult with switch-leg fallback. | | `conferenceUtils.ts` | `cleanupConferenceState`, `startBaselineCallOnAgent1`, `consultAgentAndAcceptCall`, `consultQueueAndAcceptCall`, `mergeConsultIntoConference`, `transferConsultAndSubmitWrapup`, `toggleConferenceLegIfSwitchAvailable`, `exitConferenceParticipantAndWrapup`, `endConferenceTaskAndWrapup` | Shared conference helpers used by Set 7, Set 8, and Set 9 to keep call setup/cleanup and consult-transfer flows consistent and reusable. Conference callers now choose explicit exit-vs-end behavior instead of using one mixed helper. | @@ -432,4 +439,4 @@ After a call ends, the Make Call button on the caller page may stay disabled. Cl --- -_Last Updated: 2026-03-09_ +_Last Updated: 2026-03-11_ diff --git a/playwright/global.setup.ts b/playwright/global.setup.ts index 8a4a51c84..527d81d92 100644 --- a/playwright/global.setup.ts +++ b/playwright/global.setup.ts @@ -92,6 +92,21 @@ const buildDialNumberTask = (): OAuthTask | null => { return null; }; +const buildCustomerOutdialTask = (): OAuthTask | null => { + const username = process.env.PW_DIAL_NUMBER_LOGIN_USERNAME1; + const password = process.env.PW_DIAL_NUMBER_LOGIN_PASSWORD1; + + if (username && password) { + return { + envKey: 'CUSTOMER_OUTDIAL_ACCESS_TOKEN', + username, + password, + }; + } + + return null; +}; + const fetchOAuthAccessToken = async (browser: Browser, username: string, password?: string): Promise => { const context = await browser.newContext({ignoreHTTPSErrors: true}); const page = await context.newPage(); @@ -170,6 +185,13 @@ setup('OAuth', async ({browser}) => { tokenUpdates[dialNumberTask.envKey] = dialNumberToken; } + // Fetch customer outdial token (if configured) + const customerOutdialTask = buildCustomerOutdialTask(); + if (customerOutdialTask) { + const customerToken = await fetchOAuthAccessToken(browser, customerOutdialTask.username, customerOutdialTask.password); + tokenUpdates[customerOutdialTask.envKey] = customerToken; + } + const allUpdates = {...userSetUpdates, ...tokenUpdates}; // Write everything at once diff --git a/playwright/suites/station-login-user-state-tests.spec.ts b/playwright/suites/station-login-user-state-tests.spec.ts index 5a72d716f..ea15e26d6 100644 --- a/playwright/suites/station-login-user-state-tests.spec.ts +++ b/playwright/suites/station-login-user-state-tests.spec.ts @@ -1,8 +1,10 @@ -import {test} from '@playwright/test'; +import { test } from '@playwright/test'; import createStationLoginTests from '../tests/station-login-test.spec'; import createUserStateTests from '../tests/user-state-test.spec'; import createIncomingTelephonyTaskTests from '../tests/incoming-telephony-task-test.spec'; +import createOutdialCallTests from '../tests/outdial-call-test.spec'; test.describe('Station Login Tests', createStationLoginTests); test.describe('User State Tests', createUserStateTests); test.describe('Incoming Telephony Task Tests', createIncomingTelephonyTaskTests); +test.describe('Outdial Call Tests', createOutdialCallTests); diff --git a/playwright/test-manager.ts b/playwright/test-manager.ts index 51986d993..a0dd1defc 100644 --- a/playwright/test-manager.ts +++ b/playwright/test-manager.ts @@ -383,6 +383,9 @@ export class TestManager { 'agent1 extension login' ), ]); + } else if (config.agent1LoginMode === LOGIN_MODE.DIAL_NUMBER) { + const dialNumber = process.env.PW_DIAL_NUMBER_PSTN ?? ''; + await pageSetup(this.agent1Page, LOGIN_MODE.DIAL_NUMBER, envTokens.agent1AccessToken, null, dialNumber); } } @@ -520,6 +523,43 @@ export class TestManager { }); } + async setupForOutdialDesktop(browser: Browser): Promise { + await this.setup(browser, { + needsAgent1: true, + agent1LoginMode: LOGIN_MODE.DESKTOP, + }); + await this.setupOutdialCustomer(browser); + } + + async setupForOutdialExtension(browser: Browser): Promise { + await this.setup(browser, { + needsAgent1: true, + needsExtension: true, + agent1LoginMode: LOGIN_MODE.EXTENSION, + }); + await this.setupOutdialCustomer(browser); + } + + async setupForOutdialDN(browser: Browser): Promise { + await this.setup(browser, { + needsAgent1: true, + agent1LoginMode: LOGIN_MODE.DIAL_NUMBER, + needDialNumberLogin: true, + }); + await this.setupOutdialCustomer(browser); + } + + private async setupOutdialCustomer(browser: Browser): Promise { + const customerToken = process.env.CUSTOMER_OUTDIAL_ACCESS_TOKEN ?? ''; + const result = await this.createContextWithPage(browser, PAGE_TYPES.CALLER); + this.callerExtensionContext = result.context; + this.callerPage = result.page; + await this.retryOperation( + () => loginExtension(this.callerPage, customerToken), + 'outdial customer login' + ); + } + async setupForMultipartyConference(browser: Browser) { await this.setup(browser, { needsAgent1: true, diff --git a/playwright/tests/outdial-call-test.spec.ts b/playwright/tests/outdial-call-test.spec.ts new file mode 100644 index 000000000..a9a0df841 --- /dev/null +++ b/playwright/tests/outdial-call-test.spec.ts @@ -0,0 +1,104 @@ +import {test, expect} from '@playwright/test'; +import {changeUserState, verifyCurrentState} from '../Utils/userStateUtils'; +import {acceptExtensionCall, endCallTask} from '../Utils/incomingTaskUtils'; +import {USER_STATES, WRAPUP_REASONS} from '../constants'; +import {submitWrapup, waitForWrapupAfterCallEnd} from '../Utils/wrapupUtils'; +import {waitForState} from '../Utils/helperUtils'; +import {TestManager} from '../test-manager'; +import {enterOutdialNumber, clickOutdialButton, acceptCustomerCall} from '../Utils/outdialUtils'; + +export default function createOutdialCallTests() { + test.describe('Outdial Call - Desktop Mode', () => { + let testManager: TestManager; + + test.beforeAll(async ({browser}, testInfo) => { + const projectName = testInfo.project.name; + testManager = new TestManager(projectName); + await testManager.setupForOutdialDesktop(browser); + }); + + test('should make an outdial call in Desktop mode and complete wrapup', async () => { + await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); + await expect(testManager.agent1Page.getByTestId('outdial-call-container')).toBeVisible(); + await enterOutdialNumber(testManager.agent1Page, process.env.PW_DIAL_NUMBER_PSTN1!); + await clickOutdialButton(testManager.agent1Page); + await acceptCustomerCall(testManager.callerPage); + await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); + await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); + await testManager.agent1Page.waitForTimeout(3000); + await testManager.agent1Page.getByTestId('call-control:end-call').first().click({timeout: 5000}); + await testManager.agent1Page.waitForTimeout(2000); + await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); + await waitForState(testManager.agent1Page, USER_STATES.AVAILABLE); + await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); + }); + + test.afterAll(async () => { + await testManager.cleanup(); + }); + }); + + test.describe('Outdial Call - Extension Mode', () => { + let testManager: TestManager; + + test.beforeAll(async ({browser}, testInfo) => { + const projectName = testInfo.project.name; + testManager = new TestManager(projectName); + await testManager.setupForOutdialExtension(browser); + }); + + test('should make an outdial call in Extension mode and complete wrapup', async () => { + await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); + await expect(testManager.agent1Page.getByTestId('outdial-call-container')).toBeVisible(); + await enterOutdialNumber(testManager.agent1Page, process.env.PW_DIAL_NUMBER_PSTN1!); + await clickOutdialButton(testManager.agent1Page); + await expect(testManager.agent1ExtensionPage.locator('#answer').first()).toBeEnabled({timeout: 40000}); + await acceptExtensionCall(testManager.agent1ExtensionPage); + await acceptCustomerCall(testManager.callerPage); + await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); + await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); + await testManager.agent1Page.waitForTimeout(3000); + await endCallTask(testManager.agent1ExtensionPage); + await waitForWrapupAfterCallEnd(testManager.agent1Page); + await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); + await waitForState(testManager.agent1Page, USER_STATES.AVAILABLE); + await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); + }); + + test.afterAll(async () => { + await testManager.cleanup(); + }); + }); + + test.describe('Outdial Call - Dial Number Mode', () => { + let testManager: TestManager; + + test.beforeAll(async ({browser}, testInfo) => { + const projectName = testInfo.project.name; + testManager = new TestManager(projectName); + await testManager.setupForOutdialDN(browser); + }); + + test('should make an outdial call in Dial Number mode and complete wrapup', async () => { + await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); + await expect(testManager.agent1Page.getByTestId('outdial-call-container')).toBeVisible(); + await enterOutdialNumber(testManager.agent1Page, process.env.PW_DIAL_NUMBER_PSTN1!); + await clickOutdialButton(testManager.agent1Page); + await expect(testManager.dialNumberPage.locator('#answer').first()).toBeEnabled({timeout: 40000}); + await acceptExtensionCall(testManager.dialNumberPage); + await acceptCustomerCall(testManager.callerPage); + await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); + await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); + await testManager.agent1Page.waitForTimeout(3000); + await testManager.agent1Page.getByTestId('call-control:end-call').first().click({timeout: 5000}); + await testManager.agent1Page.waitForTimeout(2000); + await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); + await waitForState(testManager.agent1Page, USER_STATES.AVAILABLE); + await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); + }); + + test.afterAll(async () => { + await testManager.cleanup(); + }); + }); +} From 43f98cde15dd1482a97776847e4306d7c89a4533 Mon Sep 17 00:00:00 2001 From: Sarang S Date: Wed, 11 Mar 2026 18:31:18 +0530 Subject: [PATCH 2/7] fix: move to dn suite and remove dn login --- playwright/global.setup.ts | 22 ------------ playwright/suites/dial-number-tests.spec.ts | 2 ++ .../station-login-user-state-tests.spec.ts | 2 -- playwright/test-manager.ts | 11 +----- playwright/tests/outdial-call-test.spec.ts | 35 ++----------------- 5 files changed, 5 insertions(+), 67 deletions(-) diff --git a/playwright/global.setup.ts b/playwright/global.setup.ts index 527d81d92..8a4a51c84 100644 --- a/playwright/global.setup.ts +++ b/playwright/global.setup.ts @@ -92,21 +92,6 @@ const buildDialNumberTask = (): OAuthTask | null => { return null; }; -const buildCustomerOutdialTask = (): OAuthTask | null => { - const username = process.env.PW_DIAL_NUMBER_LOGIN_USERNAME1; - const password = process.env.PW_DIAL_NUMBER_LOGIN_PASSWORD1; - - if (username && password) { - return { - envKey: 'CUSTOMER_OUTDIAL_ACCESS_TOKEN', - username, - password, - }; - } - - return null; -}; - const fetchOAuthAccessToken = async (browser: Browser, username: string, password?: string): Promise => { const context = await browser.newContext({ignoreHTTPSErrors: true}); const page = await context.newPage(); @@ -185,13 +170,6 @@ setup('OAuth', async ({browser}) => { tokenUpdates[dialNumberTask.envKey] = dialNumberToken; } - // Fetch customer outdial token (if configured) - const customerOutdialTask = buildCustomerOutdialTask(); - if (customerOutdialTask) { - const customerToken = await fetchOAuthAccessToken(browser, customerOutdialTask.username, customerOutdialTask.password); - tokenUpdates[customerOutdialTask.envKey] = customerToken; - } - const allUpdates = {...userSetUpdates, ...tokenUpdates}; // Write everything at once diff --git a/playwright/suites/dial-number-tests.spec.ts b/playwright/suites/dial-number-tests.spec.ts index 4ea3b9736..b914ecab2 100644 --- a/playwright/suites/dial-number-tests.spec.ts +++ b/playwright/suites/dial-number-tests.spec.ts @@ -1,4 +1,6 @@ import {test} from '@playwright/test'; import createDialNumberTaskControlTests from '../tests/dial-number-task-control-test.spec'; +import createOutdialCallTests from '../tests/outdial-call-test.spec'; test.describe('Dial Number Task Control Tests', createDialNumberTaskControlTests); +test.describe('Outdial Call Tests', createOutdialCallTests); diff --git a/playwright/suites/station-login-user-state-tests.spec.ts b/playwright/suites/station-login-user-state-tests.spec.ts index ea15e26d6..3cc53dff2 100644 --- a/playwright/suites/station-login-user-state-tests.spec.ts +++ b/playwright/suites/station-login-user-state-tests.spec.ts @@ -2,9 +2,7 @@ import { test } from '@playwright/test'; import createStationLoginTests from '../tests/station-login-test.spec'; import createUserStateTests from '../tests/user-state-test.spec'; import createIncomingTelephonyTaskTests from '../tests/incoming-telephony-task-test.spec'; -import createOutdialCallTests from '../tests/outdial-call-test.spec'; test.describe('Station Login Tests', createStationLoginTests); test.describe('User State Tests', createUserStateTests); test.describe('Incoming Telephony Task Tests', createIncomingTelephonyTaskTests); -test.describe('Outdial Call Tests', createOutdialCallTests); diff --git a/playwright/test-manager.ts b/playwright/test-manager.ts index a0dd1defc..9f09e4f03 100644 --- a/playwright/test-manager.ts +++ b/playwright/test-manager.ts @@ -540,17 +540,8 @@ export class TestManager { await this.setupOutdialCustomer(browser); } - async setupForOutdialDN(browser: Browser): Promise { - await this.setup(browser, { - needsAgent1: true, - agent1LoginMode: LOGIN_MODE.DIAL_NUMBER, - needDialNumberLogin: true, - }); - await this.setupOutdialCustomer(browser); - } - private async setupOutdialCustomer(browser: Browser): Promise { - const customerToken = process.env.CUSTOMER_OUTDIAL_ACCESS_TOKEN ?? ''; + const customerToken = process.env.DIAL_NUMBER_LOGIN_ACCESS_TOKEN ?? ''; const result = await this.createContextWithPage(browser, PAGE_TYPES.CALLER); this.callerExtensionContext = result.context; this.callerPage = result.page; diff --git a/playwright/tests/outdial-call-test.spec.ts b/playwright/tests/outdial-call-test.spec.ts index a9a0df841..495cdfeaf 100644 --- a/playwright/tests/outdial-call-test.spec.ts +++ b/playwright/tests/outdial-call-test.spec.ts @@ -20,7 +20,7 @@ export default function createOutdialCallTests() { test('should make an outdial call in Desktop mode and complete wrapup', async () => { await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await expect(testManager.agent1Page.getByTestId('outdial-call-container')).toBeVisible(); - await enterOutdialNumber(testManager.agent1Page, process.env.PW_DIAL_NUMBER_PSTN1!); + await enterOutdialNumber(testManager.agent1Page, process.env.PW_DIAL_NUMBER_PSTN!); await clickOutdialButton(testManager.agent1Page); await acceptCustomerCall(testManager.callerPage); await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); @@ -50,7 +50,7 @@ export default function createOutdialCallTests() { test('should make an outdial call in Extension mode and complete wrapup', async () => { await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await expect(testManager.agent1Page.getByTestId('outdial-call-container')).toBeVisible(); - await enterOutdialNumber(testManager.agent1Page, process.env.PW_DIAL_NUMBER_PSTN1!); + await enterOutdialNumber(testManager.agent1Page, process.env.PW_DIAL_NUMBER_PSTN!); await clickOutdialButton(testManager.agent1Page); await expect(testManager.agent1ExtensionPage.locator('#answer').first()).toBeEnabled({timeout: 40000}); await acceptExtensionCall(testManager.agent1ExtensionPage); @@ -70,35 +70,4 @@ export default function createOutdialCallTests() { }); }); - test.describe('Outdial Call - Dial Number Mode', () => { - let testManager: TestManager; - - test.beforeAll(async ({browser}, testInfo) => { - const projectName = testInfo.project.name; - testManager = new TestManager(projectName); - await testManager.setupForOutdialDN(browser); - }); - - test('should make an outdial call in Dial Number mode and complete wrapup', async () => { - await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - await expect(testManager.agent1Page.getByTestId('outdial-call-container')).toBeVisible(); - await enterOutdialNumber(testManager.agent1Page, process.env.PW_DIAL_NUMBER_PSTN1!); - await clickOutdialButton(testManager.agent1Page); - await expect(testManager.dialNumberPage.locator('#answer').first()).toBeEnabled({timeout: 40000}); - await acceptExtensionCall(testManager.dialNumberPage); - await acceptCustomerCall(testManager.callerPage); - await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); - await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); - await testManager.agent1Page.waitForTimeout(3000); - await testManager.agent1Page.getByTestId('call-control:end-call').first().click({timeout: 5000}); - await testManager.agent1Page.waitForTimeout(2000); - await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); - await waitForState(testManager.agent1Page, USER_STATES.AVAILABLE); - await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); - }); - - test.afterAll(async () => { - await testManager.cleanup(); - }); - }); } From 07c2e67c4b01d039784ffa9b4984dfe5e81c14fb Mon Sep 17 00:00:00 2001 From: Sarang S Date: Wed, 11 Mar 2026 19:08:22 +0530 Subject: [PATCH 3/7] fix: remove DN outdial mode, update architecture docs --- playwright/ai-docs/ARCHITECTURE.md | 5 ++--- playwright/test-manager.ts | 3 --- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/playwright/ai-docs/ARCHITECTURE.md b/playwright/ai-docs/ARCHITECTURE.md index ad40c06c5..fec545af7 100644 --- a/playwright/ai-docs/ARCHITECTURE.md +++ b/playwright/ai-docs/ARCHITECTURE.md @@ -88,10 +88,10 @@ Keep this section aligned to real repository contents. | ------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------- | | `SET_1` | `digital-incoming-task-tests.spec.ts` | `digital-incoming-task-and-task-controls.spec.ts`, `dial-number-task-control-test.spec.ts` | | `SET_2` | `task-list-multi-session-tests.spec.ts` | `incoming-task-and-controls-multi-session.spec.ts`, `tasklist-test.spec.ts` | -| `SET_3` | `station-login-user-state-tests.spec.ts` | `station-login-test.spec.ts`, `user-state-test.spec.ts`, `incoming-telephony-task-test.spec.ts`, `outdial-call-test.spec.ts` | +| `SET_3` | `station-login-user-state-tests.spec.ts` | `station-login-test.spec.ts`, `user-state-test.spec.ts`, `incoming-telephony-task-test.spec.ts` | | `SET_4` | `basic-advanced-task-controls-tests.spec.ts` | `basic-task-controls-test.spec.ts`, `advance-task-control-combinations-test.spec.ts` | | `SET_5` | `advanced-task-controls-tests.spec.ts` | `advanced-task-controls-test.spec.ts` | -| `SET_6` | `dial-number-tests.spec.ts` | `dial-number-task-control-test.spec.ts` | +| `SET_6` | `dial-number-tests.spec.ts` | `dial-number-task-control-test.spec.ts`, `outdial-call-test.spec.ts` | | `SET_7` | `multiparty-conference-set-7-tests.spec.ts` | `multiparty-conference-set-7-test.spec.ts` | | `SET_8` | `multiparty-conference-set-8-tests.spec.ts` | `multiparty-conference-set-8-test.spec.ts` | | `SET_9` | `multiparty-conference-set-9-tests.spec.ts` | `multiparty-conference-set-9-test.spec.ts` | @@ -237,7 +237,6 @@ When enabled by setup config/method, these page properties are created and avail | `setupForStationLogin()` | Custom path (does not call `setup()`), purpose-built station-login + multi-login bootstrap. Station-login page initialization runs sequentially (main then multi-session) to reduce init contention. | | `setupForOutdialDesktop()` | Calls `setup()` with desktop agent1 + outdial customer login | | `setupForOutdialExtension()` | Calls `setup()` with extension agent1 + outdial customer login | -| `setupForOutdialDN()` | Calls `setup()` with dial-number agent1 + dial-number phone login + outdial customer login | | `setupForMultipartyConference()` | Sets up 4 agents + caller for conference tests (agent1–4 pages + callerPage) | | `setupMultiSessionPage()` | Targeted helper to initialize only multi-session page when needed | diff --git a/playwright/test-manager.ts b/playwright/test-manager.ts index 9f09e4f03..f54de5bb9 100644 --- a/playwright/test-manager.ts +++ b/playwright/test-manager.ts @@ -383,9 +383,6 @@ export class TestManager { 'agent1 extension login' ), ]); - } else if (config.agent1LoginMode === LOGIN_MODE.DIAL_NUMBER) { - const dialNumber = process.env.PW_DIAL_NUMBER_PSTN ?? ''; - await pageSetup(this.agent1Page, LOGIN_MODE.DIAL_NUMBER, envTokens.agent1AccessToken, null, dialNumber); } } From 217573a34ee2e724ffd468e5752f653c8df2a6a7 Mon Sep 17 00:00:00 2001 From: Sarang S Date: Wed, 11 Mar 2026 19:11:19 +0530 Subject: [PATCH 4/7] fix: rename PW_DIAL_NUMBER_PSTN to PW_DIAL_NUMBER env variable --- playwright/tests/outdial-call-test.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playwright/tests/outdial-call-test.spec.ts b/playwright/tests/outdial-call-test.spec.ts index 495cdfeaf..64360c41c 100644 --- a/playwright/tests/outdial-call-test.spec.ts +++ b/playwright/tests/outdial-call-test.spec.ts @@ -20,7 +20,7 @@ export default function createOutdialCallTests() { test('should make an outdial call in Desktop mode and complete wrapup', async () => { await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await expect(testManager.agent1Page.getByTestId('outdial-call-container')).toBeVisible(); - await enterOutdialNumber(testManager.agent1Page, process.env.PW_DIAL_NUMBER_PSTN!); + await enterOutdialNumber(testManager.agent1Page, process.env.PW_DIAL_NUMBER!); await clickOutdialButton(testManager.agent1Page); await acceptCustomerCall(testManager.callerPage); await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); @@ -50,7 +50,7 @@ export default function createOutdialCallTests() { test('should make an outdial call in Extension mode and complete wrapup', async () => { await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await expect(testManager.agent1Page.getByTestId('outdial-call-container')).toBeVisible(); - await enterOutdialNumber(testManager.agent1Page, process.env.PW_DIAL_NUMBER_PSTN!); + await enterOutdialNumber(testManager.agent1Page, process.env.PW_DIAL_NUMBER!); await clickOutdialButton(testManager.agent1Page); await expect(testManager.agent1ExtensionPage.locator('#answer').first()).toBeEnabled({timeout: 40000}); await acceptExtensionCall(testManager.agent1ExtensionPage); From 33bf3bad7c21f44e5d0eddb1ec34ec76c9870ab8 Mon Sep 17 00:00:00 2001 From: Sarang S Date: Thu, 12 Mar 2026 10:58:14 +0530 Subject: [PATCH 5/7] fix: handle missing env test fail case --- playwright/tests/outdial-call-test.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/playwright/tests/outdial-call-test.spec.ts b/playwright/tests/outdial-call-test.spec.ts index 64360c41c..155971826 100644 --- a/playwright/tests/outdial-call-test.spec.ts +++ b/playwright/tests/outdial-call-test.spec.ts @@ -18,6 +18,7 @@ export default function createOutdialCallTests() { }); test('should make an outdial call in Desktop mode and complete wrapup', async () => { + test.skip(!process.env.PW_DIAL_NUMBER, 'PW_DIAL_NUMBER not set'); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await expect(testManager.agent1Page.getByTestId('outdial-call-container')).toBeVisible(); await enterOutdialNumber(testManager.agent1Page, process.env.PW_DIAL_NUMBER!); @@ -48,6 +49,7 @@ export default function createOutdialCallTests() { }); test('should make an outdial call in Extension mode and complete wrapup', async () => { + test.skip(!process.env.PW_DIAL_NUMBER, 'PW_DIAL_NUMBER not set'); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await expect(testManager.agent1Page.getByTestId('outdial-call-container')).toBeVisible(); await enterOutdialNumber(testManager.agent1Page, process.env.PW_DIAL_NUMBER!); From 4fdf079e707d389bd7eebec6680b31278dab346c Mon Sep 17 00:00:00 2001 From: Sarang S Date: Fri, 13 Mar 2026 08:38:42 +0530 Subject: [PATCH 6/7] fix: address comments --- playwright/test-manager.ts | 6 +++++- playwright/tests/outdial-call-test.spec.ts | 21 +++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/playwright/test-manager.ts b/playwright/test-manager.ts index f54de5bb9..d5d8318ab 100644 --- a/playwright/test-manager.ts +++ b/playwright/test-manager.ts @@ -538,7 +538,11 @@ export class TestManager { } private async setupOutdialCustomer(browser: Browser): Promise { - const customerToken = process.env.DIAL_NUMBER_LOGIN_ACCESS_TOKEN ?? ''; + const envTokens = this.getEnvTokens(); + const customerToken = envTokens.dialNumberLoginAccessToken; + if (!customerToken) { + throw new Error('Environment variable DIAL_NUMBER_LOGIN_ACCESS_TOKEN is missing or empty'); + } const result = await this.createContextWithPage(browser, PAGE_TYPES.CALLER); this.callerExtensionContext = result.context; this.callerPage = result.page; diff --git a/playwright/tests/outdial-call-test.spec.ts b/playwright/tests/outdial-call-test.spec.ts index 155971826..88317340a 100644 --- a/playwright/tests/outdial-call-test.spec.ts +++ b/playwright/tests/outdial-call-test.spec.ts @@ -6,9 +6,12 @@ import {submitWrapup, waitForWrapupAfterCallEnd} from '../Utils/wrapupUtils'; import {waitForState} from '../Utils/helperUtils'; import {TestManager} from '../test-manager'; import {enterOutdialNumber, clickOutdialButton, acceptCustomerCall} from '../Utils/outdialUtils'; +import {endTask} from '../Utils/taskControlUtils'; export default function createOutdialCallTests() { test.describe('Outdial Call - Desktop Mode', () => { + test.skip(!process.env.PW_DIAL_NUMBER || !process.env.DIAL_NUMBER_LOGIN_ACCESS_TOKEN, + 'Required outdial env vars not set'); let testManager: TestManager; test.beforeAll(async ({browser}, testInfo) => { @@ -18,7 +21,6 @@ export default function createOutdialCallTests() { }); test('should make an outdial call in Desktop mode and complete wrapup', async () => { - test.skip(!process.env.PW_DIAL_NUMBER, 'PW_DIAL_NUMBER not set'); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await expect(testManager.agent1Page.getByTestId('outdial-call-container')).toBeVisible(); await enterOutdialNumber(testManager.agent1Page, process.env.PW_DIAL_NUMBER!); @@ -26,20 +28,23 @@ export default function createOutdialCallTests() { await acceptCustomerCall(testManager.callerPage); await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); - await testManager.agent1Page.waitForTimeout(3000); - await testManager.agent1Page.getByTestId('call-control:end-call').first().click({timeout: 5000}); - await testManager.agent1Page.waitForTimeout(2000); + await endTask(testManager.agent1Page); + await waitForWrapupAfterCallEnd(testManager.agent1Page); await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); await waitForState(testManager.agent1Page, USER_STATES.AVAILABLE); await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); }); test.afterAll(async () => { - await testManager.cleanup(); + if (testManager) { + await testManager.cleanup(); + } }); }); test.describe('Outdial Call - Extension Mode', () => { + test.skip(!process.env.PW_DIAL_NUMBER || !process.env.DIAL_NUMBER_LOGIN_ACCESS_TOKEN, + 'Required outdial env vars not set'); let testManager: TestManager; test.beforeAll(async ({browser}, testInfo) => { @@ -49,7 +54,6 @@ export default function createOutdialCallTests() { }); test('should make an outdial call in Extension mode and complete wrapup', async () => { - test.skip(!process.env.PW_DIAL_NUMBER, 'PW_DIAL_NUMBER not set'); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await expect(testManager.agent1Page.getByTestId('outdial-call-container')).toBeVisible(); await enterOutdialNumber(testManager.agent1Page, process.env.PW_DIAL_NUMBER!); @@ -59,7 +63,6 @@ export default function createOutdialCallTests() { await acceptCustomerCall(testManager.callerPage); await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); - await testManager.agent1Page.waitForTimeout(3000); await endCallTask(testManager.agent1ExtensionPage); await waitForWrapupAfterCallEnd(testManager.agent1Page); await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); @@ -68,7 +71,9 @@ export default function createOutdialCallTests() { }); test.afterAll(async () => { - await testManager.cleanup(); + if (testManager) { + await testManager.cleanup(); + } }); }); From f520d7349cd496b659d62d61fd8ae6ca4acb7480 Mon Sep 17 00:00:00 2001 From: Sarang S Date: Fri, 13 Mar 2026 08:46:41 +0530 Subject: [PATCH 7/7] fix: add cleanup to dn tests --- .../dial-number-task-control-test.spec.ts | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/playwright/tests/dial-number-task-control-test.spec.ts b/playwright/tests/dial-number-task-control-test.spec.ts index 74a2d2f05..8fed4fc85 100644 --- a/playwright/tests/dial-number-task-control-test.spec.ts +++ b/playwright/tests/dial-number-task-control-test.spec.ts @@ -1,4 +1,4 @@ -import {test, expect} from '@playwright/test'; +import { test, expect } from '@playwright/test'; import { cancelConsult, consultOrTransfer, @@ -8,7 +8,7 @@ import { verifyTransferSuccessLogs, verifyConsultEndSuccessLogs, } from '../Utils/advancedTaskControlUtils'; -import {changeUserState, verifyCurrentState} from '../Utils/userStateUtils'; +import { changeUserState, verifyCurrentState } from '../Utils/userStateUtils'; import { createCallTask, acceptIncomingTask, @@ -16,17 +16,17 @@ import { endCallTask, declineExtensionCall, } from '../Utils/incomingTaskUtils'; -import {submitWrapup} from '../Utils/wrapupUtils'; -import {USER_STATES, TASK_TYPES, WRAPUP_REASONS} from '../constants'; -import {waitForState, clearPendingCallAndWrapup, handleStrayTasks} from '../Utils/helperUtils'; -import {endTask, holdCallToggle, verifyHoldButtonIcon, verifyTaskControls} from '../Utils/taskControlUtils'; -import {TestManager} from '../test-manager'; +import { submitWrapup } from '../Utils/wrapupUtils'; +import { USER_STATES, TASK_TYPES, WRAPUP_REASONS } from '../constants'; +import { waitForState, clearPendingCallAndWrapup, handleStrayTasks } from '../Utils/helperUtils'; +import { endTask, holdCallToggle, verifyHoldButtonIcon, verifyTaskControls } from '../Utils/taskControlUtils'; +import { TestManager } from '../test-manager'; export default function createDialNumberTaskControlTests() { test.describe('Dial Number Task Control Tests ', () => { let testManager: TestManager; - test.beforeAll(async ({browser}, testInfo) => { + test.beforeAll(async ({ browser }, testInfo) => { const projectName = testInfo.project.name; testManager = new TestManager(projectName); await testManager.setupForDialNumber(browser); @@ -36,6 +36,13 @@ export default function createDialNumberTaskControlTests() { await handleStrayTasks(testManager.agent1Page); await handleStrayTasks(testManager.agent2Page); }); + + test.afterAll(async () => { + if (testManager) { + await testManager.cleanup(); + } + }); + test.describe('Dial Number Tests', () => { test.beforeAll(async () => { test.skip(!process.env.PW_DIAL_NUMBER_NAME, 'PW_DIAL_NUMBER_NAME not set'); @@ -104,7 +111,7 @@ export default function createDialNumberTaskControlTests() { await testManager.agent1Page.waitForTimeout(2000); await cancelConsult(testManager.agent1Page); // still needs to cancel even if declined await verifyTaskControls(testManager.agent1Page, TASK_TYPES.CALL); - await verifyHoldButtonIcon(testManager.agent1Page, {expectedIsHeld: true}); + await verifyHoldButtonIcon(testManager.agent1Page, { expectedIsHeld: true }); await holdCallToggle(testManager.agent1Page); await testManager.agent1Page.waitForTimeout(2000); await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).not.toBeVisible(); @@ -121,7 +128,7 @@ export default function createDialNumberTaskControlTests() { await verifyTaskControls(testManager.agent1Page, TASK_TYPES.CALL); await testManager.agent1Page.waitForTimeout(2000); verifyConsultEndSuccessLogs(); - await verifyHoldButtonIcon(testManager.agent1Page, {expectedIsHeld: true}); + await verifyHoldButtonIcon(testManager.agent1Page, { expectedIsHeld: true }); await holdCallToggle(testManager.agent1Page); // 4. Consult transfer @@ -155,11 +162,11 @@ export default function createDialNumberTaskControlTests() { // Open consult popover and switch to Dial Number const consultButton = testManager.agent1Page.getByTestId('call-control:consult').first(); - await consultButton.waitFor({state: 'visible', timeout: 10000}); + await consultButton.waitFor({ state: 'visible', timeout: 10000 }); await consultButton.click(); const popover = testManager.agent1Page.locator('.agent-popover-content'); - await expect(popover).toBeVisible({timeout: 10000}); - await popover.getByRole('button', {name: 'Dial Number'}).click(); + await expect(popover).toBeVisible({ timeout: 10000 }); + await popover.getByRole('button', { name: 'Dial Number' }).click(); // Perform search and wait for local filtering to reflect await popover.locator('#consult-search').fill(searchTerm); @@ -176,8 +183,8 @@ export default function createDialNumberTaskControlTests() { await testManager.agent1Page.keyboard.press('Escape'); await testManager.agent1Page .locator('.md-popover-backdrop') - .waitFor({state: 'hidden', timeout: 3000}) - .catch(() => {}); + .waitFor({ state: 'hidden', timeout: 3000 }) + .catch(() => { }); // End call and complete wrapup to clean up for next tests await endTask(testManager.agent1Page);