Skip to content

Commit d87aa93

Browse files
eigengravySarang SShreyas281299
authored
feat(cc-widgets): outdial widget e2e tests (#640)
Co-authored-by: Sarang S <sarangsa@cisco.com> Co-authored-by: Shreyas Sharma <72344404+Shreyas281299@users.noreply.github.com>
1 parent 0cd6188 commit d87aa93

8 files changed

Lines changed: 271 additions & 19 deletions

File tree

packages/contact-center/task/ai-docs/widgets/OutdialCall/ARCHITECTURE.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,81 @@ sequenceDiagram
130130
end
131131
```
132132

133+
### Post-Dial Flow by Login Mode
134+
135+
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.
136+
137+
#### Desktop Mode -- Customer Rings Directly
138+
139+
In Desktop mode, the agent is auto-connected. The customer's phone rings immediately, and once the customer answers, the agent reaches ENGAGED state.
140+
141+
```mermaid
142+
sequenceDiagram
143+
participant A as Agent
144+
participant W as Widget / Store
145+
participant P as CC Platform
146+
participant C as Customer
147+
148+
A->>W: Click dial button
149+
W->>P: cc.startOutdial(destination, origin)
150+
P-->>W: TaskResponse
151+
P->>C: Customer's phone rings (second leg)
152+
C->>P: Customer answers
153+
P->>W: Agent auto-connects → ENGAGED
154+
```
155+
156+
The agent never needs to accept the incoming task. The Accept button is visible but disabled during the brief popup.
157+
158+
#### Extension Mode -- Manual Answer Required
159+
160+
The first-leg call rings on the agent's Webex Calling extension. The agent must answer it before the platform dials the customer.
161+
162+
```mermaid
163+
sequenceDiagram
164+
participant A as Agent
165+
participant W as Widget / Store
166+
participant E as Webex Calling Extension
167+
participant P as CC Platform
168+
participant C as Customer
169+
170+
A->>W: Click dial button
171+
W->>P: cc.startOutdial(destination, origin)
172+
P-->>W: TaskResponse
173+
P->>E: First-leg rings on extension
174+
Note over E: Answer button becomes enabled
175+
A->>E: Answer call on extension
176+
P->>C: Customer's phone rings (second leg)
177+
C->>P: Customer answers
178+
P->>W: Agent state → ENGAGED
179+
```
180+
181+
The agent must answer the extension call before the customer is dialed.
182+
183+
#### Dial Number (DN) Mode -- Manual Answer Required
184+
185+
The first-leg call rings on the agent's DN phone. The agent must answer it before the platform dials the customer.
186+
187+
```mermaid
188+
sequenceDiagram
189+
participant A as Agent
190+
participant W as Widget / Store
191+
participant D as Agent DN Phone
192+
participant P as CC Platform
193+
participant C as Customer
194+
195+
A->>W: Click dial button
196+
W->>P: cc.startOutdial(destination, origin)
197+
P-->>W: TaskResponse
198+
P->>D: First-leg rings on DN phone
199+
Note over D: Answer button becomes enabled
200+
A->>D: Answer call on DN phone
201+
P->>C: Customer's phone rings (second leg)
202+
C->>P: Customer answers
203+
P->>W: Agent state → ENGAGED
204+
```
205+
206+
The agent must answer the DN phone call before the customer is dialed.
207+
133208
### Number Validation
134209

135210
```mermaid

playwright/Utils/outdialUtils.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import {Page, expect} from '@playwright/test';
2+
import {ACCEPT_TASK_TIMEOUT, AWAIT_TIMEOUT, UI_SETTLE_TIMEOUT} from '../constants';
3+
4+
/**
5+
* Enters a phone number into the outdial number input field.
6+
* Prerequisite: Agent must be logged in and outdial-call-container must be visible.
7+
* @param page Playwright Page object (agent widget page)
8+
* @param number Phone number to dial (e.g., +14698041796)
9+
*/
10+
export async function enterOutdialNumber(page: Page, number: string): Promise<void> {
11+
await page.bringToFront();
12+
await expect(page.getByTestId('outdial-call-container')).toBeVisible({timeout: AWAIT_TIMEOUT});
13+
const input = page.getByTestId('outdial-number-input').locator('input');
14+
await input.fill(number, {timeout: AWAIT_TIMEOUT});
15+
}
16+
17+
/**
18+
* Clicks the outdial call button to initiate the outbound call.
19+
* Prerequisite: A valid number must be entered in the outdial input.
20+
* @param page Playwright Page object (agent widget page)
21+
*/
22+
export async function clickOutdialButton(page: Page): Promise<void> {
23+
await page.bringToFront();
24+
const dialButton = page.getByTestId('outdial-call-button');
25+
await expect(dialButton).toBeEnabled({timeout: AWAIT_TIMEOUT});
26+
await dialButton.click({timeout: AWAIT_TIMEOUT});
27+
}
28+
29+
/**
30+
* Accepts an incoming call on the customer's Webex Calling web client.
31+
* Used for outdial scenarios where the customer receives the outbound call.
32+
* @param customerPage Playwright Page object (customer's Webex Calling web client)
33+
*/
34+
export async function acceptCustomerCall(customerPage: Page): Promise<void> {
35+
await customerPage.bringToFront();
36+
await expect(customerPage.locator('#answer').first()).toBeEnabled({timeout: ACCEPT_TASK_TIMEOUT});
37+
await customerPage.waitForTimeout(UI_SETTLE_TIMEOUT);
38+
await customerPage.locator('#answer').first().click({timeout: AWAIT_TIMEOUT});
39+
}
40+
41+
/**
42+
* Ends the call on the customer's Webex Calling web client.
43+
* @param customerPage Playwright Page object (customer's Webex Calling web client)
44+
*/
45+
export async function endCustomerCall(customerPage: Page): Promise<void> {
46+
await customerPage.bringToFront();
47+
const endBtn = customerPage.locator('#end-call').first();
48+
await expect(endBtn).toBeEnabled({timeout: AWAIT_TIMEOUT});
49+
await endBtn.click({timeout: AWAIT_TIMEOUT});
50+
}

playwright/ai-docs/ARCHITECTURE.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ playwright/
5252
│ ├── basic-task-controls-test.spec.ts
5353
│ ├── advanced-task-controls-test.spec.ts
5454
│ ├── advance-task-control-combinations-test.spec.ts
55+
│ ├── outdial-call-test.spec.ts
5556
│ ├── dial-number-task-control-test.spec.ts
5657
│ ├── tasklist-test.spec.ts
5758
│ ├── multiparty-conference-set-7-test.spec.ts
@@ -66,6 +67,7 @@ playwright/
6667
│ ├── userStateUtils.ts
6768
│ ├── taskControlUtils.ts
6869
│ ├── advancedTaskControlUtils.ts
70+
│ ├── outdialUtils.ts
6971
│ └── wrapupUtils.ts
7072
├── test-manager.ts
7173
├── test-data.ts
@@ -89,7 +91,7 @@ Keep this section aligned to real repository contents.
8991
| `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` |
9092
| `SET_4` | `basic-advanced-task-controls-tests.spec.ts` | `basic-task-controls-test.spec.ts`, `advance-task-control-combinations-test.spec.ts` |
9193
| `SET_5` | `advanced-task-controls-tests.spec.ts` | `advanced-task-controls-test.spec.ts` |
92-
| `SET_6` | `dial-number-tests.spec.ts` | `dial-number-task-control-test.spec.ts` |
94+
| `SET_6` | `dial-number-tests.spec.ts` | `dial-number-task-control-test.spec.ts`, `outdial-call-test.spec.ts` |
9395
| `SET_7` | `multiparty-conference-set-7-tests.spec.ts` | `multiparty-conference-set-7-test.spec.ts` |
9496
| `SET_8` | `multiparty-conference-set-8-tests.spec.ts` | `multiparty-conference-set-8-test.spec.ts` |
9597
| `SET_9` | `multiparty-conference-set-9-tests.spec.ts` | `multiparty-conference-set-9-test.spec.ts` |
@@ -150,7 +152,8 @@ These flags are part of baseline runtime behavior and should be preserved unless
150152
- `[SET_7, SET_8]`
151153
- `[SET_9]`
152154
3. Optionally fetches dial-number OAuth token
153-
4. Performs one final `.env` upsert in the same OAuth setup run
155+
4. Optionally fetches customer outdial OAuth token (for outdial E2E tests)
156+
5. Performs one final `.env` upsert in the same OAuth setup run
154157

155158
Test files:
156159

@@ -232,6 +235,8 @@ When enabled by setup config/method, these page properties are created and avail
232235
| `setupForIncomingTaskExtension()` | Calls `setup()` for extension incoming-task flow |
233236
| `setupForIncomingTaskMultiSession()` | Calls `setup()` for multi-session incoming-task flow |
234237
| `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. |
238+
| `setupForOutdialDesktop()` | Calls `setup()` with desktop agent1 + outdial customer login |
239+
| `setupForOutdialExtension()` | Calls `setup()` with extension agent1 + outdial customer login |
235240
| `setupForMultipartyConference()` | Sets up 4 agents + caller for conference tests (agent1–4 pages + callerPage) |
236241
| `setupMultiSessionPage()` | Targeted helper to initialize only multi-session page when needed |
237242

@@ -263,6 +268,7 @@ When enabled by setup config/method, these page properties are created and avail
263268
| `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. |
264269
| `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. |
265270
| `incomingTaskUtils.ts` | `createCallTask`, `createChatTask`, `createEmailTask`, `waitForIncomingTask`, `acceptIncomingTask`, `declineIncomingTask`, `acceptExtensionCall`, `loginExtension`, `submitRonaPopup` | Incoming task creation/acceptance/decline and extension helpers |
271+
| `outdialUtils.ts` | `enterOutdialNumber`, `clickOutdialButton`, `acceptCustomerCall`, `endCustomerCall` | Outdial call helpers for entering number, clicking dial, accepting/ending customer-side calls |
266272
| `wrapupUtils.ts` | `submitWrapup` | Wrapup submission |
267273
| `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. |
268274
| `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 +438,4 @@ After a call ends, the Make Call button on the caller page may stay disabled. Cl
432438

433439
---
434440

435-
_Last Updated: 2026-03-09_
441+
_Last Updated: 2026-03-11_
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {test} from '@playwright/test';
22
import createDialNumberTaskControlTests from '../tests/dial-number-task-control-test.spec';
3+
import createOutdialCallTests from '../tests/outdial-call-test.spec';
34

45
test.describe('Dial Number Task Control Tests', createDialNumberTaskControlTests);
6+
test.describe('Outdial Call Tests', createOutdialCallTests);

playwright/suites/station-login-user-state-tests.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {test} from '@playwright/test';
1+
import { test } from '@playwright/test';
22
import createStationLoginTests from '../tests/station-login-test.spec';
33
import createUserStateTests from '../tests/user-state-test.spec';
44
import createIncomingTelephonyTaskTests from '../tests/incoming-telephony-task-test.spec';

playwright/test-manager.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,38 @@ export class TestManager {
520520
});
521521
}
522522

523+
async setupForOutdialDesktop(browser: Browser): Promise<void> {
524+
await this.setup(browser, {
525+
needsAgent1: true,
526+
agent1LoginMode: LOGIN_MODE.DESKTOP,
527+
});
528+
await this.setupOutdialCustomer(browser);
529+
}
530+
531+
async setupForOutdialExtension(browser: Browser): Promise<void> {
532+
await this.setup(browser, {
533+
needsAgent1: true,
534+
needsExtension: true,
535+
agent1LoginMode: LOGIN_MODE.EXTENSION,
536+
});
537+
await this.setupOutdialCustomer(browser);
538+
}
539+
540+
private async setupOutdialCustomer(browser: Browser): Promise<void> {
541+
const envTokens = this.getEnvTokens();
542+
const customerToken = envTokens.dialNumberLoginAccessToken;
543+
if (!customerToken) {
544+
throw new Error('Environment variable DIAL_NUMBER_LOGIN_ACCESS_TOKEN is missing or empty');
545+
}
546+
const result = await this.createContextWithPage(browser, PAGE_TYPES.CALLER);
547+
this.callerExtensionContext = result.context;
548+
this.callerPage = result.page;
549+
await this.retryOperation(
550+
() => loginExtension(this.callerPage, customerToken),
551+
'outdial customer login'
552+
);
553+
}
554+
523555
async setupForMultipartyConference(browser: Browser) {
524556
await this.setup(browser, {
525557
needsAgent1: true,

0 commit comments

Comments
 (0)