Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
"clean:dist": "yarn workspaces foreach --all --topological --parallel run clean:dist",
"test:unit": "yarn run test:tooling && yarn run test:cc-widgets && yarn run test:meetings-widget",
"test:e2e": "yarn playwright test",
"test:e2e:cc": "yarn workspace @webex/cc-widgets run test:e2e",
"test:e2e:meetings": "yarn workspace @webex/widgets run test:e2e",
Comment on lines +57 to +58

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 Preserve the documented root E2E command

Replacing test:e2e with only the two suffixed scripts leaves no root script named test:e2e, while the modified Playwright README still uses yarn test:e2e for every all-suite, single-suite, project, UI, debug, and headed invocation. Those documented local commands now fail during script resolution before Playwright starts; retain a compatible alias or update the documented callers to use test:e2e:cc.

AGENTS.md reference: AGENTS.md:L78-L78

Useful? React with 👍 / 👎.

"test:styles": "yarn workspaces foreach --all --exclude webex-widgets run test:styles",
"test:tooling": "NODE_ENV=test jest --coverage",
"test:cc-widgets": "yarn workspaces foreach --all --exclude webex-widgets --exclude samples-cc-wc-app --exclude samples-cc-react-app run test:unit",
Expand Down
1 change: 1 addition & 0 deletions packages/contact-center/cc-widgets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"build:src": "yarn run clean:dist && webpack",
"build:watch": "webpack --watch",
"test:unit": "NODE_ENV=test jest --coverage",
"test:e2e": "yarn run -T playwright test --config ../../../playwright.config.ts",
"test:styles": "eslint",
"deploy:npm": "yarn npm publish"
},
Expand Down
51 changes: 30 additions & 21 deletions playwright/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ playwright/
│ ├── dial-number-tests.spec.ts # Dial number task control orchestration
│ ├── multiparty-conference-set-7-tests.spec.ts # Multiparty conference set 7 orchestration
│ ├── multiparty-conference-set-8-tests.spec.ts # Multiparty conference set 8 orchestration
│ └── multiparty-conference-set-9-tests.spec.ts # Multiparty conference set 9 orchestration
│ ├── multiparty-conference-set-9-tests.spec.ts # Multiparty conference set 9 orchestration
│ └── ai-assistant-tests.spec.ts # AI Assistant & Real-Time Transcript orchestration
├── tests/ # Individual test implementations
├── Utils/ # Utility functions
├── test-data.ts # **CENTRAL CONFIG** - Test data & suite mapping
Expand All @@ -33,29 +34,37 @@ playwright/
- ✅ Positions browser windows automatically
- ✅ Maps test suites to user sets

| Set | Focus | Port | Suite File |
| --------- | ---------------------------------- | ---- | -------------------------------------------- |
| **SET_1** | Digital incoming tasks & controls | 9221 | `digital-incoming-task-tests.spec.ts` |
| **SET_2** | Task lists & multi-session | 9222 | `task-list-multi-session-tests.spec.ts` |
| **SET_3** | Authentication & user management | 9223 | `station-login-user-state-tests.spec.ts` |
| **SET_4** | Task controls & combinations | 9224 | `basic-advanced-task-controls-tests.spec.ts` |
| **SET_5** | Advanced task operations | 9225 | `advanced-task-controls-tests.spec.ts` |
| **SET_6** | Dial number scenarios | 9226 | `dial-number-tests.spec.ts` |
| **SET_7** | Multiparty conference (team 25-28) | 9227 | `multiparty-conference-set-7-tests.spec.ts` |
| **SET_8** | Multiparty conference (team 29-32) | 9228 | `multiparty-conference-set-8-tests.spec.ts` |
| **SET_9** | Multiparty conference (team 33-36) | 9229 | `multiparty-conference-set-9-tests.spec.ts` |
| Set | Focus | Port | Suite File |
| ---------- | ----------------------------------- | ---- | -------------------------------------------- |
| **SET_1** | Digital incoming tasks & controls | 9221 | `digital-incoming-task-tests.spec.ts` |
| **SET_2** | Task lists & multi-session | 9222 | `task-list-multi-session-tests.spec.ts` |
| **SET_3** | Authentication & user management | 9223 | `station-login-user-state-tests.spec.ts` |
| **SET_4** | Task controls & combinations | 9224 | `basic-advanced-task-controls-tests.spec.ts` |
| **SET_5** | Advanced task operations | 9225 | `advanced-task-controls-tests.spec.ts` |
| **SET_6** | Dial number scenarios | 9226 | `dial-number-tests.spec.ts` |
| **SET_7** | Multiparty conference (team 25-28) | 9227 | `multiparty-conference-set-7-tests.spec.ts` |
| **SET_8** | Multiparty conference (team 29-32) | 9228 | `multiparty-conference-set-8-tests.spec.ts` |
| **SET_9** | Multiparty conference (team 33-36) | 9229 | `multiparty-conference-set-9-tests.spec.ts` |
| **SET_10** | AI Assistant & Real-Time Transcript | 9230 | `ai-assistant-tests.spec.ts` |

### Where to Add New Tests?

| Test Type | Use Set | Why |
| ---------------------------- | --------- | --------------------------- |
| Digital channels tasks | SET_1 | Digital channels configured |
| Task list operations | SET_2 | Task list focus |
| Authentication/User states | SET_3 | User management |
| Basic/Advanced task controls | SET_4 | Task control operations |
| Complex advanced scenarios | SET_5 | Advanced operations |
| Dial number scenarios | SET_6 | Dial number flows |
| Multiparty conference | SET_7/8/9 | 4-agent conference coverage |
| Test Type | Use Set | Why |
| ----------------------------------- | --------- | --------------------------- |
| Digital channels tasks | SET_1 | Digital channels configured |
| Task list operations | SET_2 | Task list focus |
| Authentication/User states | SET_3 | User management |
| Basic/Advanced task controls | SET_4 | Task control operations |
| Complex advanced scenarios | SET_5 | Advanced operations |
| Dial number scenarios | SET_6 | Dial number flows |
| Multiparty conference | SET_7/8/9 | 4-agent conference coverage |
| AI Assistant / Real-Time Transcript | SET_10 | Live call required for both |

> **Note on AI Assistant / Real-Time Transcript coverage:** suggestion and transcript
> content is produced by a live backend AI/speech pipeline against the call's dummy
> audio, so it isn't deterministic. `SET_10` tests intentionally assert on structure
> and state transitions (landing to request view to listening mode, feedback controls
> toggling `data-active`, transcript entries appearing) rather than exact text.

## Multiparty Conference Consolidation

Expand Down
124 changes: 124 additions & 0 deletions playwright/Utils/aiAssistantUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {Page, expect, Locator} from '@playwright/test';
import {AWAIT_TIMEOUT, AI_ASSIST_SUGGESTION_TIMEOUT} from '../constants';

/**
* Utility functions for exercising the AI Assistant widget (launcher, landing
* page, Real-Time Assist chat, and adaptive-card feedback controls) in e2e
* tests.
*
* The suggestion content itself is generated by a live backend AI pipeline
* during the test call, so these helpers deliberately avoid asserting on
* exact transcript/suggestion text and instead verify structure and state
* transitions (panel open, spinner clears, chat entries appear, feedback
* controls toggle).
*
* @packageDocumentation
*/

/**
* Enables the AI Assistant widget via its sample-app checkbox.
* Unlike most widgets, AI Assistant defaults to unchecked, so this must be
* called explicitly before the widget will render.
* @param page - The Playwright page object
*/
export async function enableAIAssistantWidget(page: Page): Promise<void> {
const checkbox = page.getByTestId('samples:widget-aiAssistant');
const isChecked = await checkbox.isChecked().catch(() => false);
if (!isChecked) {
await checkbox.check({timeout: AWAIT_TIMEOUT});
}
}

/**
* Opens the AI Assistant panel by clicking its launcher button.
* No-ops if the panel is already open (launcher only renders when closed).
* @param page - The Playwright page object
*/
export async function openAIAssistant(page: Page): Promise<void> {
const launcher = page.getByTestId('ai-assistant:launcher');
if (await launcher.isVisible().catch(() => false)) {
await launcher.click({timeout: AWAIT_TIMEOUT});
}
await expect(page.getByTestId('ai-assistant:panel')).toBeVisible({timeout: AWAIT_TIMEOUT});
}

/**
* Closes the AI Assistant panel via its header close button.
* @param page - The Playwright page object
*/
export async function closeAIAssistant(page: Page): Promise<void> {
const closeButton = page.getByTestId('ai-assistant:header-close');
if (await closeButton.isVisible().catch(() => false)) {
await closeButton.click({timeout: AWAIT_TIMEOUT});
}
}

/**
* Returns true while the landing page (feature disabled / no active
* interaction) is shown instead of the Real-Time Assist chat.
* @param page - The Playwright page object
*/
export async function isShowingLanding(page: Page): Promise<boolean> {
return page
.getByTestId('ai-assistant:landing')
.isVisible()
.catch(() => false);
}

/**
* Clicks "Get Suggestions" and waits for the request to settle: either the
* chat/context-form appears (success) or the inline error message appears
* (failure). Does not throw on failure - callers assert the outcome.
* @param page - The Playwright page object
*/
export async function requestRealTimeAssistSuggestions(page: Page): Promise<void> {
const getSuggestionsButton = page.getByTestId('ai-assistant:get-suggestions');
await expect(getSuggestionsButton).toBeVisible({timeout: AWAIT_TIMEOUT});
await getSuggestionsButton.click({timeout: AWAIT_TIMEOUT});

// The button is replaced by a spinner while the request is in flight;
// wait for the request to settle one way or another.
await Promise.race([
page.getByTestId('ai-assistant:context-form').waitFor({state: 'visible', timeout: AI_ASSIST_SUGGESTION_TIMEOUT}),
page.getByTestId('ai-assistant:error').waitFor({state: 'visible', timeout: AI_ASSIST_SUGGESTION_TIMEOUT}),
]).catch(() => {});
}

/**
* Waits for at least one assistant suggestion (adaptive card or greeting) to
* appear in the Real-Time Assist chat.
* @param page - The Playwright page object
* @returns Locator for the first assistant chat entry
*/
export async function waitForFirstSuggestion(page: Page): Promise<Locator> {
const assistantEntry = page.getByTestId('ai-assistant:chat-assistant').first();
await assistantEntry.waitFor({state: 'visible', timeout: AI_ASSIST_SUGGESTION_TIMEOUT});
return assistantEntry;
}

/**
* Clicks the like/dislike feedback control (identified by its accessible
* name) on the first rendered suggestion card and returns its locator so the
* caller can assert on the resulting `data-active` state.
* @param page - The Playwright page object
* @param kind - Which control to click
*/
export async function clickSuggestionFeedback(page: Page, kind: 'like' | 'dislike'): Promise<Locator> {
const label = kind === 'like' ? 'Like suggestion' : 'Dislike suggestion';
const control = page.getByLabel(label).first();
await expect(control).toBeVisible({timeout: AWAIT_TIMEOUT});
await control.click({timeout: AWAIT_TIMEOUT});
return control;
}

/**
* Clicks the copy control on the first rendered suggestion card.
* @param page - The Playwright page object
* @returns Locator for the copy control
*/
export async function clickSuggestionCopy(page: Page): Promise<Locator> {
const control = page.getByLabel('Copy suggestion').first();
await expect(control).toBeVisible({timeout: AWAIT_TIMEOUT});
await control.click({timeout: AWAIT_TIMEOUT});
return control;
}
45 changes: 45 additions & 0 deletions playwright/Utils/realTimeTranscriptUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {Page, Locator} from '@playwright/test';
import {TRANSCRIPT_ENTRY_TIMEOUT} from '../constants';

/**
* Utility functions for exercising the Real-Time Transcript widget in e2e
* tests.
*
* Transcript content is generated by a live speech-to-text pipeline from the
* dummy audio played on the call, so these helpers verify that entries
* eventually appear rather than asserting on exact transcribed text.
*
* @packageDocumentation
*/

/**
* Waits for the Real-Time Transcript panel to be visible.
* The panel only renders while `store.currentTask` exists (an active call).
* @param page - The Playwright page object
* @param timeout - Optional timeout override in ms
*/
export async function waitForRealTimeTranscriptPanel(page: Page, timeout: number = TRANSCRIPT_ENTRY_TIMEOUT) {
const root = page.getByTestId('real-time-transcript:root');
await root.waitFor({state: 'visible', timeout});
return root;
}

/**
* Waits for at least one transcript entry to appear in the live transcript
* feed.
* @param page - The Playwright page object
* @returns Locator for the first transcript item
*/
export async function waitForFirstTranscriptEntry(page: Page): Promise<Locator> {
const item = page.getByTestId('real-time-transcript:item').first();
await item.waitFor({state: 'visible', timeout: TRANSCRIPT_ENTRY_TIMEOUT});
return item;
}

/**
* Returns the count of transcript entries currently rendered.
* @param page - The Playwright page object
*/
export async function getTranscriptEntryCount(page: Page): Promise<number> {
return page.getByTestId('real-time-transcript:item').count();
}
9 changes: 9 additions & 0 deletions playwright/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ export const CONSULT_NO_ANSWER_TIMEOUT = 12000;
// Wrapup timeouts
export const WRAPUP_TIMEOUT = 15000;

// Real-Time Assist / Real-Time Transcript timeouts.
// These are backend AI pipeline operations (live speech-to-text + suggestion
// generation), not UI interactions, so they need materially more time than
// AWAIT_TIMEOUT. Observed end-to-end latency for the first transcript segment
// and the first AI suggestion on the dummy-audio e2e call is in the 15-40s
// range; 60s leaves headroom without masking a genuine pipeline failure.
export const AI_ASSIST_SUGGESTION_TIMEOUT = 60000;
export const TRANSCRIPT_ENTRY_TIMEOUT = 60000;

// Station login timeouts
export const DROPDOWN_SETTLE_TIMEOUT = 200;
export const STATION_LOGOUT_UNREGISTER_SETTLE_TIMEOUT = 4000;
Expand Down
6 changes: 6 additions & 0 deletions playwright/suites/ai-assistant-tests.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {test} from '@playwright/test';
import createRealTimeAssistTests from '../tests/real-time-assist-test.spec';
import createRealTimeTranscriptTests from '../tests/real-time-transcript-test.spec';

test.describe('Real-Time Assist Tests', createRealTimeAssistTests);
test.describe('Real-Time Transcript Tests', createRealTimeTranscriptTests);
11 changes: 11 additions & 0 deletions playwright/test-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,15 @@ export const USER_SETS = {
ENTRY_POINT: env.PW_ENTRY_POINT9,
TEST_SUITE: 'multiparty-conference-set-9-tests.spec.ts',
},
SET_10: {
AGENTS: {
AGENT1: {username: 'user37', extension: '1037', agentName: 'User37 Agent37'},
AGENT2: {username: 'user38', extension: '1038', agentName: 'User38 Agent38'},
},
QUEUE_NAME: 'Queue e2e 10',
CHAT_URL: `${env.PW_CHAT_URL}-e2e-10.html`,
EMAIL_ENTRY_POINT: `${env.PW_SANDBOX}.e2e10@gmail.com`,
ENTRY_POINT: env.PW_ENTRY_POINT10,
TEST_SUITE: 'ai-assistant-tests.spec.ts',
},
};
15 changes: 15 additions & 0 deletions playwright/test-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,21 @@ export class TestManager {
await initialiseWidgets(this.multiSessionAgent1Page);
}

/**
* Setup for Real-Time Assist / Real-Time Transcript scenarios: a single
* agent (desktop login) plus a caller, so a live voice call can be
* established. Both widgets render automatically off `store.currentTask`
* / the sample app's checkboxes - no extension or chat context needed.
*/
async setupForRealTimeAssistAndTranscript(browser: Browser) {
await this.setup(browser, {
needsAgent1: true,
needsCaller: true,
agent1LoginMode: LOGIN_MODE.DESKTOP,
enableConsoleLogging: true,
});
}

// Specific setup methods that use the universal setup
async setupForIncomingTaskDesktop(browser: Browser) {
await this.setup(browser, {
Expand Down
Loading
Loading