-
Notifications
You must be signed in to change notification settings - Fork 69
feat(cc-widgets): UI Automation for Basic Task Controls #497
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1202ad8
0d043fb
1165d03
6c2f811
e62cf04
2783685
0f92557
fc40e67
77ce464
fe92788
4baacb6
ff53ab0
c2f2e6d
54d363f
1f1b982
9b1e7a2
a41873b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| import { Page, expect } from '@playwright/test'; | ||
| import { WRAPUP_REASONS } from '../constants'; | ||
|
|
||
| /** | ||
| * Utility functions for advanced task controls testing. | ||
| * Provides functions for consult operations, transfer operations, and end consult actions. | ||
| * These utilities handle complex multi-agent scenarios and task state transitions. | ||
| * | ||
| * @packageDocumentation | ||
| */ | ||
|
|
||
| // Array to store captured console logs for verification | ||
| let capturedAdvancedLogs: string[] = []; | ||
|
|
||
| /** | ||
| * Sets up console logging to capture transfer and consult related callback logs. | ||
| * Captures transfer success, consult start/end success, and related SDK messages. | ||
| * @param page - The agent's main page | ||
| * @returns Function to remove the console handler | ||
| */ | ||
| export function setupAdvancedConsoleLogging(page: Page): () => void { | ||
| capturedAdvancedLogs.length = 0; | ||
|
|
||
| const consoleHandler = (msg) => { | ||
| const logText = msg.text(); | ||
| if (logText.includes('WXCC_SDK_TASK_TRANSFER_SUCCESS') || | ||
| logText.includes('WXCC_SDK_TASK_CONSULT_START_SUCCESS') || | ||
| logText.includes('WXCC_SDK_TASK_CONSULT_END_SUCCESS') || | ||
| logText.includes('AgentConsultTransferred') || | ||
| logText.includes('onEnd invoked') || | ||
| logText.includes('onTransfer invoked') || | ||
| logText.includes('onConsult invoked')) { | ||
| capturedAdvancedLogs.push(logText); | ||
| } | ||
| }; | ||
|
|
||
| page.on('console', consoleHandler); | ||
| return () => page.off('console', consoleHandler); | ||
| } | ||
|
|
||
| /** | ||
| * Clears the captured advanced logs array. | ||
| * Should be called before each test or verification to ensure clean state. | ||
| */ | ||
| export function clearAdvancedCapturedLogs(): void { | ||
| capturedAdvancedLogs.length = 0; | ||
| } | ||
|
|
||
| /** | ||
| * Verifies that transfer success logs are present. | ||
| * @throws Error if verification fails with detailed error message | ||
| */ | ||
| export function verifyTransferSuccessLogs(): void { | ||
| const transferLogs = capturedAdvancedLogs.filter(log => log.includes('WXCC_SDK_TASK_TRANSFER_SUCCESS')); | ||
|
|
||
| if (transferLogs.length === 0) { | ||
| throw new Error(`No 'WXCC_SDK_TASK_TRANSFER_SUCCESS' logs found. Captured logs: ${JSON.stringify(capturedAdvancedLogs)}`); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Verifies that consult start success logs are present. | ||
| * @throws Error if verification fails with detailed error message | ||
| */ | ||
| export function verifyConsultStartSuccessLogs(): void { | ||
| const consultStartLogs = capturedAdvancedLogs.filter(log => log.includes('WXCC_SDK_TASK_CONSULT_START_SUCCESS')); | ||
|
|
||
| if (consultStartLogs.length === 0) { | ||
| throw new Error(`No 'WXCC_SDK_TASK_CONSULT_START_SUCCESS' logs found. Captured logs: ${JSON.stringify(capturedAdvancedLogs)}`); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Verifies that consult end success logs are present. | ||
| * @throws Error if verification fails with detailed error message | ||
| */ | ||
| export function verifyConsultEndSuccessLogs(): void { | ||
| const consultEndLogs = capturedAdvancedLogs.filter(log => log.includes('WXCC_SDK_TASK_CONSULT_END_SUCCESS')); | ||
|
|
||
| if (consultEndLogs.length === 0) { | ||
| throw new Error(`No 'WXCC_SDK_TASK_CONSULT_END_SUCCESS' logs found. Captured logs: ${JSON.stringify(capturedAdvancedLogs)}`); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Verifies that agent consult transferred logs are present (when consult is converted to transfer). | ||
| * @throws Error if verification fails with detailed error message | ||
| */ | ||
| export function verifyConsultTransferredLogs(): void { | ||
| const consultTransferredLogs = capturedAdvancedLogs.filter(log => log.includes('AgentConsultTransferred')); | ||
|
|
||
| if (consultTransferredLogs.length === 0) { | ||
| throw new Error(`No 'AgentConsultTransferred' logs found. Captured logs: ${JSON.stringify(capturedAdvancedLogs)}`); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Utility function to get all captured logs for debugging purposes. | ||
| * @returns Array of all captured log messages | ||
| */ | ||
| export function getAllCapturedLogs(): string[] { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should remove this if not used
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removed |
||
| return [...capturedAdvancedLogs]; | ||
| } | ||
|
|
||
| /** | ||
| * Initiates a consult with another agent via the agents tab. | ||
| * @param page - The agent's main page | ||
| * @param agentName - Name of the agent to consult with (e.g., 'User2 Agent2') | ||
| * @returns Promise<void> | ||
| */ | ||
| export async function consultViaAgent(page: Page, agentName: string = 'User2 Agent2'): Promise<void> { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. combine 4 methods as one and differentiate using param, param should be a json |
||
| // Click consult with another agent button | ||
| await page.getByTestId('call-control:consult').nth(1).click(); | ||
| // Navigate to Agents tab | ||
| await page.getByRole('tab', { name: 'Agents' }).click(); | ||
|
|
||
| //hover over the agent name - use exact match to avoid confusion with similar names | ||
| await page.getByRole('listitem', { name: agentName, exact: true }).hover(); | ||
|
|
||
| // Select the specific agent | ||
| await page.getByRole('listitem', { name: agentName, exact: true }).getByRole('button').click(); | ||
|
|
||
| // Wait a moment for the consult to be initiated | ||
| await page.waitForTimeout(2000); | ||
| } | ||
|
|
||
| /** | ||
| * Initiates a consult with a queue via the queues tab. | ||
| * @param page - The agent's main page | ||
| * @param queueName - Name of the queue to consult with (e.g., 'Customer Service Queue') | ||
| * @returns Promise<void> | ||
| */ | ||
| export async function consultViaQueue(page: Page, queueName: string): Promise<void> { | ||
| // Click consult with another agent button | ||
| await page.getByTestId('call-control:consult').nth(1).click(); | ||
|
|
||
| // Navigate to Queues tab | ||
| await page.getByRole('tab', { name: 'Queues' }).click(); | ||
|
|
||
| // Hover over the queue name - use exact match to avoid confusion with similar names | ||
| await page.getByRole('listitem', { name: queueName, exact: true }).hover(); | ||
|
|
||
| // Select the specific queue | ||
| await page.getByRole('listitem', { name: queueName, exact: true }).getByRole('button').click(); | ||
|
|
||
| // Wait a moment for the consult to be initiated | ||
| await page.waitForTimeout(2000); | ||
| } | ||
|
|
||
| /** | ||
| * Cancels an ongoing consult and resumes the original call. | ||
| * @param page - The agent's main page | ||
| * @returns Promise<void> | ||
| */ | ||
| export async function cancelConsult(page: Page): Promise<void> { | ||
| // Click cancel consult button | ||
| await page.getByTestId('cancel-consult-btn').click(); | ||
| } | ||
|
|
||
| /** | ||
| * Initiates a transfer via the agents tab (without prior consult). | ||
| * @param page - The agent's main page | ||
| * @param agentName - Name of the agent to transfer to (e.g., 'User2 Agent2') | ||
| * @returns Promise<void> | ||
| */ | ||
| export async function transferViaAgent(page: Page, agentName: string = 'User2 Agent2'): Promise<void> { | ||
| // Click transfer call button | ||
| await page.getByRole('group', { name: 'Call Control with Call' }).getByLabel('Transfer Call').click(); | ||
|
|
||
| // Navigate to Agents tab | ||
| await page.getByRole('tab', { name: 'Agents' }).click(); | ||
|
|
||
| // Hover over the agent name - use exact match to avoid confusion with similar names | ||
| await page.getByRole('listitem', { name: agentName, exact: true }).hover(); | ||
|
|
||
| // Select the specific agent | ||
| await page.getByRole('listitem', { name: agentName, exact: true }).getByRole('button').click(); | ||
|
|
||
| // Wait a moment for the transfer to be processed | ||
| await page.waitForTimeout(2000); | ||
| } | ||
|
|
||
| /** | ||
| * Initiates a transfer via the queues tab (without prior consult). | ||
| * @param page - The agent's main page | ||
| * @param queueName - Name of the queue to transfer to (e.g., 'Customer Service Queue') | ||
| * @returns Promise<void> | ||
| */ | ||
| export async function transferViaQueue(page: Page, queueName: string): Promise<void> { | ||
| // Click transfer call button | ||
| await page.getByRole('group', { name: 'Call Control with Call' }).getByLabel('Transfer Call').click(); | ||
|
|
||
| // Navigate to Queues tab | ||
| await page.getByRole('tab', { name: 'Queues' }).click(); | ||
|
|
||
| // Hover over the queue name - use exact match to avoid confusion with similar names | ||
| await page.getByRole('listitem', { name: queueName, exact: true }).hover(); | ||
|
|
||
| // Select the specific queue | ||
| await page.getByRole('listitem', { name: queueName, exact: true }).getByRole('button').click(); | ||
|
|
||
| // Wait a moment for the transfer to be processed | ||
| await page.waitForTimeout(2000); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| import { Page } from '@playwright/test'; | ||
| import { getCurrentState, changeUserState } from './userStateUtils'; | ||
| import { WRAPUP_REASONS, USER_STATES, RONA_OPTIONS, LOGIN_MODE, LoginMode, ThemeColor, userState, WrapupReason } from 'playwright/constants'; | ||
| import { WRAPUP_REASONS, USER_STATES, RONA_OPTIONS, LOGIN_MODE, LoginMode, ThemeColor, userState, WrapupReason } from '../constants'; | ||
| import { submitWrapup } from './wrapupUtils'; | ||
| import { acceptExtensionCall, submitRonaPopup } from './incomingTaskUtils'; | ||
| import { loginViaAccessToken, disableMultiLogin, enableMultiLogin, initialiseWidgets, enableAllWidgets, } from './initUtils'; | ||
|
|
@@ -317,6 +317,9 @@ export const handleStrayTasks = async (page: Page, extensionPage: Page | null = | |
| const acceptButtonVisible = await acceptButton.isVisible().catch(() => false); | ||
| const isExtensionCall = await (await task.innerText()).includes('Ringing...'); | ||
| if (isExtensionCall) { | ||
| if (!extensionPage) { | ||
| throw new Error('Extension page is not available for handling extension call'); | ||
| } | ||
|
Comment on lines
+320
to
+322
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we rather check if the UI goes rightly to RONA if the extension page is not open?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be done as a separate test? If so, I think it would be more suitable to add it to incomingTask tests.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @rsarika - Here' we are failing the test. However, shouldn't it be a test that simply ensures it goes to RONA if the extension isn't open?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes. we should do this |
||
| const extensionCallVisible = await extensionPage.locator('[data-test="right-action-button"]').waitFor({ state: 'visible', timeout: 40000 }).then(() => true).catch(() => false); | ||
| if (extensionCallVisible) { | ||
| await acceptExtensionCall(extensionPage); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why we are not running this in chromium?