diff --git a/packages/contact-center/store/package.json b/packages/contact-center/store/package.json index 541d5bcf2..39d1bc0d7 100644 --- a/packages/contact-center/store/package.json +++ b/packages/contact-center/store/package.json @@ -23,7 +23,7 @@ "deploy:npm": "yarn npm publish" }, "dependencies": { - "@webex/contact-center": "3.10.0-next.20", + "@webex/contact-center": "3.10.0-next.32", "mobx": "6.13.5", "typescript": "5.6.3" }, diff --git a/playwright/Utils/advancedTaskControlUtils.ts b/playwright/Utils/advancedTaskControlUtils.ts index 8b3364770..99b642cd9 100644 --- a/playwright/Utils/advancedTaskControlUtils.ts +++ b/playwright/Utils/advancedTaskControlUtils.ts @@ -118,6 +118,7 @@ export async function consultOrTransfer( action: 'consult' | 'transfer', value: string ): Promise { + await page.bringToFront(); await openConsultOrTransferMenu(page, action); const popover = await getPopover(page); @@ -139,14 +140,13 @@ export async function consultOrTransfer( // ===== Internal helper functions ===== async function openConsultOrTransferMenu(page: Page, action: 'consult' | 'transfer'): Promise { + await page.bringToFront(); + await dismissOverlays(page); + if (action === 'consult') { - await dismissOverlays(page); - await page.getByTestId('call-control:consult').nth(1).click({timeout: AWAIT_TIMEOUT}); + await page.getByTestId('call-control:consult').first().click({timeout: AWAIT_TIMEOUT}); } else { - await page - .getByRole('group', {name: 'Call Control with Call'}) - .getByLabel('Transfer Call') - .click({timeout: AWAIT_TIMEOUT}); + await page.getByTestId('call-control:transfer').first().click({timeout: AWAIT_TIMEOUT}); } } diff --git a/playwright/Utils/helperUtils.ts b/playwright/Utils/helperUtils.ts index cede87b13..bea43aab2 100644 --- a/playwright/Utils/helperUtils.ts +++ b/playwright/Utils/helperUtils.ts @@ -22,6 +22,18 @@ import { enableAllWidgets, } from './initUtils'; import {stationLogout, telephonyLogin} from './stationLoginUtils'; + +/** + * Creates a prefixed logger function for consistent test logging + * @param prefix - The prefix to use for all log messages (e.g., 'TaskList', 'UserState') + * @returns A function that logs messages with the specified prefix + * @example + * ```typescript + * const log = createLogger('TaskList'); + * log('Starting test'); // Outputs: [TaskList] Starting test + * ``` + */ +export const createLogger = (prefix: string) => (msg: string) => console.log(`[${prefix}] ${msg}`); /** * Parses a time string in MM:SS format and converts it to total seconds * @param timeString - Time string in format "MM:SS" (e.g., "01:30" for 1 minute 30 seconds) @@ -120,6 +132,7 @@ export async function waitForWebSocketReconnection( export const waitForState = async (page: Page, expectedState: userState): Promise => { try { + await page.bringToFront(); await page.waitForFunction( async (expectedStateArg) => { // Re-import getCurrentState in the browser context @@ -303,193 +316,392 @@ export function isColorClose(receivedColor: string, expectedColor: ThemeColor, t * Handles stray incoming tasks by accepting them and performing wrap-up actions, to be used for clean up before tests * @param page - Playwright Page object * @param extensionPage - Optional extension page for handling calls (default: null) - * @param maxIterations - Maximum number of task handling iterations to prevent infinite loops (default: 10) + * @param maxIterations - Maximum number of iterations to prevent infinite loops (default: 10) * @returns Promise - * @description Continuously checks for incoming tasks, accepts them, and performs wrap-up actions until no more tasks are available + * @description Checks in order: RONA popup → incoming tasks → end button → wrapup button + * Continues until nothing actionable is found or maxIterations reached * @example * ```typescript * await handleStrayTasks(page, extensionPage); * ``` */ - export const handleStrayTasks = async ( page: Page, extensionPage: Page | null = null, maxIterations: number = 10 ): Promise => { - await page.waitForTimeout(1000); + const startTime = Date.now(); + const log = (msg: string) => console.log(`[handleStrayTasks] ${msg}`); - const stateSelectVisible = await page - .getByTestId('state-select') - .waitFor({state: 'visible', timeout: 30000}) - .then(() => true) - .catch(() => false); + let iteration = 0; + let tasksHandled = 0; - if (stateSelectVisible) { - const ronapopupVisible = await page - .getByTestId('samples:rona-popup') - .waitFor({state: 'visible', timeout: AWAIT_TIMEOUT}) - .then(() => true) - .catch(() => false); + while (iteration < maxIterations) { + iteration++; + let actionTaken = false; + + // Dismiss any overlays/popovers first + await page.keyboard.press('Escape').catch(() => {}); + await page.waitForTimeout(100); + + // ============================================ + // STEP 1: Check for RONA popup + // ============================================ + const ronaPopup = page.getByTestId('samples:rona-popup'); + const ronaVisible = await ronaPopup.isVisible().catch(() => false); - if (ronapopupVisible) { - await submitRonaPopup(page, RONA_OPTIONS.AVAILABLE); + if (ronaVisible) { + log(`Iteration ${iteration}: RONA popup visible, submitting`); + try { + await submitRonaPopup(page, RONA_OPTIONS.AVAILABLE); + actionTaken = true; + log('RONA popup submitted'); + await page.waitForTimeout(300); + continue; // Start fresh after RONA + } catch (e) { + log(`RONA popup submission failed: ${e}`); + } } - await changeUserState(page, USER_STATES.AVAILABLE); - await page.waitForTimeout(4000); - } - const incomingTaskDiv = page.getByTestId(/^samples:incoming-task(-\w+)?$/); + // ============================================ + // STEP 2: Check for wrapup FIRST (complete pending tasks before accepting new ones) + // ============================================ + const wrapupButton = page.getByTestId('call-control:wrapup-button').first(); + const wrapupVisible = await wrapupButton.isVisible().catch(() => false); + + if (wrapupVisible) { + log(`Iteration ${iteration}: Wrapup visible, submitting`); + try { + await submitWrapup(page, WRAPUP_REASONS.SALE); + tasksHandled++; + actionTaken = true; + log(`Wrapup submitted (${tasksHandled} total)`); + await page.waitForTimeout(300); + continue; // Check for more pending tasks + } catch (e) { + log(`Wrapup submission failed: ${e}`); + } + } + + // ============================================ + // STEP 3: Check for end button (end active calls before accepting new ones) + // ============================================ + const endButton = page.getByTestId('call-control:end-call').first(); + const endButtonVisible = await endButton.isVisible().catch(() => false); - let iterations = 0; - while (iterations < maxIterations) { - iterations++; - let flag1 = false; - let flag2 = true; + if (endButtonVisible) { + let endButtonEnabled = await endButton.isEnabled().catch(() => false); + + if (!endButtonEnabled) { + // End button disabled - try to resume from hold first + const holdToggle = page.getByTestId('call-control:hold-toggle').first(); + const holdToggleVisible = await holdToggle.isVisible().catch(() => false); + + if (holdToggleVisible) { + log(`Iteration ${iteration}: End button disabled, attempting resume from hold`); + try { + await holdCallToggle(page); + await page.waitForTimeout(500); + endButtonEnabled = await endButton.isEnabled().catch(() => false); + } catch (e) { + log(`Resume from hold failed: ${e}`); + } + } else { + log(`Iteration ${iteration}: End button disabled, no hold toggle visible`); + } + } - // Check if there's actually anything to handle before processing + if (endButtonEnabled) { + log(`Iteration ${iteration}: Clicking end button`); + try { + await endButton.click({timeout: AWAIT_TIMEOUT}); + await page.waitForTimeout(500); + + // Verify the click worked - either end button gone or wrapup appeared + const endStillVisible = await endButton.isVisible().catch(() => false); + const wrapupNowVisible = await wrapupButton.isVisible().catch(() => false); + + if (!endStillVisible || wrapupNowVisible) { + actionTaken = true; + log('End button clicked - state changed'); + // Don't continue - fall through to check wrapup immediately + } else { + log('End button clicked but still visible - click may not have worked'); + } + } catch (e) { + log(`End call click failed: ${e}`); + } + } + + // After clicking end, check for wrapup immediately (same iteration) + const wrapupAfterEnd = await wrapupButton.isVisible().catch(() => false); + if (wrapupAfterEnd) { + log(`Iteration ${iteration}: Wrapup appeared after end, submitting`); + try { + await submitWrapup(page, WRAPUP_REASONS.SALE); + tasksHandled++; + actionTaken = true; + log(`Wrapup submitted (${tasksHandled} total)`); + await page.waitForTimeout(300); + continue; + } catch (e) { + log(`Wrapup submission failed: ${e}`); + } + } + + if (actionTaken) { + continue; + } + } + + // ============================================ + // STEP 4: Check for incoming tasks (only accept if no active task to handle) + // ============================================ + const incomingTaskDiv = page.getByTestId(/^samples:incoming-task(-\w+)?$/); const hasIncomingTask = await incomingTaskDiv .first() .isVisible() .catch(() => false); - const hasEndButton = await page - .getByTestId('call-control:end-call') - .first() - .isVisible() - .catch(() => false); - const hasWrapupButton = await page - .getByTestId('call-control:wrapup-button') - .first() - .isVisible() - .catch(() => false); - - if (!hasIncomingTask && !hasEndButton && !hasWrapupButton) { - // Nothing to handle, exit early - break; - } - // Inner task acceptance loop with timeout protection - let taskAttempts = 0; - const maxTaskAttempts = 5; - - while (taskAttempts < maxTaskAttempts) { - taskAttempts++; + if (hasIncomingTask) { const task = incomingTaskDiv.first(); - let isTaskVisible = await task.isVisible().catch(() => false); - if (!isTaskVisible) break; - - const acceptButton = task.getByTestId('task:accept-button').first(); - const acceptButtonVisible = await acceptButton.isVisible().catch(() => false); - const isExtensionCall = await (await task.innerText()).includes('Ringing...'); + const taskText = await task.innerText().catch(() => ''); + const isExtensionCall = taskText.includes('Ringing...'); if (isExtensionCall) { - if (!extensionPage) { - throw new Error('Extension page is not available for handling extension call'); - } - const extensionCallVisible = await extensionPage - .locator('[data-test="right-action-button"]') - .waitFor({state: 'visible', timeout: 40000}) // Restored original timeout - .then(() => true) - .catch(() => false); - if (extensionCallVisible) { - await acceptExtensionCall(extensionPage); - flag1 = true; + // Extension call - try extensionPage first, fallback to waiting for RONA + if (extensionPage) { + log(`Iteration ${iteration}: Extension call detected`); + try { + // Dismiss any dialogs on extension page first + await extensionPage.keyboard.press('Escape').catch(() => {}); + await extensionPage.waitForTimeout(200); + + const extButton = extensionPage.locator('[data-test="right-action-button"]'); + const extButtonVisible = await extButton + .waitFor({state: 'visible', timeout: 5000}) + .then(() => true) + .catch(() => false); + + if (extButtonVisible) { + await acceptExtensionCall(extensionPage); + log(`Extension call accepted`); + await page.waitForTimeout(500); + // After accepting, immediately try to end and wrapup + const endBtnAfterAccept = page.getByTestId('call-control:end-call').first(); + const endVisibleAfterAccept = await endBtnAfterAccept.isVisible().catch(() => false); + if (endVisibleAfterAccept) { + const endEnabledAfterAccept = await endBtnAfterAccept.isEnabled().catch(() => false); + if (endEnabledAfterAccept) { + log(`Iteration ${iteration}: Ending accepted extension call`); + await endBtnAfterAccept.click({timeout: AWAIT_TIMEOUT}).catch(() => {}); + await page.waitForTimeout(500); + const wrapupAfterEnd = await wrapupButton.isVisible().catch(() => false); + if (wrapupAfterEnd) { + await submitWrapup(page, WRAPUP_REASONS.SALE).catch(() => {}); + tasksHandled++; + log(`Task fully handled (${tasksHandled} total)`); + await page.waitForTimeout(300); + } + } + } + actionTaken = true; + continue; + } + } catch (e) { + log(`Extension call accept failed: ${e}`); + } } else { - console.warn('Extension call timeout - skipping task'); - break; // Skip this task instead of throwing error + // No extensionPage - wait for RONA timeout + log(`Iteration ${iteration}: Extension call detected, no extensionPage - waiting for timeout`); + await page.waitForTimeout(2000); + // Check if RONA appeared + const ronaAfterWait = await ronaPopup.isVisible().catch(() => false); + if (ronaAfterWait) { + continue; // Handle RONA on next iteration + } + // If still no RONA, we can't handle this - exit + const stillHasExtCall = await incomingTaskDiv + .first() + .isVisible() + .catch(() => false); + if (stillHasExtCall) { + log(`Iteration ${iteration}: Extension call still present, cannot handle without extensionPage - exiting`); + break; + } } } else { - try { - await acceptButton.click({timeout: AWAIT_TIMEOUT}); - flag1 = true; - } catch (error) { - console.warn('Failed to click accept button:', error); + // Regular task - check if accept button is enabled + const acceptButton = task.getByTestId('task:accept-button').first(); + const acceptVisible = await acceptButton.isVisible().catch(() => false); + const acceptEnabled = await acceptButton.isEnabled().catch(() => false); + + if (acceptVisible && acceptEnabled) { + log(`Iteration ${iteration}: Incoming task found, accepting`); + try { + await acceptButton.click({timeout: AWAIT_TIMEOUT}); + log(`Task accepted`); + await page.waitForTimeout(2000); + // After accepting, immediately try to end and wrapup (same iteration) + const endBtnAfterAccept = page.getByTestId('call-control:end-call').first(); + const endVisibleAfterAccept = await endBtnAfterAccept.isVisible().catch(() => false); + if (endVisibleAfterAccept) { + const endEnabledAfterAccept = await endBtnAfterAccept.isEnabled().catch(() => false); + if (endEnabledAfterAccept) { + log(`Iteration ${iteration}: Ending accepted task`); + await endBtnAfterAccept.click({timeout: AWAIT_TIMEOUT}).catch(() => {}); + await page.waitForTimeout(500); + const wrapupAfterEnd = await wrapupButton.isVisible().catch(() => false); + if (wrapupAfterEnd) { + log(`Iteration ${iteration}: Wrapup appeared, submitting`); + await submitWrapup(page, WRAPUP_REASONS.SALE).catch(() => {}); + tasksHandled++; + log(`Task fully handled (${tasksHandled} total)`); + await page.waitForTimeout(300); + } + } + } + actionTaken = true; + continue; + } catch (e) { + log(`Accept click failed: ${e}`); + } + } else if (acceptVisible && !acceptEnabled) { + log(`Iteration ${iteration}: Accept button visible but disabled - skipping`); } } - await page.waitForTimeout(1000); } - const endButton = page.getByTestId('call-control:end-call').first(); - const endButtonVisible = await endButton - .waitFor({state: 'visible', timeout: 2000}) - .then(() => true) - .catch(() => false); - if (endButtonVisible) { - await page.waitForTimeout(2000); - await endButton.click({timeout: AWAIT_TIMEOUT}); - await submitWrapup(page, WRAPUP_REASONS.SALE); - } else { - const wrapupBox = page.getByTestId('call-control:wrapup-button').first(); - const isWrapupBoxVisible = await wrapupBox - .waitFor({state: 'visible', timeout: 2000}) - .then(() => true) + // ============================================ + // Check if anything is still pending that we couldn't handle + // ============================================ + if (!actionTaken) { + const stillHasTask = await incomingTaskDiv + .first() + .isVisible() .catch(() => false); - if (isWrapupBoxVisible) { - await page.waitForTimeout(2000); - await submitWrapup(page, WRAPUP_REASONS.SALE); - await page.waitForTimeout(2000); + const stillHasEnd = await endButton.isVisible().catch(() => false); + const stillHasWrapup = await wrapupButton.isVisible().catch(() => false); + + // Check if end button is visible but disabled (stuck state) + if (stillHasEnd && !stillHasWrapup) { + const endEnabled = await endButton.isEnabled().catch(() => false); + const holdToggle = page.getByTestId('call-control:hold-toggle').first(); + const holdVisible = await holdToggle.isVisible().catch(() => false); + + if (!endEnabled && !holdVisible) { + log(`Iteration ${iteration}: End button disabled with no way to enable - cannot handle, exiting`); + break; + } + } + + if (stillHasWrapup) { + log(`Iteration ${iteration}: Wrapup visible but couldn't submit - will retry`); + await page.waitForTimeout(500); + } else if (stillHasEnd) { + const endEnabled = await endButton.isEnabled().catch(() => false); + if (endEnabled) { + if (iteration >= 3) { + log(`Iteration ${iteration}: End button clicks not working after ${iteration} attempts - exiting`); + break; + } + log(`Iteration ${iteration}: End button click didn't work - will retry`); + await page.waitForTimeout(500); + } + } else if (stillHasTask) { + log(`Iteration ${iteration}: Task visible but cannot act - will retry`); + await page.waitForTimeout(500); } else { - flag2 = false; + log(`Iteration ${iteration}: Nothing found, cleanup complete`); + break; } } + } - if (!flag1 && !flag2) { - break; + if (iteration >= maxIterations) { + log(`Max iterations (${maxIterations}) reached`); + } + + // Ensure user is in Available state at the end + const stateSelectVisible = await page + .getByTestId('state-select') + .isVisible() + .catch(() => false); + + if (stateSelectVisible) { + try { + await changeUserState(page, USER_STATES.AVAILABLE); + } catch (e) { + log(`Failed to set Available state: ${e}`); } } - console.log(`Completed stray task handling after ${iterations} iterations`); + const duration = Date.now() - startTime; + log(`Completed in ${duration}ms - ${tasksHandled} task(s) handled in ${iteration} iteration(s)`); }; /** * Clears any pending call UI on the page by ending the call and/or submitting wrapup if visible. - * Does nothing if neither end-call nor wrapup controls are present. + * Follows same logic as handleStrayTasks: end button (resume if disabled) → wrapup + * @returns true if something was cleared, false otherwise */ -export async function clearPendingCallAndWrapup(page: Page): Promise { - // If wrapup is available, submit it - const wrapupBtn = page.getByTestId('call-control:wrapup-button').first(); - const wrapupVisible = await wrapupBtn.isVisible().catch(() => false); - if (wrapupVisible) { - await submitWrapup(page, WRAPUP_REASONS.SALE); - await page.waitForTimeout(500); - return; - } +export async function clearPendingCallAndWrapup(page: Page): Promise { + const log = (msg: string) => console.log(`[clearPendingCallAndWrapup] ${msg}`); + + // Dismiss any open popovers first + await page.keyboard.press('Escape').catch(() => {}); + await page.waitForTimeout(200); const endBtn = page.getByTestId('call-control:end-call').first(); + const wrapupBtn = page.getByTestId('call-control:wrapup-button').first(); + + // Check end button first const endVisible = await endBtn.isVisible().catch(() => false); + if (endVisible) { - const endEnabled = await endBtn.isEnabled().catch(() => false); + let endEnabled = await endBtn.isEnabled().catch(() => false); + + // If disabled, try to resume from hold + if (!endEnabled) { + log('End button disabled, attempting resume from hold'); + try { + await holdCallToggle(page); + await page.waitForTimeout(500); + endEnabled = await endBtn.isEnabled().catch(() => false); + } catch (e) { + log(`Resume failed: ${e}`); + } + } + + // Try to end the call if (endEnabled) { try { + log('Clicking end button'); await endBtn.click({timeout: AWAIT_TIMEOUT}); - await submitWrapup(page, WRAPUP_REASONS.SALE); await page.waitForTimeout(500); - } catch {} - } else { - // If end button is disabled, try resuming from hold then end; otherwise, see if wrapup is available - try { - await holdCallToggle(page); - } catch {} - const endEnabledAfterResume = await endBtn.isEnabled().catch(() => false); - if (endEnabledAfterResume) { - try { - await endBtn.click({timeout: AWAIT_TIMEOUT}); - await submitWrapup(page, WRAPUP_REASONS.SALE); - await page.waitForTimeout(500); - return; - } catch {} + } catch (e) { + log(`End click failed: ${e}`); } + } + } - // If resume path failed, see if wrapup became available instead; otherwise, skip silently - const wrapupNowVisible = await wrapupBtn.isVisible().catch(() => false); - if (wrapupNowVisible) { - try { - await submitWrapup(page, WRAPUP_REASONS.SALE); - await page.waitForTimeout(500); - } catch {} - } + // Check wrapup button + const wrapupVisible = await wrapupBtn.isVisible().catch(() => false); + + if (wrapupVisible) { + try { + log('Submitting wrapup'); + await submitWrapup(page, WRAPUP_REASONS.SALE); + await page.waitForTimeout(500); + return true; + } catch (e) { + log(`Wrapup failed: ${e}`); + return false; } } + + // Return true if end button was clicked (even without wrapup) + return endVisible; } /* diff --git a/playwright/Utils/incomingTaskUtils.ts b/playwright/Utils/incomingTaskUtils.ts index 78a3935b8..1a0fe058e 100644 --- a/playwright/Utils/incomingTaskUtils.ts +++ b/playwright/Utils/incomingTaskUtils.ts @@ -39,6 +39,7 @@ const transporter = nodemailer.createTransport({ * @param number Phone number to dial (defaults to PW_ENTRY_POINT env variable) */ export async function createCallTask(page: Page, number: string) { + await page.bringToFront(); if (!number || number.trim() === '') { throw new Error('Dial number is required'); } @@ -239,47 +240,116 @@ export async function createEmailTask(to: string) { } /** - * Accepts an incoming task of the given type (call, chat, email, social). - * Expects the incoming task to be already there. + * Gets the incoming task div locator for a given task type. * @param page Playwright Page object * @param type Task type (see TASK_TYPES) - * @throws Error if accept button is not found + * @returns Locator for the incoming task div */ -export async function acceptIncomingTask(page: Page, type: TaskType) { - await page.waitForTimeout(2000); - let incomingTaskDiv; +export function getIncomingTaskLocator(page: Page, type: TaskType) { if (type === TASK_TYPES.CALL) { - incomingTaskDiv = page.getByTestId('samples:incoming-task-telephony').first(); - const isExtensionCall = await (await incomingTaskDiv.innerText()).includes(TEST_DATA.EXTENSION_CALL_INDICATOR); - if (isExtensionCall) { - throw new Error('This is an extension call, use acceptExtensionCall instead'); - } + return page.getByTestId('samples:incoming-task-telephony').first(); } else if (type === TASK_TYPES.CHAT) { - incomingTaskDiv = page.getByTestId('samples:incoming-task-chat').first(); + return page.getByTestId('samples:incoming-task-chat').first(); } else if (type === TASK_TYPES.EMAIL) { - incomingTaskDiv = page.getByTestId('samples:incoming-task-email').first(); + return page.getByTestId('samples:incoming-task-email').first(); } else if (type === TASK_TYPES.SOCIAL) { - incomingTaskDiv = page.locator('samples:incoming-task-social').first(); + return page.locator('samples:incoming-task-social').first(); } - incomingTaskDiv = incomingTaskDiv.first(); - await expect(incomingTaskDiv).toBeVisible({timeout: AWAIT_TIMEOUT}); - const acceptButton = incomingTaskDiv.getByTestId('task:accept-button').first(); - if (!(await acceptButton.isVisible())) { - throw new Error('Accept button not found'); + throw new Error(`Unknown task type: ${type}`); +} + +/** + * Waits for an incoming task of the given type to be visible. + * Brings the page to front and waits for the task div to appear. + * @param page Playwright Page object + * @param type Task type (see TASK_TYPES) + * @param timeout Optional timeout in ms (default: 40000) + * @returns Locator for the incoming task div + */ +export async function waitForIncomingTask(page: Page, type: TaskType, timeout: number = 40000) { + await page.bringToFront(); + const incomingTaskDiv = getIncomingTaskLocator(page, type); + await incomingTaskDiv.waitFor({state: 'visible', timeout}); + return incomingTaskDiv; +} + +/** + * Accepts an incoming task of the given type (call, chat, email, social). + * Waits for the task to appear, then clicks the accept button. + * @param page Playwright Page object + * @param type Task type (see TASK_TYPES) + * @param timeout Optional timeout in ms for waiting for task (default: 40000) + * @throws Error if accept button is not found or if this is an extension call + */ +export async function acceptIncomingTask(page: Page, type: TaskType, timeout: number = 40000) { + const log = (msg: string) => console.log(`[acceptIncomingTask] ${msg}`); + + log(`Starting - type: ${type}, timeout: ${timeout}`); + await page.bringToFront(); + log('Page brought to front'); + + const incomingTaskDiv = await waitForIncomingTask(page, type, timeout); + log('Incoming task div found'); + + // Check if this is an extension call (only for CALL type) + if (type === TASK_TYPES.CALL) { + const taskText = await incomingTaskDiv.innerText(); + log(`Task text: "${taskText.substring(0, 100)}..."`); + if (taskText.includes(TEST_DATA.EXTENSION_CALL_INDICATOR)) { + log('ERROR: This is an extension call, throwing error'); + throw new Error('This is an extension call, use acceptExtensionCall instead'); + } } - // Wait for button to be enabled and clickable + const acceptButton = incomingTaskDiv.getByTestId('task:accept-button').first(); + log('Looking for accept button'); + + const isButtonVisible = await acceptButton.isVisible().catch(() => false); + log(`Accept button visible: ${isButtonVisible}`); + await acceptButton.waitFor({state: 'visible', timeout: AWAIT_TIMEOUT}); + log('Accept button is visible'); + + const isButtonEnabled = await acceptButton.isEnabled().catch(() => false); + log(`Accept button enabled: ${isButtonEnabled}`); + await expect(acceptButton).toBeEnabled({timeout: AWAIT_TIMEOUT}); + log('Accept button is enabled'); - // Use force click as backup or add retry logic try { + await page.waitForTimeout(2000); await acceptButton.click({timeout: AWAIT_TIMEOUT}); + log('Accept button clicked successfully'); } catch (error) { + log(`Normal click failed: ${error}, retrying with force click`); // Retry with force click if normal click fails await acceptButton.click({force: true, timeout: AWAIT_TIMEOUT}); + log('Force click succeeded'); } + await page.waitForTimeout(2000); + + // Verify the task was actually accepted by checking if incoming task div is gone + let isStillVisible = await incomingTaskDiv.isVisible().catch(() => false); + if (isStillVisible) { + log('WARNING: Incoming task div is still visible after clicking accept - retrying once more'); + // Retry clicking the accept button one more time + const retryAcceptButton = incomingTaskDiv.getByTestId('task:accept-button').first(); + const isRetryButtonVisible = await retryAcceptButton.isVisible().catch(() => false); + if (isRetryButtonVisible) { + await retryAcceptButton.click({force: true, timeout: AWAIT_TIMEOUT}); + log('Retry click on accept button completed'); + await page.waitForTimeout(2000); + isStillVisible = await incomingTaskDiv.isVisible().catch(() => false); + } + if (isStillVisible) { + log('WARNING: Incoming task div is still visible after retry - task may not have been accepted'); + } else { + log('SUCCESS: Incoming task div is no longer visible after retry - task accepted'); + } + } else { + log('SUCCESS: Incoming task div is no longer visible - task accepted'); + } } /** @@ -290,6 +360,7 @@ export async function acceptIncomingTask(page: Page, type: TaskType) { * @throws Error if decline button is not found */ export async function declineIncomingTask(page: Page, type: TaskType) { + await page.bringToFront(); let incomingTaskDiv; if (type === TASK_TYPES.CALL) { incomingTaskDiv = page.getByTestId('samples:incoming-task-telephony').first(); @@ -321,14 +392,24 @@ export async function declineIncomingTask(page: Page, type: TaskType) { */ export async function acceptExtensionCall(page: Page) { try { - await page.waitForTimeout(2000); + await page.bringToFront(); await expect(page).toHaveURL(/.*\.webex\.com\/calling.*/); } catch (error) { throw new Error('The Input Page should be logged into calling web-client.'); } + + // Dismiss any blocking dialog with "Close" button + const closeButton = page.locator('mdc-button:has-text("Close")').first(); + const closeVisible = await closeButton.isVisible().catch(() => false); + if (closeVisible) { + await closeButton.click({timeout: 3000}).catch(() => {}); + await page.waitForTimeout(300); + } + await page.locator('[data-test="right-action-button"]').waitFor({state: 'visible', timeout: AWAIT_TIMEOUT}); + await page.waitForTimeout(2000); await page.locator('[data-test="right-action-button"]').click({timeout: AWAIT_TIMEOUT}); - await page.waitForTimeout(500); + await page.waitForTimeout(1000); } /** @@ -337,7 +418,7 @@ export async function acceptExtensionCall(page: Page) { */ export async function declineExtensionCall(page: Page) { try { - await page.waitForTimeout(2000); + await page.bringToFront(); await expect(page).toHaveURL(/.*\.webex\.com\/calling.*/); } catch (error) { throw new Error('The Input Page should be logged into calling web-client.'); @@ -352,7 +433,7 @@ export async function declineExtensionCall(page: Page) { */ export async function endExtensionCall(page: Page) { try { - await page.waitForTimeout(2000); + await page.bringToFront(); await expect(page).toHaveURL(/.*\.webex\.com\/calling.*/); } catch (error) { throw new Error('The Input Page should be logged into calling web-client.'); @@ -371,6 +452,7 @@ export async function endExtensionCall(page: Page) { * @throws Error if login fails after maxRetries */ export async function loginExtension(page: Page, email: string, password: string) { + await page.bringToFront(); if (!email || !password) { throw new Error('Email and password are required for loginExtension'); } @@ -398,6 +480,7 @@ export async function loginExtension(page: Page, email: string, password: string .then(() => true) .catch(() => false); if (!isLoginPageVisible) { + await page.bringToFront(); await expect(page.getByRole('button', {name: 'Back to sign in'})).toBeVisible({timeout: AWAIT_TIMEOUT}); await page.getByRole('button', {name: 'Back to sign in'}).click({timeout: AWAIT_TIMEOUT}); await page.getByRole('button', {name: 'Sign in'}).waitFor({state: 'visible', timeout: AWAIT_TIMEOUT}); @@ -429,7 +512,7 @@ export async function submitRonaPopup(page: Page, nextState: RonaOption) { if (!nextState) { throw new Error('RONA next state selection is required'); } - await page.waitForTimeout(1000); + await page.bringToFront(); await page.getByTestId('samples:rona-popup').waitFor({state: 'visible', timeout: AWAIT_TIMEOUT}); await page.waitForTimeout(1000); await expect(page.getByTestId('samples:rona-select-state')).toBeVisible({timeout: AWAIT_TIMEOUT}); diff --git a/playwright/Utils/stationLoginUtils.ts b/playwright/Utils/stationLoginUtils.ts index 65c754686..3bbf15f67 100644 --- a/playwright/Utils/stationLoginUtils.ts +++ b/playwright/Utils/stationLoginUtils.ts @@ -118,13 +118,14 @@ export const dialLogin = async (page: Page, dialNumber?: string): Promise * ``` */ export const stationLogout = async (page: Page): Promise => { - // Ensure the logout button is visible before clicking + // Wait for the logout button to be visible before clicking const logoutButton = page.getByTestId('samples:station-logout-button'); - const isLogoutButtonVisible = await logoutButton.isVisible().catch(() => false); - if (!isLogoutButtonVisible) { + try { + await logoutButton.waitFor({state: 'visible', timeout: AWAIT_TIMEOUT}); + } catch { throw new Error('Station logout button is not visible. Cannot perform logout.'); } - await page.getByTestId('samples:station-logout-button').click({timeout: AWAIT_TIMEOUT}); + await logoutButton.click({timeout: AWAIT_TIMEOUT}); //check if the station logout button is hidden after logouts const isLogoutButtonHidden = await page .getByTestId('samples:station-logout-button') @@ -147,6 +148,8 @@ export const stationLogout = async (page: Page): Promise => { } catch (e) { throw new Error(`Station logout failed: ${e instanceof Error ? e.message : 'Unknown error'}`); } + } else { + await page.waitForTimeout(5000); } }; diff --git a/playwright/Utils/userStateUtils.ts b/playwright/Utils/userStateUtils.ts index 887e420c3..3c7e84969 100644 --- a/playwright/Utils/userStateUtils.ts +++ b/playwright/Utils/userStateUtils.ts @@ -19,6 +19,7 @@ dotenv.config(); export const changeUserState = async (page: Page, userState: string): Promise => { // Get the current state name with timeout, return early if not found try { + await page.bringToFront(); const currentState = await page .getByTestId('state-select') .getByTestId('state-name') @@ -40,7 +41,7 @@ export const changeUserState = async (page: Page, userState: string): Promise => { + await page.bringToFront(); const stateName = await page .getByTestId('state-select') .getByTestId('state-name') @@ -74,6 +76,7 @@ export const getCurrentState = async (page: Page): Promise => { * ``` */ export const verifyCurrentState = async (page: Page, expectedState: string): Promise => { + await page.bringToFront(); const currentState = await getCurrentState(page); if (currentState !== expectedState) { throw new Error(`Expected state "${expectedState}" but found "${currentState}".`); @@ -93,6 +96,7 @@ export const verifyCurrentState = async (page: Page, expectedState: string): Pro * ``` */ export const getStateElapsedTime = async (page: Page): Promise => { + await page.bringToFront(); // Directly select the timer by its test id const timerText = await page.getByTestId('elapsed-time').innerText({timeout: AWAIT_TIMEOUT}); return timerText.trim(); diff --git a/playwright/Utils/wrapupUtils.ts b/playwright/Utils/wrapupUtils.ts index 5ab4ebfaf..acee77d96 100644 --- a/playwright/Utils/wrapupUtils.ts +++ b/playwright/Utils/wrapupUtils.ts @@ -12,6 +12,12 @@ export async function submitWrapup(page: Page, reason: WrapupReason): Promise true) .catch(() => false); if (!isWrapupBoxVisible) throw new Error('Wrapup box is not visible'); - await wrapupBox.first().click({timeout: AWAIT_TIMEOUT}); - await page.waitForTimeout(UI_SETTLE_TIMEOUT); + + // Check if dropdown is already open (aria-expanded="true") + const isAlreadyOpen = (await wrapupBox.first().getAttribute('aria-expanded')) === 'true'; + if (!isAlreadyOpen) { + await wrapupBox.first().click({timeout: AWAIT_TIMEOUT}); + await page.waitForTimeout(UI_SETTLE_TIMEOUT); + } await expect(page.getByTestId('call-control:wrapup-select').first()).toBeVisible({timeout: AWAIT_TIMEOUT}); await page.getByTestId('call-control:wrapup-select').first().click({timeout: AWAIT_TIMEOUT}); await page.waitForTimeout(UI_SETTLE_TIMEOUT); diff --git a/playwright/constants.ts b/playwright/constants.ts index c0aaf4ace..e28e48f4d 100644 --- a/playwright/constants.ts +++ b/playwright/constants.ts @@ -41,7 +41,7 @@ export const DEFAULT_TIMEOUT = 5000; export const UI_SETTLE_TIMEOUT = 2000; export const FORM_FIELD_TIMEOUT = 20000; export const OPERATION_TIMEOUT = 30000; -export const NETWORK_OPERATION_TIMEOUT = 35000; +export const NETWORK_OPERATION_TIMEOUT = 40000; // Specific timeouts for incoming task operations export const CHAT_LAUNCHER_TIMEOUT = 60000; diff --git a/playwright/suites/dial-number-tests.spec.ts b/playwright/suites/dial-number-tests.spec.ts new file mode 100644 index 000000000..4ea3b9736 --- /dev/null +++ b/playwright/suites/dial-number-tests.spec.ts @@ -0,0 +1,4 @@ +import {test} from '@playwright/test'; +import createDialNumberTaskControlTests from '../tests/dial-number-task-control-test.spec'; + +test.describe('Dial Number Task Control Tests', createDialNumberTaskControlTests); diff --git a/playwright/suites/digital-incoming-task-tests.spec.ts b/playwright/suites/digital-incoming-task-tests.spec.ts index 865d52f08..33053164e 100644 --- a/playwright/suites/digital-incoming-task-tests.spec.ts +++ b/playwright/suites/digital-incoming-task-tests.spec.ts @@ -1,5 +1,5 @@ import {test} from '@playwright/test'; - import createDigitalIncomingTaskAndTaskControlsTests from '../tests/digital-incoming-task-and-task-controls.spec'; +import createDialNumberTaskControlTests from '../tests/dial-number-task-control-test.spec'; test.describe('Digital Incoming and Task Controls Tests', createDigitalIncomingTaskAndTaskControlsTests); diff --git a/playwright/test-data.ts b/playwright/test-data.ts index f60f1a0cb..491cff4d6 100644 --- a/playwright/test-data.ts +++ b/playwright/test-data.ts @@ -58,4 +58,15 @@ export const USER_SETS = { ENTRY_POINT: env.PW_ENTRY_POINT5, TEST_SUITE: 'advanced-task-controls-tests.spec.ts', }, + SET_6: { + AGENTS: { + AGENT1: {username: 'user17', extension: '1017', agentName: 'User17 Agent17'}, + AGENT2: {username: 'user18', extension: '1018', agentName: 'User18 Agent18'}, + }, + QUEUE_NAME: 'Queue e2e 6', + CHAT_URL: `${env.PW_CHAT_URL}-e2e-6.html`, + EMAIL_ENTRY_POINT: `${env.PW_SANDBOX}.e2e6@gmail.com`, + ENTRY_POINT: env.PW_ENTRY_POINT6, + TEST_SUITE: 'dial-number-tests.spec.ts', + }, }; diff --git a/playwright/test-manager.ts b/playwright/test-manager.ts index 20f0a8738..8aa49c76f 100644 --- a/playwright/test-manager.ts +++ b/playwright/test-manager.ts @@ -4,7 +4,7 @@ import {stationLogout, telephonyLogin} from './Utils/stationLoginUtils'; import {loginExtension} from './Utils/incomingTaskUtils'; import {setupConsoleLogging} from './Utils/taskControlUtils'; import {setupAdvancedConsoleLogging} from './Utils/advancedTaskControlUtils'; -import {pageSetup} from './Utils/helperUtils'; +import {pageSetup, handleStrayTasks} from './Utils/helperUtils'; import { LOGIN_MODE, LoginMode, @@ -328,7 +328,7 @@ export class TestManager { 'dial number login' ); // Ensure only one page remains in the Dial Number context to avoid duplicate web client instances - await this.enforceSingleDialNumberInOwnContext(); + // await this.enforceSingleDialNumberInOwnContext(); } // Helper method for Caller setup @@ -396,11 +396,23 @@ export class TestManager { agent1LoginMode: LOGIN_MODE.EXTENSION, enableConsoleLogging: true, enableAdvancedLogging: true, - needDialNumberLogin: true, + needDialNumberLogin: false, }); } async setupForAdvancedCombinations(browser: Browser) { + await this.setup(browser, { + needsAgent1: true, + needsAgent2: true, + needsCaller: true, + needDialNumberLogin: false, + agent1LoginMode: LOGIN_MODE.DESKTOP, + enableConsoleLogging: true, + enableAdvancedLogging: true, + }); + } + + async setupForDialNumber(browser: Browser) { await this.setup(browser, { needsAgent1: true, needsAgent2: true, @@ -537,7 +549,31 @@ export class TestManager { }); } + /** + * Soft cleanup - only handles stray tasks without logging out or closing browsers. + * Use this in afterAll to clean up state between test files. + */ + async softCleanup(): Promise { + const cleanupOps: Promise[] = []; + + if (this.agent1Page) { + cleanupOps.push(handleStrayTasks(this.agent1Page, this.agent1ExtensionPage)); + } + if (this.agent2Page) { + cleanupOps.push(handleStrayTasks(this.agent2Page)); + } + + await Promise.all(cleanupOps); + } + + /** + * Full cleanup - logs out and closes all pages/contexts. + * Use this only at the end of the entire test suite. + */ async cleanup(): Promise { + // First handle any stray tasks + await this.softCleanup().catch(() => {}); + // Logout operations - can be done in parallel const logoutOperations: Promise[] = []; @@ -591,15 +627,18 @@ export class TestManager { await Promise.all(cleanupOperations); } - // Helper method to hard-reset the dial number login session - public async resetDialNumberSession(): Promise { - if (!this.dialNumberPage || !this.dialNumberContext) { + // Helper method to hard-reset a session (caller, dialNumber, or extension) + public async resetSession(sessionType: 'caller' | 'dialNumber' | 'extension'): Promise { + const {page, context, username, password, label} = this.getSessionConfig(sessionType); + + if (!page || !context) { return; } + const envTokens = this.getEnvTokens(); try { - await this.dialNumberContext.clearCookies(); - await this.dialNumberPage.evaluate(() => { + await context.clearCookies(); + await page.evaluate(() => { try { localStorage.clear(); } catch {} @@ -608,11 +647,51 @@ export class TestManager { } catch {} }); // Navigate fresh and login again - await this.dialNumberPage.goto(CALL_URL); - await loginExtension(this.dialNumberPage, envTokens.dialNumberUsername!, envTokens.dialNumberPassword!); - await this.enforceSingleDialNumberInOwnContext(); + await page.goto(CALL_URL); + await loginExtension(page, username(envTokens), password(envTokens)); + + // Enforce single page for dialNumber context + if (sessionType === 'dialNumber') { + await this.enforceSingleDialNumberInOwnContext(); + } } catch (error) { - throw new Error(`Failed to reset dial number session: ${error}`); + throw new Error(`Failed to reset ${label} session: ${error}`); + } + } + + // Helper to get session configuration based on type + private getSessionConfig(sessionType: 'caller' | 'dialNumber' | 'extension'): { + page: Page | undefined; + context: BrowserContext | undefined; + username: (env: EnvTokens) => string; + password: (env: EnvTokens) => string; + label: string; + } { + switch (sessionType) { + case 'caller': + return { + page: this.callerPage, + context: this.callerExtensionContext, + username: (env) => env.agent2Username, + password: (env) => env.password, + label: 'caller', + }; + case 'dialNumber': + return { + page: this.dialNumberPage, + context: this.dialNumberContext, + username: (env) => env.dialNumberUsername!, + password: (env) => env.dialNumberPassword!, + label: 'dial number', + }; + case 'extension': + return { + page: this.agent1ExtensionPage, + context: this.extensionContext, + username: (env) => env.agent1Username, + password: (env) => env.password, + label: 'extension', + }; } } diff --git a/playwright/tests/Untitled b/playwright/tests/Untitled new file mode 100644 index 000000000..cc3394bbc --- /dev/null +++ b/playwright/tests/Untitled @@ -0,0 +1 @@ +hould change state to Available and verify theme and timer reset \ No newline at end of file diff --git a/playwright/tests/advance-task-control-combinations-test.spec.ts b/playwright/tests/advance-task-control-combinations-test.spec.ts index 029bab244..830894a44 100644 --- a/playwright/tests/advance-task-control-combinations-test.spec.ts +++ b/playwright/tests/advance-task-control-combinations-test.spec.ts @@ -4,76 +4,83 @@ import { consultOrTransfer, clearAdvancedCapturedLogs, verifyConsultStartSuccessLogs, - verifyConsultTransferredLogs, - ensureDialNumberLoggedIn, } from '../Utils/advancedTaskControlUtils'; import {changeUserState, verifyCurrentState} from '../Utils/userStateUtils'; -import {createCallTask, acceptIncomingTask, acceptExtensionCall, endCallTask} from '../Utils/incomingTaskUtils'; +import {createCallTask, acceptIncomingTask} from '../Utils/incomingTaskUtils'; import {submitWrapup} from '../Utils/wrapupUtils'; import {USER_STATES, TASK_TYPES, WRAPUP_REASONS} from '../constants'; -import {waitForState, clearPendingCallAndWrapup} from '../Utils/helperUtils'; +import {waitForState, handleStrayTasks, createLogger} from '../Utils/helperUtils'; import {endTask, holdCallToggle} from '../Utils/taskControlUtils'; import {TestManager} from '../test-manager'; +const log = createLogger('AdvCombinations'); + export default function createAdvanceCombinationsTests() { test.describe('Advanced Combinations Tests ', () => { let testManager: TestManager; test.beforeAll(async ({browser}, testInfo) => { + log('beforeAll: Setting up test manager'); const projectName = testInfo.project.name; testManager = new TestManager(projectName); await testManager.setupForAdvancedCombinations(browser); + log('beforeAll: Setup complete'); + }); + + test.beforeEach(async () => { + log('beforeEach: Handling stray tasks'); + await handleStrayTasks(testManager.agent1Page); + await handleStrayTasks(testManager.agent2Page); }); test('Transfer from one agent to another, then transfer back to the first agent', async () => { + log('Test: Transfer A1→A2→A1 - Starting'); await changeUserState(testManager.agent2Page, USER_STATES.MEETING); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); - let incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); + log('Creating call and A1 accepting'); await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); await changeUserState(testManager.agent2Page, USER_STATES.AVAILABLE); await testManager.agent1Page.waitForTimeout(2000); await waitForState(testManager.agent2Page, USER_STATES.AVAILABLE); + log('A1 engaged, transferring to A2'); await consultOrTransfer( testManager.agent1Page, 'agent', 'transfer', process.env[`${testManager.projectName}_AGENT2_NAME`]! ); - incomingTaskDiv = testManager.agent2Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 20000}); await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL); await waitForState(testManager.agent2Page, USER_STATES.ENGAGED); await testManager.agent1Page.waitForTimeout(2000); await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); await testManager.agent1Page.waitForTimeout(2000); + log('A2 engaged, transferring back to A1'); await consultOrTransfer( testManager.agent2Page, 'agent', 'transfer', process.env[`${testManager.projectName}_AGENT1_NAME`]! ); - incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 20000}); await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); await testManager.agent1Page.waitForTimeout(2000); await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); await testManager.agent1Page.waitForTimeout(2000); await submitWrapup(testManager.agent2Page, WRAPUP_REASONS.SALE); + log('A1 re-engaged, ending call'); await testManager.agent1Page.getByTestId('call-control:end-call').first().click(); await testManager.agent1Page.waitForTimeout(3000); await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); + log('Test: Transfer A1→A2→A1 - Complete'); }); test('Consult with another agent then transfer the call', async () => { + log('Test: Consult then transfer - Starting'); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await changeUserState(testManager.agent2Page, USER_STATES.MEETING); await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); - let incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); await changeUserState(testManager.agent2Page, USER_STATES.AVAILABLE); await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); @@ -86,8 +93,7 @@ export default function createAdvanceCombinationsTests() { 'consult', process.env[`${testManager.projectName}_AGENT2_NAME`]! ); - incomingTaskDiv = testManager.agent2Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 20000}); + log('Consult started, transferring'); await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL); await waitForState(testManager.agent2Page, USER_STATES.ENGAGED); await testManager.agent1Page.getByTestId('transfer-consult-btn').click(); @@ -100,10 +106,6 @@ export default function createAdvanceCombinationsTests() { 'consult', process.env[`${testManager.projectName}_AGENT1_NAME`]! ); - await testManager.agent1Page - .getByTestId('samples:incoming-task-telephony') - .first() - .waitFor({state: 'visible', timeout: 20000}); await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); await testManager.agent2Page.getByTestId('transfer-consult-btn').click(); @@ -114,14 +116,14 @@ export default function createAdvanceCombinationsTests() { await testManager.agent1Page.waitForTimeout(2000); await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); await testManager.agent1Page.waitForTimeout(2000); + log('Test: Consult then transfer - Complete'); }); test('Consult with another agent, transfer the call and transfer the call back to the agent', async () => { + log('Test: Consult, transfer, transfer back - Starting'); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await changeUserState(testManager.agent2Page, USER_STATES.MEETING); await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); - let incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); await changeUserState(testManager.agent2Page, USER_STATES.AVAILABLE); await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); @@ -132,8 +134,6 @@ export default function createAdvanceCombinationsTests() { 'consult', process.env[`${testManager.projectName}_AGENT2_NAME`]! ); - incomingTaskDiv = testManager.agent2Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 20000}); await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL); await waitForState(testManager.agent2Page, USER_STATES.ENGAGED); await testManager.agent1Page.getByTestId('transfer-consult-btn').click(); @@ -147,11 +147,6 @@ export default function createAdvanceCombinationsTests() { 'transfer', process.env[`${testManager.projectName}_AGENT1_NAME`]! ); - incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({ - state: 'visible', - timeout: 20000, - }); await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); await testManager.agent2Page.waitForTimeout(2000); @@ -162,14 +157,14 @@ export default function createAdvanceCombinationsTests() { await testManager.agent1Page.waitForTimeout(2000); await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); await testManager.agent1Page.waitForTimeout(2000); + log('Test: Consult, transfer, transfer back - Complete'); }); test('Transfer the call to another agent & then consult from the other agent', async () => { + log('Test: Transfer then consult - Starting'); await changeUserState(testManager.agent2Page, USER_STATES.MEETING); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); - let incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); await changeUserState(testManager.agent2Page, USER_STATES.AVAILABLE); @@ -180,10 +175,6 @@ export default function createAdvanceCombinationsTests() { 'transfer', process.env[`${testManager.projectName}_AGENT2_NAME`]! ); - await testManager.agent2Page - .getByTestId('samples:incoming-task-telephony') - .first() - .waitFor({state: 'visible', timeout: 20000}); await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL); await waitForState(testManager.agent2Page, USER_STATES.ENGAGED); await testManager.agent1Page.waitForTimeout(2000); @@ -195,11 +186,6 @@ export default function createAdvanceCombinationsTests() { 'consult', process.env[`${testManager.projectName}_AGENT1_NAME`]! ); - incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({ - state: 'visible', - timeout: 20000, - }); await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); await testManager.agent2Page.getByTestId('transfer-consult-btn').click(); @@ -210,15 +196,15 @@ export default function createAdvanceCombinationsTests() { await testManager.agent1Page.waitForTimeout(2000); await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); await testManager.agent1Page.waitForTimeout(2000); + log('Test: Transfer then consult - Complete'); }); test('Multi-Stage Consult and Transfer Between A1 and A2', async () => { + log('Test: Multi-stage consult/transfer - Starting'); await changeUserState(testManager.agent2Page, USER_STATES.MEETING); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); await testManager.agent1Page.waitForTimeout(5000); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 80000}); await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); await changeUserState(testManager.agent2Page, USER_STATES.AVAILABLE); await testManager.agent1Page.waitForTimeout(5000); @@ -229,8 +215,6 @@ export default function createAdvanceCombinationsTests() { 'consult', process.env[`${testManager.projectName}_AGENT2_NAME`]! ); - const consultRequestDiv = testManager.agent2Page.getByTestId('samples:incoming-task-telephony').first(); - await consultRequestDiv.waitFor({state: 'visible', timeout: 60000}); await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL); await testManager.agent2Page.waitForTimeout(3000); await verifyCurrentState(testManager.agent2Page, USER_STATES.ENGAGED); @@ -245,8 +229,6 @@ export default function createAdvanceCombinationsTests() { 'consult', process.env[`${testManager.projectName}_AGENT1_NAME`]! ); - const returnConsultDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await returnConsultDiv.waitFor({state: 'visible', timeout: 60000}); await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); await testManager.agent1Page.waitForTimeout(3000); await testManager.agent2Page.getByTestId('transfer-consult-btn').click(); @@ -262,124 +244,36 @@ export default function createAdvanceCombinationsTests() { await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).toBeVisible(); await expect(testManager.agent1Page.getByTestId('transfer-consult-btn')).toBeVisible(); await cancelConsult(testManager.agent1Page); - await expect(testManager.agent1Page.getByRole('group', {name: 'Call Control with Call'})).toBeVisible(); + await expect(testManager.agent1Page.getByTestId('call-control:consult').first()).toBeVisible(); await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); await holdCallToggle(testManager.agent1Page); await endTask(testManager.agent1Page); await testManager.agent1Page.waitForTimeout(3000); await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.RESOLVED); await testManager.agent1Page.waitForTimeout(2000); - }); - - test('Dial Number: consult then end consult returns UI to normal', async () => { - test.skip(!process.env.PW_DIAL_NUMBER_NAME, 'PW_DIAL_NUMBER_NAME not set'); - - await testManager.resetDialNumberSession(); - await changeUserState(testManager.agent2Page, USER_STATES.MEETING); - await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 80000}); - await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); - await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); - - clearAdvancedCapturedLogs(); - await consultOrTransfer(testManager.agent1Page, 'dialNumber', 'consult', process.env.PW_DIAL_NUMBER_NAME!); - await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).toBeVisible(); - await cancelConsult(testManager.agent1Page); - await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).not.toBeVisible(); - await endCallTask(testManager.callerPage!); - await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); - }); - - test('Dial Number: consult then transfer completes and remote ends', async () => { - test.skip(!process.env.PW_DIAL_NUMBER_NAME, 'PW_DIAL_NUMBER_NAME not set'); - - await testManager.resetDialNumberSession(); - await changeUserState(testManager.agent2Page, USER_STATES.MEETING); - await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 80000}); - await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); - - clearAdvancedCapturedLogs(); - await consultOrTransfer(testManager.agent1Page, 'dialNumber', 'consult', process.env.PW_DIAL_NUMBER_NAME!); - await ensureDialNumberLoggedIn(testManager.dialNumberPage); - await expect(testManager.dialNumberPage.locator('[data-test="right-action-button"]')).toBeVisible(); - await acceptExtensionCall(testManager.dialNumberPage); - await testManager.agent1Page.getByTestId('transfer-consult-btn').click(); - await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); - await verifyConsultStartSuccessLogs(); - await verifyConsultTransferredLogs(); - await endCallTask(testManager.dialNumberPage); + log('Test: Multi-stage consult/transfer - Complete'); }); test('Entry Point: consult then end consult returns UI to normal', async () => { test.skip(!process.env.PW_ENTRYPOINT_NAME, 'PW_ENTRYPOINT_NAME not set'); + log('Test: Entry Point consult cancel - Starting'); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 80000}); await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); - clearAdvancedCapturedLogs(); await consultOrTransfer(testManager.agent1Page, 'entryPoint', 'consult', process.env.PW_ENTRYPOINT_NAME!); await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).toBeVisible(); await verifyConsultStartSuccessLogs(); await cancelConsult(testManager.agent1Page); await testManager.agent1Page.waitForTimeout(1000); - }); - - // Two-hop to Dial Number (moved into top-level describe) - test('Two-hop: consult to Agent then consult-transfer to Dial Number', async () => { - test.skip(!process.env.PW_DIAL_NUMBER_NAME, 'PW_DIAL_NUMBER_NAME not set'); - - await clearPendingCallAndWrapup(testManager.agent1Page); - await clearPendingCallAndWrapup(testManager.agent2Page); - await testManager.resetDialNumberSession(); - await changeUserState(testManager.agent2Page, USER_STATES.MEETING); - await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 80000}); - await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); - await changeUserState(testManager.agent2Page, USER_STATES.AVAILABLE); - await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); - await waitForState(testManager.agent2Page, USER_STATES.AVAILABLE); - await testManager.agent2Page.waitForTimeout(2000); - await testManager.agent1Page.waitForTimeout(2000); - - clearAdvancedCapturedLogs(); - await consultOrTransfer( - testManager.agent1Page, - 'agent', - 'consult', - process.env[`${testManager.projectName}_AGENT2_NAME`]! - ); - const consultRequestDiv = testManager.agent2Page.getByTestId('samples:incoming-task-telephony').first(); - await consultRequestDiv.waitFor({state: 'visible', timeout: 60000}); - await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL); - await testManager.agent2Page.waitForTimeout(3000); - await verifyCurrentState(testManager.agent2Page, USER_STATES.ENGAGED); - await testManager.agent1Page.getByTestId('transfer-consult-btn').click(); - await testManager.agent1Page.waitForTimeout(2000); - await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); - await testManager.agent2Page.waitForTimeout(3000); - await verifyCurrentState(testManager.agent2Page, USER_STATES.ENGAGED); - - await consultOrTransfer(testManager.agent2Page, 'dialNumber', 'consult', process.env.PW_DIAL_NUMBER_NAME!); - await ensureDialNumberLoggedIn(testManager.dialNumberPage); - await acceptExtensionCall(testManager.dialNumberPage); - await testManager.agent2Page.getByTestId('transfer-consult-btn').click(); - await submitWrapup(testManager.agent2Page, WRAPUP_REASONS.SALE); - await verifyConsultTransferredLogs(); - await endCallTask(testManager.dialNumberPage); + log('Test: Entry Point consult cancel - Complete'); }); test.afterAll(async () => { + log('afterAll: Cleanup starting'); await testManager.cleanup(); + log('afterAll: Cleanup complete'); }); }); } diff --git a/playwright/tests/advanced-task-controls-test.spec.ts b/playwright/tests/advanced-task-controls-test.spec.ts index 4b554e8bc..b5f43c12b 100644 --- a/playwright/tests/advanced-task-controls-test.spec.ts +++ b/playwright/tests/advanced-task-controls-test.spec.ts @@ -16,17 +16,19 @@ import { acceptIncomingTask, declineIncomingTask, acceptExtensionCall, - endCallTask, - declineExtensionCall, + waitForIncomingTask, } from '../Utils/incomingTaskUtils'; import {submitWrapup} from '../Utils/wrapupUtils'; import {USER_STATES, TASK_TYPES, WRAPUP_REASONS} from '../constants'; import {holdCallToggle, endTask, verifyHoldButtonIcon, verifyTaskControls} from '../Utils/taskControlUtils'; import {TestManager} from '../test-manager'; +import {handleStrayTasks, clearPendingCallAndWrapup, waitForState, createLogger} from '../Utils/helperUtils'; // Extract test functions for cleaner syntax const {describe, beforeAll, afterAll, beforeEach} = test; +const log = createLogger('AdvTaskControls'); + /** * Transfer and Consult Tests * @@ -40,14 +42,26 @@ const {describe, beforeAll, afterAll, beforeEach} = test; export default function createAdvancedTaskControlsTests() { let testManager: TestManager; - beforeAll(async ({browser}, testInfo) => { + test.beforeAll(async ({browser}, testInfo) => { + log('beforeAll: Setting up test manager'); const projectName = testInfo.project.name; testManager = new TestManager(projectName); await testManager.setupForAdvancedTaskControls(browser); + log('beforeAll: Setup complete'); + }); + + test.afterAll(async () => { + log('afterAll: Cleanup starting'); + if (testManager) { + await testManager.cleanup(); + } + log('afterAll: Cleanup complete'); }); - afterAll(async () => { - await testManager.cleanup(); + test.beforeEach(async () => { + log('beforeEach: Handling stray tasks'); + await handleStrayTasks(testManager.agent1Page, testManager.callerPage); + await handleStrayTasks(testManager.agent2Page, testManager.callerPage); }); // ============================================================================= @@ -56,25 +70,26 @@ export default function createAdvancedTaskControlsTests() { describe('Blind Transfer Tests', () => { beforeEach(async () => { + log('BlindTransfer beforeEach: Creating call and A1 accepting'); await changeUserState(testManager.agent2Page, USER_STATES.MEETING); // Create call task and agent 1 accepts it await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 80000}); - + await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 80000); await acceptExtensionCall(testManager.agent1ExtensionPage); await changeUserState(testManager.agent2Page, USER_STATES.AVAILABLE); await testManager.agent1Page.waitForTimeout(5000); await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); + log('BlindTransfer beforeEach: A1 engaged'); // Clear console logs to track transfer events clearAdvancedCapturedLogs(); }); test('Call Blind Transferred by Agent to Another Agent', async () => { + log('Test: Blind Transfer A1→A2 - Starting'); // Agent 1 performs blind transfer to Agent 2 await consultOrTransfer( testManager.agent1Page, @@ -82,19 +97,15 @@ export default function createAdvancedTaskControlsTests() { 'transfer', process.env[`${testManager.projectName}_AGENT2_NAME`]! ); - + // Agent 2 should receive the transfer and accept it + await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL, 60000); + await testManager.agent2Page.waitForTimeout(3000); // Verify transfer success in console logs - await testManager.agent1Page.waitForTimeout(3000); + await testManager.agent1Page.bringToFront(); verifyTransferSuccessLogs(); // Verify Agent 1 goes to wrapup state await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); - // Agent 2 should receive the transfer and accept it - const incomingTransferDiv = testManager.agent2Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTransferDiv.waitFor({state: 'visible', timeout: 60000}); - - await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL); - await testManager.agent2Page.waitForTimeout(3000); // Verify Agent 2 now has the call and is engaged await verifyCurrentState(testManager.agent2Page, USER_STATES.ENGAGED); @@ -108,10 +119,12 @@ export default function createAdvancedTaskControlsTests() { await endTask(testManager.agent2Page); await testManager.agent2Page.waitForTimeout(3000); await submitWrapup(testManager.agent2Page, WRAPUP_REASONS.RESOLVED); + log('Test: Blind Transfer A1→A2 - Complete'); await testManager.agent2Page.waitForTimeout(2000); }); test('Call Blind Transferred to Queue', async () => { + log('Test: Blind Transfer to Queue - Starting'); // First transfer from Agent 1 to Agent 2 await consultOrTransfer( testManager.agent1Page, @@ -121,11 +134,9 @@ export default function createAdvancedTaskControlsTests() { ); // Agent 2 accepts the transfer - const incomingTransferDiv = testManager.agent2Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTransferDiv.waitFor({state: 'visible', timeout: 60000}); + await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL, 60000); await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); - await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL); - await testManager.agent2Page.waitForTimeout(3000); + await testManager.agent1Page.waitForTimeout(3000); verifyTransferSuccessLogs(); await verifyCurrentState(testManager.agent2Page, USER_STATES.ENGAGED); await endTask(testManager.agent2Page); @@ -137,43 +148,7 @@ export default function createAdvancedTaskControlsTests() { // Verify Agent 2 is no longer engaged await verifyCurrentState(testManager.agent2Page, USER_STATES.AVAILABLE); - }); - - test('Call Blind Transferred to DialNumber', async () => { - await testManager.resetDialNumberSession(); - await consultOrTransfer(testManager.agent1Page, 'dialNumber', 'transfer', process.env.PW_DIAL_NUMBER_NAME); - - //DialNumber accepts the transfer - await ensureDialNumberLoggedIn(testManager.dialNumberPage); - await acceptExtensionCall(testManager.dialNumberPage); - verifyTransferSuccessLogs(); - await endCallTask(testManager.callerPage!); - - // Verify Agent 1 goes to wrapup after transfer - await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.RESOLVED); - await testManager.agent1Page.waitForTimeout(2000); - - // Verify Agent 1 is no longer engaged - await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); - }); - - test('Call Blind Transferred to Queue with DialNumber', async () => { - await testManager.resetDialNumberSession(); - // First transfer from Agent 1 to Agent 2 - await consultOrTransfer(testManager.agent1Page, 'queue', 'transfer', 'queue with dn'); - - //DialNumber accepts the transfer - await ensureDialNumberLoggedIn(testManager.dialNumberPage); - await acceptExtensionCall(testManager.dialNumberPage); - verifyTransferSuccessLogs(); - await endCallTask(testManager.callerPage!); - - // Verify Agent 1 goes to wrapup after transfer - await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.RESOLVED); - await testManager.agent1Page.waitForTimeout(2000); - - // Verify Agent 1 is no longer engaged - await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); + log('Test: Blind Transfer to Queue - Complete'); }); }); @@ -182,40 +157,19 @@ export default function createAdvancedTaskControlsTests() { // ============================================================================= describe('Consult and Consult Transfer Scenarios', () => { - test('Entry Point Consult: visible and functional only for supported users (no blind transfer)', async ({}, testInfo) => { - await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 80000}); - await acceptExtensionCall(testManager.agent1ExtensionPage); - await testManager.agent1Page.waitForTimeout(3000); - await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); - - // Ensure consult UI and Entry Point tab exists - await testManager.agent1Page.getByTestId('call-control:consult').nth(1).click(); - await expect(testManager.agent1Page.locator('#consult-search')).toBeVisible(); - await testManager.agent1Page.getByRole('button', {name: 'Entry Point'}).click(); - - await consultOrTransfer(testManager.agent1Page, 'entryPoint', 'consult', process.env.PW_ENTRYPOINT_NAME!); - await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).toBeVisible(); - await testManager.agent1Page.waitForTimeout(1000); - await cancelConsult(testManager.agent1Page); - await testManager.agent1Page.waitForTimeout(1000); - await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); - }); test('Agent Consult Transfer: cancel, decline, timeout, and transfer scenarios are handled correctly in sequence', async () => { + log('Test: Agent Consult scenarios - Starting'); // ...existing code for Agent Consult Transfer test... await changeUserState(testManager.agent2Page, USER_STATES.MEETING); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 80000}); + await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 80000); await testManager.agent1ExtensionPage .locator('[data-test="generic-person-item-base"]') .waitFor({state: 'visible', timeout: 20000}); await acceptExtensionCall(testManager.agent1ExtensionPage); await changeUserState(testManager.agent2Page, USER_STATES.AVAILABLE); - await testManager.agent1Page.waitForTimeout(5000); + await testManager.agent1Page.waitForTimeout(3000); await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); // 1. Accept consult and end @@ -228,9 +182,7 @@ export default function createAdvancedTaskControlsTests() { ); await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).toBeVisible(); await expect(testManager.agent1Page.getByTestId('transfer-consult-btn')).toBeVisible(); - const consultRequestDiv1 = testManager.agent2Page.getByTestId('samples:incoming-task-telephony').first(); - await consultRequestDiv1.waitFor({state: 'visible', timeout: 60000}); - await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL); + await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL, 60000); await testManager.agent2Page.waitForTimeout(3000); await expect(testManager.agent1Page.getByTestId('transfer-consult-btn')).toBeVisible(); await testManager.agent1Page.waitForTimeout(2000); @@ -250,9 +202,7 @@ export default function createAdvancedTaskControlsTests() { 'consult', process.env[`${testManager.projectName}_AGENT2_NAME`]! ); - const consultRequestDiv2 = testManager.agent2Page.getByTestId('samples:incoming-task-telephony').first(); - await consultRequestDiv2.waitFor({state: 'visible', timeout: 60000}); - await testManager.agent2Page.waitForTimeout(3000); + await waitForIncomingTask(testManager.agent2Page, TASK_TYPES.CALL, 60000); await declineIncomingTask(testManager.agent2Page, TASK_TYPES.CALL); await verifyTaskControls(testManager.agent1Page, TASK_TYPES.CALL); await verifyHoldButtonIcon(testManager.agent1Page, {expectedIsHeld: true}); @@ -270,7 +220,7 @@ export default function createAdvancedTaskControlsTests() { 'consult', process.env[`${testManager.projectName}_AGENT2_NAME`]! ); - await testManager.agent1Page.waitForTimeout(20000); // Wait for timeout + await testManager.agent1Page.waitForTimeout(10000); await verifyTaskControls(testManager.agent1Page, TASK_TYPES.CALL); await verifyHoldButtonIcon(testManager.agent1Page, {expectedIsHeld: true}); await holdCallToggle(testManager.agent1Page); @@ -285,10 +235,9 @@ export default function createAdvancedTaskControlsTests() { 'consult', process.env[`${testManager.projectName}_AGENT2_NAME`]! ); - const consultRequestDiv3 = testManager.agent2Page.getByTestId('samples:incoming-task-telephony').first(); - await consultRequestDiv3.waitFor({state: 'visible', timeout: 60000}); - await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL); + await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL, 60000); await testManager.agent2Page.waitForTimeout(3000); + await testManager.agent1Page.bringToFront(); await testManager.agent1Page.getByTestId('transfer-consult-btn').click(); await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); await verifyCurrentState(testManager.agent2Page, USER_STATES.ENGAGED); @@ -299,25 +248,24 @@ export default function createAdvancedTaskControlsTests() { await endTask(testManager.agent2Page); await testManager.agent2Page.waitForTimeout(3000); await submitWrapup(testManager.agent2Page, WRAPUP_REASONS.RESOLVED); + log('Test: Agent Consult scenarios - Complete'); await testManager.agent2Page.waitForTimeout(2000); }); test('Queue Consult: cancel, accept/end, agent-end, and transfer scenarios are handled correctly in sequence', async () => { + log('Test: Queue Consult scenarios - Starting'); // ...existing code for Queue Consult test... await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); // Setup: create call and get to engaged state await changeUserState(testManager.agent2Page, USER_STATES.MEETING); await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 80000}); - await testManager.agent1ExtensionPage - .locator('[data-test="generic-person-item-base"]') - .waitFor({state: 'visible', timeout: 20000}); + await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 80000); await acceptExtensionCall(testManager.agent1ExtensionPage); await testManager.agent1Page.waitForTimeout(5000); await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); - + await changeUserState(testManager.agent2Page, USER_STATES.AVAILABLE); + await testManager.agent2Page.waitForTimeout(2000); // 1. Cancel consult clearAdvancedCapturedLogs(); await consultOrTransfer( @@ -332,8 +280,6 @@ export default function createAdvancedTaskControlsTests() { await verifyTaskControls(testManager.agent1Page, TASK_TYPES.CALL); await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).not.toBeVisible(); - // 2. Accept consult and end (Agent 2 accepts, Agent 1 ends) - await changeUserState(testManager.agent2Page, USER_STATES.AVAILABLE); clearAdvancedCapturedLogs(); await consultOrTransfer( testManager.agent1Page, @@ -343,10 +289,7 @@ export default function createAdvancedTaskControlsTests() { ); await testManager.agent1Page.waitForTimeout(3000); verifyConsultStartSuccessLogs(); - const consultRequestDiv1 = testManager.agent2Page.getByTestId('samples:incoming-task-telephony').first(); - await consultRequestDiv1.waitFor({state: 'visible', timeout: 60000}); - await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL); - await testManager.agent2Page.waitForTimeout(3000); + await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL, 60000); await cancelConsult(testManager.agent1Page); await testManager.agent1Page.waitForTimeout(3000); await verifyCurrentState(testManager.agent2Page, USER_STATES.AVAILABLE); @@ -365,9 +308,7 @@ export default function createAdvancedTaskControlsTests() { 'consult', process.env[`${testManager.projectName}_QUEUE_NAME`]! ); - const consultRequestDiv2 = testManager.agent2Page.getByTestId('samples:incoming-task-telephony').first(); - await consultRequestDiv2.waitFor({state: 'visible', timeout: 60000}); - await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL); + await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL, 60000); await testManager.agent2Page.waitForTimeout(3000); await cancelConsult(testManager.agent2Page); await testManager.agent2Page.waitForTimeout(3000); @@ -386,12 +327,9 @@ export default function createAdvancedTaskControlsTests() { process.env[`${testManager.projectName}_QUEUE_NAME`]! ); await testManager.agent1Page.waitForTimeout(2000); - const consultRequestDiv3 = testManager.agent2Page.getByTestId('samples:incoming-task-telephony').first(); - await consultRequestDiv3.waitFor({state: 'visible', timeout: 60000}); - await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL); - await testManager.agent2Page.waitForTimeout(3000); + await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL, 60000); await testManager.agent1Page.getByTestId('transfer-consult-btn').click(); - await testManager.agent1Page.waitForTimeout(2000); + await testManager.agent1Page.bringToFront(); await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); await verifyCurrentState(testManager.agent2Page, USER_STATES.ENGAGED); await verifyTaskControls(testManager.agent2Page, TASK_TYPES.CALL); @@ -401,119 +339,37 @@ export default function createAdvancedTaskControlsTests() { await endTask(testManager.agent2Page); await testManager.agent2Page.waitForTimeout(3000); await submitWrapup(testManager.agent2Page, WRAPUP_REASONS.RESOLVED); + log('Test: Queue Consult scenarios - Complete'); await testManager.agent2Page.waitForTimeout(2000); }); + }); - test('Dial Number Consult: cancel, decline, accept/end, and transfer scenarios are handled correctly in sequence', async () => { - await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - - // Setup: create call and get to engaged state - await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 80000}); - await testManager.agent1ExtensionPage - .locator('[data-test="generic-person-item-base"]') - .waitFor({state: 'visible', timeout: 20000}); - await acceptExtensionCall(testManager.agent1ExtensionPage); - await testManager.agent1Page.waitForTimeout(5000); - await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); - - // 1. Cancel consult - clearAdvancedCapturedLogs(); - await consultOrTransfer(testManager.agent1Page, 'dialNumber', 'consult', process.env.PW_DIAL_NUMBER_NAME); - await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).toBeVisible(); - await testManager.agent1Page.waitForTimeout(2000); - await cancelConsult(testManager.agent1Page); - await verifyTaskControls(testManager.agent1Page, TASK_TYPES.CALL); - await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).not.toBeVisible(); - - // 2. Decline consult - clearAdvancedCapturedLogs(); - await consultOrTransfer(testManager.agent1Page, 'dialNumber', 'consult', process.env.PW_DIAL_NUMBER_NAME); - await declineExtensionCall(testManager.dialNumberPage); - 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 holdCallToggle(testManager.agent1Page); - await testManager.agent1Page.waitForTimeout(2000); - await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).not.toBeVisible(); - await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); - - // 3. Accept consult and end - clearAdvancedCapturedLogs(); - await consultOrTransfer(testManager.agent1Page, 'dialNumber', 'consult', process.env.PW_DIAL_NUMBER_NAME); - await testManager.agent1Page.waitForTimeout(2000); - verifyConsultStartSuccessLogs(); - await acceptExtensionCall(testManager.dialNumberPage); - await testManager.agent1Page.waitForTimeout(2000); - await cancelConsult(testManager.agent1Page); - await verifyTaskControls(testManager.agent1Page, TASK_TYPES.CALL); - await testManager.agent1Page.waitForTimeout(2000); - verifyConsultEndSuccessLogs(); - await verifyHoldButtonIcon(testManager.agent1Page, {expectedIsHeld: true}); - await holdCallToggle(testManager.agent1Page); - - // 4. Consult transfer - clearAdvancedCapturedLogs(); - await consultOrTransfer(testManager.agent1Page, 'dialNumber', 'consult', process.env.PW_DIAL_NUMBER_NAME); - await acceptExtensionCall(testManager.dialNumberPage); - await testManager.agent1Page.waitForTimeout(3000); - await testManager.agent1Page.getByTestId('transfer-consult-btn').click(); - await testManager.agent1Page.waitForTimeout(2000); - await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); - await testManager.dialNumberPage.waitForTimeout(2000); - verifyConsultStartSuccessLogs(); - verifyConsultTransferredLogs(); - await endCallTask(testManager.dialNumberPage); - }); - - test('Dial Number search filters list to the matching entry (local search)', async () => { - if (testManager.projectName !== 'SET_5') { - test.skip(true, 'Dial Number search validation runs only for SET_5 (user23/user24).'); - } - - const searchTerm = process.env.PW_DIAL_NUMBER_NAME!; - - // Setup: create call and get to engaged state - await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - await changeUserState(testManager.agent2Page, USER_STATES.MEETING); - await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 80000}); - await acceptExtensionCall(testManager.agent1ExtensionPage); - await testManager.agent1Page.waitForTimeout(3000); - await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); + // ============================================================================= + // DIAL NUMBER TESTS - All tests requiring dialNumber session + // ============================================================================= - // Open consult popover and switch to Dial Number - await testManager.agent1Page.getByTestId('call-control:consult').nth(1).click(); - const popover = testManager.agent1Page.locator('.agent-popover-content'); - 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); - await testManager.agent1Page.waitForTimeout(4000); - - // Read visible list item titles (aria-labels) and validate only the searched item remains - const labels = await popover - .locator('[role="listitem"]') - .evaluateAll((nodes) => nodes.map((n) => n.getAttribute('aria-label'))); - expect(labels).toContain(searchTerm); - expect(labels.filter(Boolean).length).toBe(1); - - // Close the popover to avoid overlay blocking further actions - await testManager.agent1Page.keyboard.press('Escape'); - await testManager.agent1Page - .locator('.md-popover-backdrop') - .waitFor({state: 'hidden', timeout: 3000}) - .catch(() => {}); - - // End call and complete wrapup to clean up for next tests - await endTask(testManager.agent1Page); - await testManager.agent1Page.waitForTimeout(2000); - await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); - await testManager.agent1Page.waitForTimeout(1000); - }); + test('Entry Point Consult: visible and functional only for supported users (no blind transfer)', async ({}, testInfo) => { + log('Test: EP Consult - Starting'); + await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); + await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); + await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 80000); + await acceptExtensionCall(testManager.agent1ExtensionPage); + await testManager.agent1Page.waitForTimeout(3000); + await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); + + // Ensure consult UI and Entry Point tab exists + const consultButton = testManager.agent1Page.getByTestId('call-control:consult').first(); + await consultButton.waitFor({state: 'visible', timeout: 10000}); + await consultButton.click(); + await expect(testManager.agent1Page.locator('#consult-search')).toBeVisible(); + await testManager.agent1Page.getByRole('button', {name: 'Entry Point'}).click(); + + await consultOrTransfer(testManager.agent1Page, 'entryPoint', 'consult', process.env.PW_ENTRYPOINT_NAME!); + await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).toBeVisible(); + await testManager.agent1Page.waitForTimeout(1000); + await cancelConsult(testManager.agent1Page); + await testManager.agent1Page.waitForTimeout(1000); + await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); + log('Test: EP Consult - Complete'); }); } diff --git a/playwright/tests/basic-task-controls-test.spec.ts b/playwright/tests/basic-task-controls-test.spec.ts index feb27b591..81430abda 100644 --- a/playwright/tests/basic-task-controls-test.spec.ts +++ b/playwright/tests/basic-task-controls-test.spec.ts @@ -19,10 +19,13 @@ import { import {submitWrapup} from '../Utils/wrapupUtils'; import {USER_STATES, TASK_TYPES, WRAPUP_REASONS} from '../constants'; import {TestManager} from '../test-manager'; +import {createLogger} from '../Utils/helperUtils'; // Extract test functions for cleaner syntax const {describe, beforeAll, afterAll, beforeEach} = test; +const log = createLogger('BasicTaskControls'); + export default function createCallTaskControlsTests() { let testManager: TestManager; @@ -31,12 +34,15 @@ export default function createCallTaskControlsTests() { }); beforeAll(async ({browser}, testInfo) => { + log('beforeAll: Setting up test manager'); const projectName = testInfo.project.name; testManager = new TestManager(projectName); await testManager.setupForIncomingTaskDesktop(browser); + log('beforeAll: Setup complete'); }); afterAll(async () => { + log('afterAll: Cleanup starting'); if ((await getCurrentState(testManager.agent1Page)) === USER_STATES.ENGAGED) { // If still engaged, end the call to clean up await endTask(testManager.agent1Page); @@ -47,19 +53,17 @@ export default function createCallTaskControlsTests() { if (testManager) { await testManager.cleanup(); } + log('afterAll: Cleanup complete'); }); test('Call task - create call and verify all control buttons are visible', async () => { + log('Test: Create call and verify controls - Starting'); // Create call task await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - // Wait for incoming call notification - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 80000}); - - // Accept the incoming call - await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); + // Accept the incoming call (waits for task to be visible) + await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 80000); await testManager.agent1Page.waitForTimeout(5000); // Verify agent state changed to engaged @@ -68,24 +72,28 @@ export default function createCallTaskControlsTests() { // Use utility to check all call control buttons are visible try { await verifyTaskControls(testManager.agent1Page, TASK_TYPES.CALL); + log('Test: Create call and verify controls - Complete'); } catch (error) { throw new Error(`Call control buttons verification failed: ${error.message}`); } }); test('Call task - verify remote audio tracks from caller to browser', async () => { + log('Test: Verify remote audio tracks - Starting'); // Verify we're still in an engaged call from previous test await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); try { // Then verify the audio tracks with the exact structure you provided await verifyRemoteAudioTracks(testManager.agent1Page); + log('Test: Verify remote audio tracks - Complete'); } catch (error) { throw new Error(`Remote audio tracks verification failed: ${error.message}`); } }); test('Call task - verify hold and resume functionality with callbacks, timer, and hold music', async () => { + log('Test: Verify hold/resume - Starting'); // Verify we're still in an engaged call from previous test await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); @@ -126,6 +134,7 @@ export default function createCallTaskControlsTests() { await verifyHoldButtonIcon(testManager.agent1Page, {expectedIsHeld: false}); verifyHoldTimer(testManager.agent1Page, {shouldBeVisible: false}); + log('Test: Verify hold/resume - Complete'); } catch (error) { throw new Error( `Hold/Resume functionality with callbacks, timer, and hold music verification failed: ${error.message}` @@ -134,6 +143,7 @@ export default function createCallTaskControlsTests() { }); test('Call task - verify recording pause and resume functionality with callbacks', async () => { + log('Test: Verify recording pause/resume - Starting'); // Verify we're still in an engaged call from previous tests await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); @@ -162,12 +172,14 @@ export default function createCallTaskControlsTests() { // Verify record button icon changed back to pause icon (when recording is active) await verifyRecordButtonIcon(testManager.agent1Page, {expectedIsRecording: true}); + log('Test: Verify recording pause/resume - Complete'); } catch (error) { throw new Error(`Recording pause/resume functionality verification failed: ${error.message}`); } }); test('Call task - end call and complete wrapup', async () => { + log('Test: End call and wrapup - Starting'); // Verify we're still in an engaged call from previous tests await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); @@ -182,6 +194,7 @@ export default function createCallTaskControlsTests() { // Submit wrapup await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.RESOLVED); await testManager.agent1Page.waitForTimeout(2000); + log('Test: End call and wrapup - Complete'); } catch (error) { throw new Error(`Call task end and wrapup failed: ${error.message}`); } diff --git a/playwright/tests/dial-number-task-control-test.spec.ts b/playwright/tests/dial-number-task-control-test.spec.ts new file mode 100644 index 000000000..77e06ddc7 --- /dev/null +++ b/playwright/tests/dial-number-task-control-test.spec.ts @@ -0,0 +1,327 @@ +import {test, expect} from '@playwright/test'; +import { + cancelConsult, + consultOrTransfer, + clearAdvancedCapturedLogs, + verifyConsultStartSuccessLogs, + verifyConsultTransferredLogs, + ensureDialNumberLoggedIn, + verifyTransferSuccessLogs, + verifyConsultEndSuccessLogs, +} from '../Utils/advancedTaskControlUtils'; +import {changeUserState, verifyCurrentState} from '../Utils/userStateUtils'; +import { + createCallTask, + acceptIncomingTask, + acceptExtensionCall, + endCallTask, + declineExtensionCall, +} from '../Utils/incomingTaskUtils'; +import {submitWrapup} from '../Utils/wrapupUtils'; +import {USER_STATES, TASK_TYPES, WRAPUP_REASONS} from '../constants'; +import {waitForState, clearPendingCallAndWrapup, handleStrayTasks, createLogger} from '../Utils/helperUtils'; +import {endTask, holdCallToggle, verifyHoldButtonIcon, verifyTaskControls} from '../Utils/taskControlUtils'; +import {TestManager} from '../test-manager'; + +const log = createLogger('AdvCombinations'); + +export default function createDialNumberTaskControlTests() { + test.describe('Dial Number Task Control Tests ', () => { + let testManager: TestManager; + + test.beforeAll(async ({browser}, testInfo) => { + log('beforeAll: Setting up test manager'); + const projectName = testInfo.project.name; + testManager = new TestManager(projectName); + await testManager.setupForDialNumber(browser); + log('beforeAll: Setup complete'); + }); + + test.beforeEach(async () => { + log('beforeEach: Handling stray tasks'); + await handleStrayTasks(testManager.agent1Page); + await handleStrayTasks(testManager.agent2Page); + await testManager.resetSession('dialNumber'); + }); + test.describe('Dial Number Tests', () => { + test.beforeAll(async () => { + log('DialNumber beforeAll: Setting up dialNumber session'); + test.skip(!process.env.PW_DIAL_NUMBER_NAME, 'PW_DIAL_NUMBER_NAME not set'); + log('DialNumber beforeAll: Setup complete'); + }); + + test('Two-hop: consult to Agent then consult-transfer to Dial Number', async () => { + test.skip(!process.env.PW_DIAL_NUMBER_NAME, 'PW_DIAL_NUMBER_NAME not set'); + log('Test: Two-hop consult transfer - Starting'); + + await clearPendingCallAndWrapup(testManager.agent1Page); + await clearPendingCallAndWrapup(testManager.agent2Page); + await changeUserState(testManager.agent2Page, USER_STATES.MEETING); + await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); + await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); + await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); + await changeUserState(testManager.agent2Page, USER_STATES.AVAILABLE); + await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); + await waitForState(testManager.agent2Page, USER_STATES.AVAILABLE); + await testManager.agent2Page.waitForTimeout(2000); + await testManager.agent1Page.waitForTimeout(2000); + + clearAdvancedCapturedLogs(); + await consultOrTransfer( + testManager.agent1Page, + 'agent', + 'consult', + process.env[`${testManager.projectName}_AGENT2_NAME`]! + ); + await acceptIncomingTask(testManager.agent2Page, TASK_TYPES.CALL); + await testManager.agent2Page.waitForTimeout(3000); + await verifyCurrentState(testManager.agent2Page, USER_STATES.ENGAGED); + await testManager.agent1Page.getByTestId('transfer-consult-btn').click(); + await testManager.agent1Page.waitForTimeout(2000); + await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); + await testManager.agent2Page.waitForTimeout(3000); + await verifyCurrentState(testManager.agent2Page, USER_STATES.ENGAGED); + + await consultOrTransfer(testManager.agent2Page, 'dialNumber', 'consult', process.env.PW_DIAL_NUMBER_NAME!); + await acceptExtensionCall(testManager.dialNumberPage); + await testManager.agent2Page.getByTestId('transfer-consult-btn').click(); + await submitWrapup(testManager.agent2Page, WRAPUP_REASONS.SALE); + await verifyConsultTransferredLogs(); + await endCallTask(testManager.dialNumberPage); + log('Test: Two-hop consult transfer - Complete'); + }); + + test('Dial Number Consult: cancel, decline, accept/end, and transfer scenarios are handled correctly in sequence', async () => { + log('Test: DN Consult scenarios - Starting'); + await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); + // Setup: create call and get to engaged state + await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); + await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); + await testManager.agent1Page.waitForTimeout(5000); + await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); + + // 1. Cancel consult + clearAdvancedCapturedLogs(); + await consultOrTransfer(testManager.agent1Page, 'dialNumber', 'consult', process.env.PW_DIAL_NUMBER_NAME); + await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).toBeVisible(); + await testManager.agent1Page.waitForTimeout(2000); + await cancelConsult(testManager.agent1Page); + await verifyTaskControls(testManager.agent1Page, TASK_TYPES.CALL); + await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).not.toBeVisible(); + + // 2. Decline consult + clearAdvancedCapturedLogs(); + await consultOrTransfer(testManager.agent1Page, 'dialNumber', 'consult', process.env.PW_DIAL_NUMBER_NAME); + await declineExtensionCall(testManager.dialNumberPage); + 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 holdCallToggle(testManager.agent1Page); + await testManager.agent1Page.waitForTimeout(2000); + await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).not.toBeVisible(); + await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); + + // 3. Accept consult and end + clearAdvancedCapturedLogs(); + await consultOrTransfer(testManager.agent1Page, 'dialNumber', 'consult', process.env.PW_DIAL_NUMBER_NAME); + await testManager.agent1Page.waitForTimeout(2000); + verifyConsultStartSuccessLogs(); + await acceptExtensionCall(testManager.dialNumberPage); + await testManager.agent1Page.bringToFront(); + await cancelConsult(testManager.agent1Page); + await verifyTaskControls(testManager.agent1Page, TASK_TYPES.CALL); + await testManager.agent1Page.waitForTimeout(2000); + verifyConsultEndSuccessLogs(); + await verifyHoldButtonIcon(testManager.agent1Page, {expectedIsHeld: true}); + await holdCallToggle(testManager.agent1Page); + + // 4. Consult transfer + clearAdvancedCapturedLogs(); + await consultOrTransfer(testManager.agent1Page, 'dialNumber', 'consult', process.env.PW_DIAL_NUMBER_NAME); + await acceptExtensionCall(testManager.dialNumberPage); + await testManager.agent1Page.waitForTimeout(3000); + await testManager.agent1Page.getByTestId('transfer-consult-btn').click(); + await testManager.agent1Page.waitForTimeout(2000); + await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); + await testManager.dialNumberPage.waitForTimeout(2000); + verifyConsultStartSuccessLogs(); + verifyConsultTransferredLogs(); + await endCallTask(testManager.dialNumberPage); + log('Test: DN Consult scenarios - Complete'); + }); + + test('Dial Number search filters list to the matching entry (local search)', async () => { + log('Test: DN search filter - Starting'); + if (testManager.projectName !== 'SET_5') { + test.skip(true, 'Dial Number search validation runs only for SET_5 (user23/user24).'); + } + + const searchTerm = process.env.PW_DIAL_NUMBER_NAME!; + + // Setup: create call and get to engaged state + await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); + await changeUserState(testManager.agent2Page, USER_STATES.MEETING); + await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); + await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); + await testManager.agent1Page.waitForTimeout(3000); + await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); + + // 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.click(); + const popover = testManager.agent1Page.locator('.agent-popover-content'); + 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); + await testManager.agent1Page.waitForTimeout(4000); + + // Read visible list item titles (aria-labels) and validate only the searched item remains + const labels = await popover + .locator('[role="listitem"]') + .evaluateAll((nodes) => nodes.map((n) => n.getAttribute('aria-label'))); + expect(labels).toContain(searchTerm); + expect(labels.filter(Boolean).length).toBe(1); + + // Close the popover to avoid overlay blocking further actions + await testManager.agent1Page.keyboard.press('Escape'); + await testManager.agent1Page + .locator('.md-popover-backdrop') + .waitFor({state: 'hidden', timeout: 3000}) + .catch(() => {}); + + // End call and complete wrapup to clean up for next tests + await endTask(testManager.agent1Page); + await testManager.agent1Page.bringToFront(); + await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); + log('Test: DN search filter - Complete'); + await testManager.agent1Page.waitForTimeout(1000); + }); + + test('Dial Number: consult then end consult returns UI to normal', async () => { + test.skip(!process.env.PW_DIAL_NUMBER_NAME, 'PW_DIAL_NUMBER_NAME not set'); + log('Test: Dial Number consult cancel - Starting'); + + await changeUserState(testManager.agent2Page, USER_STATES.MEETING); + await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); + await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); + await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); + await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); + clearAdvancedCapturedLogs(); + await consultOrTransfer(testManager.agent1Page, 'dialNumber', 'consult', process.env.PW_DIAL_NUMBER_NAME!); + await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).toBeVisible(); + await cancelConsult(testManager.agent1Page); + await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).not.toBeVisible(); + await endCallTask(testManager.callerPage!); + await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); + log('Test: Dial Number consult cancel - Complete'); + }); + + test('Dial Number: consult then transfer completes and remote ends', async () => { + test.skip(!process.env.PW_DIAL_NUMBER_NAME, 'PW_DIAL_NUMBER_NAME not set'); + log('Test: Dial Number consult transfer - Starting'); + + await changeUserState(testManager.agent2Page, USER_STATES.MEETING); + await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); + await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); + await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); + clearAdvancedCapturedLogs(); + await consultOrTransfer(testManager.agent1Page, 'dialNumber', 'consult', process.env.PW_DIAL_NUMBER_NAME!); + await ensureDialNumberLoggedIn(testManager.dialNumberPage); + await expect(testManager.dialNumberPage.locator('[data-test="right-action-button"]')).toBeVisible(); + await acceptExtensionCall(testManager.dialNumberPage); + await testManager.agent1Page.getByTestId('transfer-consult-btn').click(); + await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); + await verifyConsultStartSuccessLogs(); + await verifyConsultTransferredLogs(); + await endCallTask(testManager.dialNumberPage); + log('Test: Dial Number consult transfer - Complete'); + }); + + test.beforeEach(async () => { + log('DialNumber beforeEach: Cleaning up and setting states'); + testManager.softCleanup(); + await changeUserState(testManager.agent2Page, USER_STATES.MEETING); + await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); + }); + + test('Blind Transfer to DialNumber', async () => { + log('Test: Blind Transfer to DN - Starting'); + // Create call and agent 1 accepts + await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); + await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); + await testManager.agent1Page.waitForTimeout(3000); + await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); + clearAdvancedCapturedLogs(); + + await consultOrTransfer(testManager.agent1Page, 'dialNumber', 'transfer', process.env.PW_DIAL_NUMBER_NAME); + await ensureDialNumberLoggedIn(testManager.dialNumberPage); + await acceptExtensionCall(testManager.dialNumberPage); + verifyTransferSuccessLogs(); + await endCallTask(testManager.callerPage!); + await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.RESOLVED); + await testManager.agent1Page.waitForTimeout(2000); + await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); + log('Test: Blind Transfer to DN - Complete'); + }); + + test('Blind Transfer to Queue with DialNumber', async () => { + log('Test: Blind Transfer to Queue+DN - Starting'); + // Create call and agent 1 accepts + await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); + await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); + await testManager.agent1Page.waitForTimeout(3000); + await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); + clearAdvancedCapturedLogs(); + + await consultOrTransfer(testManager.agent1Page, 'queue', 'transfer', 'queue with dn e2e'); + await ensureDialNumberLoggedIn(testManager.dialNumberPage); + await acceptExtensionCall(testManager.dialNumberPage); + verifyTransferSuccessLogs(); + await endCallTask(testManager.callerPage!); + await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.RESOLVED); + await testManager.agent1Page.waitForTimeout(2000); + await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); + log('Test: Blind Transfer to Queue+DN - Complete'); + }); + + test('Consult then end consult returns UI to normal', async () => { + log('Test: DN consult then end - Starting'); + await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); + await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); + await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); + + clearAdvancedCapturedLogs(); + await consultOrTransfer(testManager.agent1Page, 'dialNumber', 'consult', process.env.PW_DIAL_NUMBER_NAME!); + await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).toBeVisible(); + await cancelConsult(testManager.agent1Page); + await expect(testManager.agent1Page.getByTestId('cancel-consult-btn')).not.toBeVisible(); + await endCallTask(testManager.callerPage!); + await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); + log('Test: DN consult then end - Complete'); + }); + + test('Consult then transfer completes and remote ends', async () => { + log('Test: DN consult then transfer - Starting'); + await createCallTask(testManager.callerPage!, process.env[`${testManager.projectName}_ENTRY_POINT`]!); + await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); + + clearAdvancedCapturedLogs(); + await consultOrTransfer(testManager.agent1Page, 'dialNumber', 'consult', process.env.PW_DIAL_NUMBER_NAME!); + await ensureDialNumberLoggedIn(testManager.dialNumberPage); + await expect(testManager.dialNumberPage.locator('[data-test="right-action-button"]')).toBeVisible(); + await acceptExtensionCall(testManager.dialNumberPage); + await testManager.agent1Page.bringToFront(); + await testManager.agent1Page.getByTestId('transfer-consult-btn').click(); + await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); + await verifyConsultStartSuccessLogs(); + await verifyConsultTransferredLogs(); + await endCallTask(testManager.dialNumberPage); + log('Test: DN consult then transfer - Complete'); + }); + }); + }); +} diff --git a/playwright/tests/digital-incoming-task-and-task-controls.spec.ts b/playwright/tests/digital-incoming-task-and-task-controls.spec.ts index 960fb36f1..c7773af93 100644 --- a/playwright/tests/digital-incoming-task-and-task-controls.spec.ts +++ b/playwright/tests/digital-incoming-task-and-task-controls.spec.ts @@ -8,6 +8,7 @@ import { acceptExtensionCall, createEmailTask, submitRonaPopup, + waitForIncomingTask, } from '../Utils/incomingTaskUtils'; import {verifyTaskControls, endTask, verifyEndLogs} from '../Utils/taskControlUtils'; import {TASK_TYPES, USER_STATES, THEME_COLORS, WRAPUP_REASONS, RONA_OPTIONS} from '../constants'; @@ -19,9 +20,12 @@ import { waitForWrapupReasonLogs, getLastWrapupReasonFromLogs, isColorClose, + createLogger, } from '../Utils/helperUtils'; import {TestManager} from '../test-manager'; +const log = createLogger('DigitalTask'); + let capturedLogs: string[] = []; //NOTE : Make Sure to set RONA Timeout to 18 seconds before running this test. @@ -112,17 +116,27 @@ export default function createDigitalIncomingTaskAndTaskControlsTests() { }); test.beforeAll(async ({browser}, testInfo) => { + log('beforeAll: Setting up test manager'); const projectName = testInfo.project.name; testManager = new TestManager(projectName); await testManager.setupForIncomingTaskExtension(browser); setupConsoleLogging(testManager.agent1Page); + log('beforeAll: Setup complete'); + }); + + test.afterAll(async () => { + log('afterAll: Cleanup starting'); + if (testManager) { + await testManager.cleanup(); + } + log('afterAll: Cleanup complete'); }); test('should ignore incoming chat task and wait for RONA popup', async () => { + log('Test: Ignore chat → RONA popup - Starting'); await createChatTask(testManager.chatPage, process.env[`${testManager.projectName}_CHAT_URL`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-chat').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 60000}); + const incomingTaskDiv = await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CHAT, 60000); await incomingTaskDiv.waitFor({state: 'hidden', timeout: 20000}); await expect(incomingTaskDiv).toBeHidden(); await testManager.agent1Page.getByTestId('samples:rona-popup').waitFor({state: 'visible', timeout: 15000}); @@ -136,9 +150,11 @@ export default function createDigitalIncomingTaskAndTaskControlsTests() { expect(isColorClose(userStateElementColor, THEME_COLORS.RONA)).toBe(true); await submitRonaPopup(testManager.agent1Page, RONA_OPTIONS.IDLE); await waitForState(testManager.agent1Page, USER_STATES.MEETING); + log('Test: Ignore chat → RONA popup - Complete'); }); test('should set agent to Available and verify chat task behavior', async () => { + log('Test: Available + chat behavior - Starting'); await testManager.agent1Page.waitForTimeout(2000); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-chat').first(); @@ -162,9 +178,11 @@ export default function createDigitalIncomingTaskAndTaskControlsTests() { await testManager.agent1Page.getByTestId('samples:rona-popup').waitFor({state: 'visible', timeout: 15000}); await submitRonaPopup(testManager.agent1Page, RONA_OPTIONS.IDLE); await waitForState(testManager.agent1Page, USER_STATES.MEETING); + log('Test: Available + chat behavior - Complete'); }); test('should set agent state to busy after ignoring chat task', async () => { + log('Test: Ignore chat → busy - Starting'); await testManager.agent1Page.waitForTimeout(2000); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-chat').first(); @@ -181,9 +199,11 @@ export default function createDigitalIncomingTaskAndTaskControlsTests() { await expect(testManager.agent1Page.getByTestId('samples:rona-popup')).not.toBeVisible(); await testManager.agent1Page.waitForTimeout(3000); await verifyCurrentState(testManager.agent1Page, USER_STATES.MEETING); + log('Test: Ignore chat → busy - Complete'); }); test('should accept incoming chat, end chat and complete wrapup with callback verification', async () => { + log('Test: Accept chat + wrapup - Starting'); await testManager.agent1Page.waitForTimeout(2000); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-chat').first(); @@ -207,9 +227,11 @@ export default function createDigitalIncomingTaskAndTaskControlsTests() { await waitForWrapupReasonLogs(capturedLogs, WRAPUP_REASONS.SALE); expect(await getLastWrapupReasonFromLogs(capturedLogs)).toBe(WRAPUP_REASONS.SALE); expect(await verifyCallbackLogs(capturedLogs, WRAPUP_REASONS.SALE, USER_STATES.AVAILABLE)).toBe(true); + log('Test: Accept chat + wrapup - Complete'); }); test('should handle chat disconnect before agent answers', async () => { + log('Test: Chat disconnect before answer - Starting'); await createChatTask(testManager.chatPage, process.env[`${testManager.projectName}_CHAT_URL`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-chat').first(); @@ -218,9 +240,11 @@ export default function createDigitalIncomingTaskAndTaskControlsTests() { await incomingTaskDiv.waitFor({state: 'hidden', timeout: 30000}); await waitForState(testManager.agent1Page, USER_STATES.AVAILABLE); await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); + log('Test: Chat disconnect before answer - Complete'); }); test('should ignore incoming email task and wait for RONA popup and accept and wrapup', async () => { + log('Test: Ignore email → RONA → accept - Starting'); await createEmailTask(process.env[`${testManager.projectName}_EMAIL_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-email').first(); @@ -250,10 +274,12 @@ export default function createDigitalIncomingTaskAndTaskControlsTests() { await waitForWrapupReasonLogs(capturedLogs, WRAPUP_REASONS.SALE); expect(await getLastWrapupReasonFromLogs(capturedLogs)).toBe(WRAPUP_REASONS.SALE); expect(await verifyCallbackLogs(capturedLogs, WRAPUP_REASONS.SALE, USER_STATES.AVAILABLE)).toBe(true); + log('Test: Ignore email → RONA → accept - Complete'); await testManager.agent1Page.waitForTimeout(2000); }); test('should set agent to Available and verify email task behavior', async () => { + log('Test: Available + email behavior - Starting'); await createEmailTask(process.env[`${testManager.projectName}_EMAIL_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-email').first(); @@ -279,10 +305,12 @@ export default function createDigitalIncomingTaskAndTaskControlsTests() { await testManager.agent1Page.waitForTimeout(1000); await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); await waitForState(testManager.agent1Page, USER_STATES.AVAILABLE); + log('Test: Available + email behavior - Complete'); await testManager.agent1Page.waitForTimeout(2000); }); test('should set agent state to busy after ignoring email task', async () => { + log('Test: Ignore email → busy - Starting'); await createEmailTask(process.env[`${testManager.projectName}_EMAIL_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-email').first(); @@ -298,13 +326,15 @@ export default function createDigitalIncomingTaskAndTaskControlsTests() { await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await incomingTaskDiv.waitFor({state: 'visible', timeout: 10000}); await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.EMAIL); - await testManager.agent1Page.waitForTimeout(1000); + await testManager.agent1Page.waitForTimeout(3000); await testManager.agent1Page.getByTestId('call-control:end-call').first().click({timeout: 5000}); await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); await waitForState(testManager.agent1Page, USER_STATES.AVAILABLE); + log('Test: Ignore email → busy - Complete'); }); test('should handle multiple incoming tasks with callback verifications', async () => { + log('Test: Multiple incoming tasks - Starting'); await changeUserState(testManager.agent1Page, USER_STATES.MEETING); await testManager.agent1Page.waitForTimeout(1000); @@ -380,9 +410,11 @@ export default function createDigitalIncomingTaskAndTaskControlsTests() { ).toBe(true); count--; } + log('Test: Multiple incoming tasks - Complete'); }); test('Chat task - verify transfer and end buttons are visible, end chat, and wrap up', async () => { + log('Test: Chat controls + wrapup - Starting'); // Create chat task await createChatTask(testManager.chatPage, process.env[`${testManager.projectName}_CHAT_URL`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); @@ -412,12 +444,14 @@ export default function createDigitalIncomingTaskAndTaskControlsTests() { // Submit wrapup await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.RESOLVED); await testManager.agent1Page.waitForTimeout(2000); + log('Test: Chat controls + wrapup - Complete'); } catch (error) { throw new Error(`Chat task control test failed: ${error.message}`); } }); test('Email task - verify transfer and end buttons are visible, end email, and wrap up', async () => { + log('Test: Email controls + wrapup - Starting'); // Create email task await createEmailTask(process.env[`${testManager.projectName}_EMAIL_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); @@ -447,6 +481,7 @@ export default function createDigitalIncomingTaskAndTaskControlsTests() { // Submit wrapup await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.RESOLVED); await testManager.agent1Page.waitForTimeout(2000); + log('Test: Email controls + wrapup - Complete'); } catch (error) { throw new Error(`Email task control test failed: ${error.message}`); } diff --git a/playwright/tests/incoming-task-and-controls-multi-session.spec.ts b/playwright/tests/incoming-task-and-controls-multi-session.spec.ts index dfd308e70..bbdedbf21 100644 --- a/playwright/tests/incoming-task-and-controls-multi-session.spec.ts +++ b/playwright/tests/incoming-task-and-controls-multi-session.spec.ts @@ -9,6 +9,7 @@ import { declineExtensionCall, endCallTask, submitRonaPopup, + waitForIncomingTask, } from '../Utils/incomingTaskUtils'; import {changeUserState, verifyCurrentState} from '../Utils/userStateUtils'; import { @@ -20,23 +21,36 @@ import { verifyTaskControls, } from '../Utils/taskControlUtils'; import {RONA_OPTIONS, TASK_TYPES, THEME_COLORS, USER_STATES, WRAPUP_REASONS} from '../constants'; -import {isColorClose, waitForState} from '../Utils/helperUtils'; +import {isColorClose, waitForState, createLogger} from '../Utils/helperUtils'; import {submitWrapup} from '../Utils/wrapupUtils'; +const log = createLogger('MultiSession'); + export default function createIncomingTaskAndControlsMultiSessionTests() { let testManager: TestManager; test.beforeAll(async ({browser}, testInfo) => { + log('beforeAll: Setting up test manager'); const projectName = testInfo.project.name; testManager = new TestManager(projectName); await testManager.setupForIncomingTaskMultiSession(browser); + log('beforeAll: Setup complete'); + }); + + test.afterAll(async () => { + log('afterAll: Cleanup starting'); + if (testManager) { + await testManager.cleanup(); + } + log('afterAll: Cleanup complete'); }); test('should handle multi-session incoming call with state synchronization', async () => { + log('Test: Multi-session call sync - Starting'); await createCallTask(testManager.callerPage, process.env[`${testManager.projectName}_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - const incomingTaskDiv2 = testManager.multiSessionAgent1Page.getByTestId('samples:incoming-task-telephony').first(); + let incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); + let incomingTaskDiv2 = testManager.multiSessionAgent1Page.getByTestId('samples:incoming-task-telephony').first(); await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); await testManager.agent1ExtensionPage.waitForTimeout(2000); await testManager.agent1ExtensionPage @@ -64,12 +78,14 @@ export default function createIncomingTaskAndControlsMultiSessionTests() { await waitForState(testManager.multiSessionAgent1Page, USER_STATES.AVAILABLE); await testManager.multiSessionAgent1Page.waitForTimeout(2000); await verifyCurrentState(testManager.multiSessionAgent1Page, USER_STATES.AVAILABLE); + incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); await incomingTaskDiv.waitFor({state: 'visible', timeout: 10000}); await testManager.agent1ExtensionPage.waitForTimeout(2000); await testManager.agent1ExtensionPage .locator('[data-test="generic-person-item-base"]') .first() .waitFor({state: 'visible', timeout: 10000}); + incomingTaskDiv2 = testManager.multiSessionAgent1Page.getByTestId('samples:incoming-task-telephony').first(); await incomingTaskDiv2.waitFor({state: 'visible', timeout: 10000}); await testManager.agent1Page.waitForTimeout(2000); await acceptExtensionCall(testManager.agent1ExtensionPage); @@ -93,9 +109,11 @@ export default function createIncomingTaskAndControlsMultiSessionTests() { await waitForState(testManager.multiSessionAgent1Page, USER_STATES.AVAILABLE); await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); await verifyCurrentState(testManager.multiSessionAgent1Page, USER_STATES.AVAILABLE); + log('Test: Multi-session call sync - Complete'); }); test('Multi-login call controls - verify controls are synchronized', async () => { + log('Test: Multi-login call controls sync - Starting'); // Set both AGENT1 sessions to available state await Promise.all([changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE)]); await testManager.agent1Page.waitForTimeout(2000); @@ -212,16 +230,18 @@ export default function createIncomingTaskAndControlsMultiSessionTests() { verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE), verifyCurrentState(testManager.multiSessionAgent1Page!, USER_STATES.AVAILABLE), ]); + log('Test: Multi-login call controls sync - Complete'); } catch (error) { throw new Error(`Multi-session call controls synchronization failed: ${error.message}`); } }); test('should handle multi-session incoming chat with state synchronization', async () => { + log('Test: Multi-session chat sync - Starting'); await createChatTask(testManager.chatPage, process.env[`${testManager.projectName}_CHAT_URL`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-chat').first(); - const incomingTaskDiv2 = testManager.multiSessionAgent1Page.getByTestId('samples:incoming-task-chat').first(); + let incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-chat').first(); + let incomingTaskDiv2 = testManager.multiSessionAgent1Page.getByTestId('samples:incoming-task-chat').first(); await incomingTaskDiv.waitFor({state: 'visible', timeout: 60000}); await incomingTaskDiv2.waitFor({state: 'visible', timeout: 10000}); await incomingTaskDiv.waitFor({state: 'hidden', timeout: 30000}); @@ -240,6 +260,8 @@ export default function createIncomingTaskAndControlsMultiSessionTests() { await waitForState(testManager.multiSessionAgent1Page, USER_STATES.AVAILABLE); await testManager.multiSessionAgent1Page.waitForTimeout(2000); await verifyCurrentState(testManager.multiSessionAgent1Page, USER_STATES.AVAILABLE); + incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-chat').first(); + incomingTaskDiv2 = testManager.multiSessionAgent1Page.getByTestId('samples:incoming-task-chat').first(); await incomingTaskDiv.waitFor({state: 'visible', timeout: 10000}); await incomingTaskDiv2.waitFor({state: 'visible', timeout: 10000}); await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CHAT); @@ -262,21 +284,21 @@ export default function createIncomingTaskAndControlsMultiSessionTests() { await waitForState(testManager.multiSessionAgent1Page, USER_STATES.AVAILABLE); await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); await verifyCurrentState(testManager.multiSessionAgent1Page, USER_STATES.AVAILABLE); + log('Test: Multi-session chat sync - Complete'); }); test('should handle multi-session incoming email with state synchronization', async () => { + log('Test: Multi-session email sync - Starting'); await createEmailTask(process.env[`${testManager.projectName}_EMAIL_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-email').first(); - const incomingTaskDiv2 = testManager.multiSessionAgent1Page.getByTestId('samples:incoming-task-email').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 50000}); - await incomingTaskDiv2.waitFor({state: 'visible', timeout: 10000}); - await incomingTaskDiv.waitFor({state: 'hidden', timeout: 30000}); - await incomingTaskDiv2.waitFor({state: 'hidden', timeout: 10000}); - await testManager.agent1Page.getByTestId('samples:rona-popup').waitFor({state: 'visible', timeout: 15000}); + await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.EMAIL); + await waitForIncomingTask(testManager.multiSessionAgent1Page, TASK_TYPES.EMAIL); + await testManager.agent1Page.bringToFront(); + await testManager.agent1Page.getByTestId('samples:rona-popup').waitFor({state: 'visible', timeout: 20000}); + await testManager.multiSessionAgent1Page.bringToFront(); await testManager.multiSessionAgent1Page .getByTestId('samples:rona-popup') - .waitFor({state: 'visible', timeout: 15000}); + .waitFor({state: 'visible', timeout: 20000}); await testManager.agent1Page.waitForTimeout(3000); await submitRonaPopup(testManager.multiSessionAgent1Page, RONA_OPTIONS.IDLE); await waitForState(testManager.agent1Page, USER_STATES.MEETING); @@ -289,6 +311,8 @@ export default function createIncomingTaskAndControlsMultiSessionTests() { await waitForState(testManager.agent1Page, USER_STATES.AVAILABLE); await testManager.multiSessionAgent1Page.waitForTimeout(2000); await verifyCurrentState(testManager.multiSessionAgent1Page, USER_STATES.AVAILABLE); + let incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-email').first(); + let incomingTaskDiv2 = testManager.multiSessionAgent1Page.getByTestId('samples:incoming-task-email').first(); await incomingTaskDiv.waitFor({state: 'visible', timeout: 15000}); await incomingTaskDiv2.waitFor({state: 'visible', timeout: 15000}); await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.EMAIL); @@ -311,6 +335,7 @@ export default function createIncomingTaskAndControlsMultiSessionTests() { await waitForState(testManager.multiSessionAgent1Page, USER_STATES.AVAILABLE); await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); await verifyCurrentState(testManager.multiSessionAgent1Page, USER_STATES.AVAILABLE); + log('Test: Multi-session email sync - Complete'); }); test.afterAll(async () => { diff --git a/playwright/tests/incoming-telephony-task-test.spec.ts b/playwright/tests/incoming-telephony-task-test.spec.ts index 070e1673c..aa1e08733 100644 --- a/playwright/tests/incoming-telephony-task-test.spec.ts +++ b/playwright/tests/incoming-telephony-task-test.spec.ts @@ -8,6 +8,7 @@ import { acceptIncomingTask, acceptExtensionCall, submitRonaPopup, + waitForIncomingTask, } from '../Utils/incomingTaskUtils'; import {TASK_TYPES, USER_STATES, THEME_COLORS, WRAPUP_REASONS, RONA_OPTIONS} from '../constants'; import {submitWrapup} from '../Utils/wrapupUtils'; @@ -18,9 +19,12 @@ import { waitForWrapupReasonLogs, getLastWrapupReasonFromLogs, isColorClose, + createLogger, } from '../Utils/helperUtils'; import {TestManager} from '../test-manager'; +const log = createLogger('TelephonyTask'); + let capturedLogs: string[] = []; //NOTE : Make Sure to set RONA Timeout to 18 seconds before running this test. @@ -120,11 +124,10 @@ export default function createIncomingTelephonyTaskTests() { }); test('should accept incoming call, end call and complete wrapup in desktop mode', async () => { + log('Desktop: Accept call + wrapup - Starting'); await createCallTask(testManager.callerPage, process.env[`${testManager.projectName}_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); - await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); + await acceptIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 40000); await waitForState(testManager.agent1Page, USER_STATES.ENGAGED); await verifyCurrentState(testManager.agent1Page, USER_STATES.ENGAGED); await testManager.agent1Page.waitForTimeout(3000); @@ -143,13 +146,14 @@ export default function createIncomingTelephonyTaskTests() { await waitForWrapupReasonLogs(capturedLogs, WRAPUP_REASONS.SALE); expect(await getLastWrapupReasonFromLogs(capturedLogs)).toBe(WRAPUP_REASONS.SALE); expect(await verifyCallbackLogs(capturedLogs, WRAPUP_REASONS.SALE, USER_STATES.AVAILABLE)).toBe(true); + log('Desktop: Accept call + wrapup - Complete'); }); test('should decline incoming call and verify RONA state in desktop mode', async () => { + log('Desktop: Decline call → RONA - Starting'); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await createCallTask(testManager.callerPage, process.env[`${testManager.projectName}_ENTRY_POINT`]!); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); + await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 40000); await testManager.agent1Page.waitForTimeout(3000); await declineIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); await testManager.agent1Page.getByTestId('samples:rona-popup').waitFor({state: 'visible', timeout: 15000}); @@ -162,28 +166,30 @@ export default function createIncomingTelephonyTaskTests() { await endCallTask(testManager.callerPage!); await submitRonaPopup(testManager.agent1Page, RONA_OPTIONS.IDLE); await waitForState(testManager.agent1Page, USER_STATES.MEETING); + log('Desktop: Decline call → RONA - Complete'); }); test('should ignore incoming call and wait for RONA popup in desktop mode', async () => { + log('Desktop: Ignore call → RONA - Starting'); await testManager.agent1Page.waitForTimeout(2000); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await createCallTask(testManager.callerPage, process.env[`${testManager.projectName}_ENTRY_POINT`]!); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); + const incomingTaskDiv = await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 40000); await incomingTaskDiv.waitFor({state: 'hidden', timeout: 30000}); await testManager.agent1Page.getByTestId('samples:rona-popup').waitFor({state: 'visible', timeout: 15000}); await expect(testManager.agent1Page.getByTestId('samples:rona-popup')).toBeVisible(); await endCallTask(testManager.callerPage!); await submitRonaPopup(testManager.agent1Page, RONA_OPTIONS.IDLE); await waitForState(testManager.agent1Page, USER_STATES.MEETING); + log('Desktop: Ignore call → RONA - Complete'); }); test('should set agent state to Available and receive another call in desktop mode', async () => { + log('Desktop: Available + another call - Starting'); await testManager.agent1Page.waitForTimeout(2000); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await createCallTask(testManager.callerPage, process.env[`${testManager.projectName}_ENTRY_POINT`]!); - let incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); + await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 40000); await testManager.agent1Page.waitForTimeout(3000); await declineIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); await testManager.agent1Page.getByTestId('samples:rona-popup').waitFor({state: 'visible', timeout: 15000}); @@ -193,8 +199,7 @@ export default function createIncomingTelephonyTaskTests() { await expect(testManager.agent1Page.getByTestId('samples:rona-popup')).not.toBeVisible(); await testManager.agent1Page.waitForTimeout(5000); await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); - incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 10000}); + const incomingTaskDiv = await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 10000); await expect(incomingTaskDiv).toBeVisible(); await testManager.agent1Page.waitForTimeout(3000); await declineIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); @@ -203,14 +208,15 @@ export default function createIncomingTelephonyTaskTests() { await endCallTask(testManager.callerPage!); await submitRonaPopup(testManager.agent1Page, RONA_OPTIONS.IDLE); await waitForState(testManager.agent1Page, USER_STATES.MEETING); + log('Desktop: Available + another call - Complete'); }); test('should set agent state to busy after declining call in desktop mode', async () => { + log('Desktop: Decline → busy - Starting'); await testManager.agent1Page.waitForTimeout(2000); await createCallTask(testManager.callerPage, process.env[`${testManager.projectName}_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - let incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); + await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 40000); await testManager.agent1Page.waitForTimeout(3000); await declineIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); await testManager.agent1Page.getByTestId('samples:rona-popup').waitFor({state: 'visible', timeout: 15000}); @@ -221,22 +227,24 @@ export default function createIncomingTelephonyTaskTests() { await expect(testManager.agent1Page.getByTestId('samples:rona-popup')).not.toBeVisible(); await waitForState(testManager.agent1Page, USER_STATES.MEETING); await verifyCurrentState(testManager.agent1Page, USER_STATES.MEETING); - incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); + const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); await expect(incomingTaskDiv).toBeHidden(); await endCallTask(testManager.callerPage!); + log('Desktop: Decline → busy - Complete'); await testManager.agent1Page.waitForTimeout(2000); }); test('should handle customer disconnect before agent answers in desktop mode', async () => { + log('Desktop: Customer disconnect - Starting'); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await createCallTask(testManager.callerPage, process.env[`${testManager.projectName}_ENTRY_POINT`]!); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); + const incomingTaskDiv = await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 40000); await endCallTask(testManager.callerPage!); await incomingTaskDiv.waitFor({state: 'hidden', timeout: 30000}); await expect(incomingTaskDiv).toBeHidden(); await waitForState(testManager.agent1Page, USER_STATES.AVAILABLE); await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); + log('Desktop: Customer disconnect - Complete'); }); test.afterAll(async () => { @@ -259,11 +267,11 @@ export default function createIncomingTelephonyTaskTests() { }); test('should accept incoming call, end call and complete wrapup in extension mode', async () => { + log('Extension: Accept call + wrapup - Starting'); await testManager.agent1Page.waitForTimeout(2000); await createCallTask(testManager.callerPage, process.env[`${testManager.projectName}_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); + await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 40000); await testManager.agent1ExtensionPage .locator('[data-test="generic-person-item-base"]') .waitFor({state: 'visible', timeout: 20000}); @@ -287,14 +295,15 @@ export default function createIncomingTelephonyTaskTests() { await waitForWrapupReasonLogs(capturedLogs, WRAPUP_REASONS.SALE); expect(await getLastWrapupReasonFromLogs(capturedLogs)).toBe(WRAPUP_REASONS.SALE); expect(await verifyCallbackLogs(capturedLogs, WRAPUP_REASONS.SALE, USER_STATES.AVAILABLE)).toBe(true); + log('Extension: Accept call + wrapup - Complete'); await testManager.agent1Page.waitForTimeout(10000); }); test('should decline incoming call and verify RONA state in extension mode', async () => { + log('Extension: Decline call → RONA - Starting'); await createCallTask(testManager.callerPage, process.env[`${testManager.projectName}_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); + await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 40000); await testManager.agent1ExtensionPage .locator('[data-test="generic-person-item-base"]') .waitFor({state: 'visible', timeout: 20000}); @@ -315,14 +324,15 @@ export default function createIncomingTelephonyTaskTests() { await waitForStateLogs(capturedLogs, USER_STATES.AGENT_DECLINED); expect(await getLastStateFromLogs(capturedLogs)).toBe(USER_STATES.AGENT_DECLINED); await submitRonaPopup(testManager.agent1Page, RONA_OPTIONS.IDLE); + log('Extension: Decline call → RONA - Complete'); await testManager.agent1Page.waitForTimeout(10000); }); test('should ignore incoming call and wait for RONA popup in extension mode', async () => { + log('Extension: Ignore call → RONA - Starting'); await createCallTask(testManager.callerPage, process.env[`${testManager.projectName}_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); + const incomingTaskDiv = await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 40000); await testManager.agent1ExtensionPage .locator('[data-test="generic-person-item-base"]') .first() @@ -340,14 +350,15 @@ export default function createIncomingTelephonyTaskTests() { await waitForStateLogs(capturedLogs, USER_STATES.RONA); expect(await getLastStateFromLogs(capturedLogs)).toBe(USER_STATES.RONA); await submitRonaPopup(testManager.agent1Page, RONA_OPTIONS.IDLE); + log('Extension: Ignore call → RONA - Complete'); await testManager.agent1Page.waitForTimeout(10000); }); test('should set agent state to Available and receive another call in extension mode', async () => { + log('Extension: Available + another call - Starting'); await createCallTask(testManager.callerPage, process.env[`${testManager.projectName}_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); + await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 40000); await testManager.agent1ExtensionPage .locator('[data-test="generic-person-item-base"]') .waitFor({state: 'visible', timeout: 20000}); @@ -363,17 +374,18 @@ export default function createIncomingTelephonyTaskTests() { await expect(testManager.agent1Page.getByTestId('samples:rona-popup')).not.toBeVisible(); await waitForState(testManager.agent1Page, USER_STATES.AVAILABLE); await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 10000}); + const incomingTaskDiv = await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 10000); await expect(incomingTaskDiv).toBeVisible(); await endCallTask(testManager.callerPage!); + log('Extension: Available + another call - Complete'); await testManager.agent1Page.waitForTimeout(8000); }); test('should set agent state to busy after declining call in extension mode', async () => { + log('Extension: Decline → busy - Starting'); await createCallTask(testManager.callerPage, process.env[`${testManager.projectName}_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); + const incomingTaskDiv = await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 40000); await testManager.agent1ExtensionPage .locator('[data-test="generic-person-item-base"]') .first() @@ -395,14 +407,15 @@ export default function createIncomingTelephonyTaskTests() { ).toBeHidden(); await verifyCurrentState(testManager.agent1Page, USER_STATES.MEETING); await endCallTask(testManager.callerPage!); + log('Extension: Decline → busy - Complete'); await testManager.agent1Page.waitForTimeout(10000); }); test('should handle call disconnect before agent answers in extension mode', async () => { + log('Extension: Call disconnect - Starting'); await createCallTask(testManager.callerPage, process.env[`${testManager.projectName}_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); + const incomingTaskDiv = await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL, 40000); await testManager.agent1Page.waitForTimeout(5000); await endCallTask(testManager.callerPage!); await testManager.agent1Page.waitForTimeout(5000); @@ -410,6 +423,7 @@ export default function createIncomingTelephonyTaskTests() { await expect(incomingTaskDiv).toBeHidden(); await waitForState(testManager.agent1Page, USER_STATES.AVAILABLE); await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); + log('Extension: Call disconnect - Complete'); }); test.afterAll(async () => { diff --git a/playwright/tests/station-login-test.spec.ts b/playwright/tests/station-login-test.spec.ts index 86caf56e0..4334baad0 100644 --- a/playwright/tests/station-login-test.spec.ts +++ b/playwright/tests/station-login-test.spec.ts @@ -7,27 +7,34 @@ import { verifyDesktopOptionVisibility, } from '../Utils/stationLoginUtils'; import {changeUserState, verifyCurrentState, getStateElapsedTime} from '../Utils/userStateUtils'; -import {parseTimeString, waitForWebSocketDisconnection, waitForWebSocketReconnection} from '../Utils/helperUtils'; +import {parseTimeString, waitForWebSocketDisconnection, waitForWebSocketReconnection, createLogger} from '../Utils/helperUtils'; import {USER_STATES, LOGIN_MODE, LONG_WAIT} from '../constants'; import {TestManager} from '../test-manager'; +const log = createLogger('StationLogin'); + export default function createStationLoginTests() { test.describe('Station Login Tests - Dial Number Mode', () => { let testManager: TestManager; test.beforeAll(async ({browser}, testInfo) => { + log('beforeAll: Setting up test manager'); const projectName = testInfo.project.name; testManager = new TestManager(projectName); await testManager.setupForStationLogin(browser); + log('beforeAll: Setup complete'); }); test.afterAll(async () => { + log('afterAll: Cleanup starting'); if (testManager) { await testManager.cleanup(); } + log('afterAll: Cleanup complete'); }); test('should login with Dial Number mode and verify all fields are visible', async () => { + log('DialNumber: Login + verify fields - Starting'); await expect(testManager.agent1Page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); const loginModeSelector = testManager.agent1Page.getByTestId('login-option-select'); await expect(loginModeSelector).toBeVisible({timeout: 2000}); @@ -45,9 +52,11 @@ export default function createStationLoginTests() { ); await expect(testManager.agent1Page.getByTestId('state-select')).toBeVisible({timeout: LONG_WAIT}); await verifyLoginMode(testManager.agent1Page, 'Dial Number'); + log('DialNumber: Login + verify fields - Complete'); }); test('should handle page reload and maintain Dial Number login state', async () => { + log('DialNumber: Reload persistence - Starting'); await ensureUserStateVisible( testManager.agent1Page, LOGIN_MODE.DIAL_NUMBER, @@ -61,9 +70,11 @@ export default function createStationLoginTests() { await expect(testManager.agent1Page.getByTestId('dial-number-input').locator('input')).toHaveValue(dialNumber); } await expect(testManager.agent1Page.getByTestId('state-select')).toBeVisible({timeout: 2000}); + log('DialNumber: Reload persistence - Complete'); }); test('should retain user state timer and switch to Meeting state after network disconnection with Dial Number mode', async () => { + log('DialNumber: Network disconnect - Starting'); await ensureUserStateVisible( testManager.agent1Page, LOGIN_MODE.DIAL_NUMBER, @@ -91,10 +102,12 @@ export default function createStationLoginTests() { const secondsAfterReconnection = parseTimeString(timerAfterReconnection); expect(secondsAfterReconnection).toBeGreaterThan(secondsBeforeDisconnection); await verifyLoginMode(testManager.agent1Page, 'Dial Number'); + log('DialNumber: Network disconnect - Complete'); }); // TODO: The bug of timer reset for Available state should be fixed before implementing this test case test.skip('should reset user state timer and maintain Available state after network disconnection with Dial Number mode', async () => { + log('DialNumber: Available network disconnect - Starting'); await ensureUserStateVisible( testManager.agent1Page, LOGIN_MODE.DIAL_NUMBER, @@ -125,9 +138,11 @@ export default function createStationLoginTests() { ); expect(agentStateChangeLog).toBeTruthy(); await verifyLoginMode(testManager.agent1Page, 'Dial Number'); + log('DialNumber: Available network disconnect - Complete'); }); test('should support multi-login synchronization for Dial Number Mode ', async () => { + log('DialNumber: Multi-login sync - Starting'); await ensureUserStateVisible( testManager.agent1Page, LOGIN_MODE.DIAL_NUMBER, @@ -144,6 +159,7 @@ export default function createStationLoginTests() { .isVisible() .catch(() => false); expect(isLogoutButtonVisible).toBe(false); + log('DialNumber: Multi-login sync - Complete'); }); }); @@ -163,6 +179,7 @@ export default function createStationLoginTests() { }); test('should login with Extension mode and verify all fields are visible', async () => { + log('Extension: Login + verify fields - Starting'); await expect(testManager.agent1Page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); const loginModeSelector = testManager.agent1Page.getByTestId('login-option-select'); await expect(loginModeSelector).toBeVisible({timeout: 2000}); @@ -180,9 +197,11 @@ export default function createStationLoginTests() { ); await expect(testManager.agent1Page.getByTestId('state-select')).toBeVisible({timeout: LONG_WAIT}); await verifyLoginMode(testManager.agent1Page, 'Extension'); + log('Extension: Login + verify fields - Complete'); }); test('should handle page reload and maintain Extension login state', async () => { + log('Extension: Reload persistence - Starting'); await ensureUserStateVisible( testManager.agent1Page, LOGIN_MODE.EXTENSION, @@ -198,9 +217,11 @@ export default function createStationLoginTests() { ); } await expect(testManager.agent1Page.getByTestId('state-select')).toBeVisible({timeout: 2000}); + log('Extension: Reload persistence - Complete'); }); test('should retain user state timer and switch to Meeting state after network disconnection with Extension mode', async () => { + log('Extension: Network disconnect - Starting'); await ensureUserStateVisible( testManager.agent1Page, LOGIN_MODE.EXTENSION, @@ -228,10 +249,12 @@ export default function createStationLoginTests() { const secondsAfterReconnection = parseTimeString(timerAfterReconnection); expect(secondsAfterReconnection).toBeGreaterThan(secondsBeforeDisconnection); await verifyLoginMode(testManager.agent1Page, 'Extension'); + log('Extension: Network disconnect - Complete'); }); // TODO: The bug of timer reset for Available state should be fixed before implementing this test case test.skip('should reset user state timer and maintain Available state after network disconnection with Extension mode', async () => { + log('Extension: Available network disconnect - Starting'); await ensureUserStateVisible( testManager.agent1Page, LOGIN_MODE.EXTENSION, @@ -262,9 +285,11 @@ export default function createStationLoginTests() { ); expect(agentStateChangeLog).toBeTruthy(); await verifyLoginMode(testManager.agent1Page, 'Extension'); + log('Extension: Available network disconnect - Complete'); }); test('should support multi-login synchronization for Extension Mode', async () => { + log('Extension: Multi-login sync - Starting'); await ensureUserStateVisible( testManager.agent1Page, LOGIN_MODE.EXTENSION, @@ -280,6 +305,7 @@ export default function createStationLoginTests() { .isVisible() .catch(() => false); expect(isLogoutButtonVisible).toBe(false); + log('Extension: Multi-login sync - Complete'); }); }); @@ -299,23 +325,25 @@ export default function createStationLoginTests() { }); test('should toggle Desktop option visibility when hideDesktopLogin checkbox is toggled', async () => { + log('HideDesktop: Toggle visibility - Starting'); await expect(testManager.agent1Page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); const hideDesktopCheckbox = testManager.agent1Page.getByTestId('samples:hide-desktop-login-checkbox'); // Uncheck - Desktop should be visible await hideDesktopCheckbox.click(); await testManager.agent1Page.waitForTimeout(500); - await verifyDesktopOptionVisibility(testManager.agent1Page, true); + await verifyDesktopOptionVisibility(testManager.agent1Page, false); // Check - Desktop should be hidden await hideDesktopCheckbox.click(); await testManager.agent1Page.waitForTimeout(500); - await verifyDesktopOptionVisibility(testManager.agent1Page, false); + await verifyDesktopOptionVisibility(testManager.agent1Page, true); // Uncheck again - Desktop should be visible again await hideDesktopCheckbox.click(); await testManager.agent1Page.waitForTimeout(500); - await verifyDesktopOptionVisibility(testManager.agent1Page, true); + await verifyDesktopOptionVisibility(testManager.agent1Page, false); + log('HideDesktop: Toggle visibility - Complete'); }); }); @@ -335,6 +363,7 @@ export default function createStationLoginTests() { }); test('should login with Desktop mode and verify all fields are visible', async () => { + log('Desktop: Login + verify fields - Starting'); await expect(testManager.agent1Page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); const loginModeSelector = testManager.agent1Page.getByTestId('login-option-select'); await expect(loginModeSelector).toBeVisible({timeout: 2000}); @@ -346,17 +375,21 @@ export default function createStationLoginTests() { await telephonyLogin(testManager.agent1Page, LOGIN_MODE.DESKTOP); await expect(testManager.agent1Page.getByTestId('state-select')).toBeVisible({timeout: 3000}); await verifyLoginMode(testManager.agent1Page, 'Desktop'); + log('Desktop: Login + verify fields - Complete'); }); test.skip('should handle page reload and maintain Desktop login state', async () => { + log('Desktop: Reload persistence - Starting'); await ensureUserStateVisible(testManager.agent1Page, LOGIN_MODE.DESKTOP); await agentRelogin(testManager.agent1Page); await expect(testManager.agent1Page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); await verifyLoginMode(testManager.agent1Page, 'Desktop'); await expect(testManager.agent1Page.getByTestId('state-select')).toBeVisible({timeout: 2000}); + log('Desktop: Reload persistence - Complete'); }); test('should retain user state timer and switch to Meeting state after network disconnection with Desktop mode', async () => { + log('Desktop: Network disconnect - Starting'); await ensureUserStateVisible(testManager.agent1Page, LOGIN_MODE.DESKTOP); await changeUserState(testManager.agent1Page, USER_STATES.MEETING); await verifyCurrentState(testManager.agent1Page, USER_STATES.MEETING); @@ -380,10 +413,12 @@ export default function createStationLoginTests() { const secondsAfterReconnection = parseTimeString(timerAfterReconnection); expect(secondsAfterReconnection).toBeGreaterThan(secondsBeforeDisconnection); await verifyLoginMode(testManager.agent1Page, 'Desktop'); + log('Desktop: Network disconnect - Complete'); }); // TODO: The bug of timer reset for Available state should be fixed before implementing this test case test.skip('should reset user state timer and maintain Available state after network disconnection with Desktop mode', async () => { + log('Desktop: Available network disconnect - Starting'); await ensureUserStateVisible(testManager.agent1Page, LOGIN_MODE.DESKTOP); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); @@ -410,6 +445,7 @@ export default function createStationLoginTests() { ); expect(agentStateChangeLog).toBeTruthy(); await verifyLoginMode(testManager.agent1Page, 'Desktop'); + log('Desktop: Available network disconnect - Complete'); }); }); } diff --git a/playwright/tests/tasklist-test.spec.ts b/playwright/tests/tasklist-test.spec.ts index 5b594c09a..1cc99b7e9 100644 --- a/playwright/tests/tasklist-test.spec.ts +++ b/playwright/tests/tasklist-test.spec.ts @@ -1,11 +1,13 @@ import {test, Page, expect} from '@playwright/test'; import {TestManager} from '../test-manager'; import {changeUserState} from '../Utils/userStateUtils'; -import {createCallTask, createChatTask, createEmailTask} from '../Utils/incomingTaskUtils'; +import {createCallTask, createChatTask, createEmailTask, waitForIncomingTask} from '../Utils/incomingTaskUtils'; import {TASK_TYPES, USER_STATES, WRAPUP_REASONS} from '../constants'; import {verifyTaskControls} from '../Utils/taskControlUtils'; import {submitWrapup} from '../Utils/wrapupUtils'; -import {waitForState} from '../Utils/helperUtils'; +import {waitForState, createLogger} from '../Utils/helperUtils'; + +const log = createLogger('TaskList'); let capturedLogs: string[] = []; @@ -46,18 +48,31 @@ async function getCurrentHandleTime(page: Page, index: number = 0): Promise { + await testManager.agent1Page.bringToFront(); const timeoutMs = 60000, pollInterval = 2000; const start = Date.now(); - const type = testId.split('-').pop(); + while (Date.now() - start < timeoutMs) { const taskDiv = testManager.agent1Page.getByTestId(testId).first(); const isVisible = await taskDiv.isVisible().catch(() => false); + if (isVisible) { + // Dismiss any open popovers that might be blocking + await testManager.agent1Page.keyboard.press('Escape'); + await testManager.agent1Page.waitForTimeout(200); + const acceptButton = taskDiv.getByTestId('task:accept-button').first(); + const acceptVisible = await acceptButton.isVisible().catch(() => false); + + if (!acceptVisible) { + await testManager.agent1Page.waitForTimeout(pollInterval); + continue; + } + await expect(acceptButton).toBeVisible({timeout: 5000}); + await expect(acceptButton).toBeEnabled({timeout: 5000}); await acceptButton.click({timeout: 3000}); - return; } await testManager.agent1Page.waitForTimeout(pollInterval); @@ -103,7 +118,7 @@ async function waitForConsoleLogs( const escTitle = escapeForRegExp(title!); const escMedia = escapeForRegExp(mediaType!); const pattern = new RegExp( - '^onTaskSelected invoked for task with title : ' + escTitle + ', and mediaType : ' + escMedia + '$' + '^onTaskSelected invoked for task with title : ' + escTitle + ', and mediaType : ' + escMedia ); const start = Date.now(); @@ -123,6 +138,7 @@ export default function createTaskListTests() { }); test.beforeAll(async ({browser}, testInfo) => { + log('beforeAll: Setting up test manager'); const projectName = testInfo.project.name; testManager = new TestManager(projectName); await testManager.setup(browser, { @@ -132,13 +148,22 @@ export default function createTaskListTests() { enableConsoleLogging: true, }); setupConsoleLogging(testManager.agent1Page); + log('beforeAll: Setup complete'); + }); + + test.afterAll(async () => { + log('afterAll: Cleanup starting'); + if (testManager) { + await testManager.cleanup(); + } + log('afterAll: Cleanup complete'); }); test('Verify Task List for incoming Call', async () => { + log('Test: Task List for Call - Starting'); await createCallTask(testManager.callerPage, process.env[`${testManager.projectName}_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - let incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-telephony').first(); - await incomingTaskDiv.waitFor({state: 'visible', timeout: 40000}); + let incomingTaskDiv = await waitForIncomingTask(testManager.agent1Page, TASK_TYPES.CALL); await testManager.agent1Page.waitForTimeout(1000); const taskListItem = testManager.agent1Page.getByTestId('task-list').getByRole('listitem').first(); expect(taskListItem).toBeVisible(); @@ -177,9 +202,11 @@ export default function createTaskListTests() { await testManager.agent1Page.waitForTimeout(500); await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); await waitForState(testManager.agent1Page, USER_STATES.AVAILABLE); + log('Test: Task List for Call - Complete'); }); test('Verify Task List for incoming Chat Task', async () => { + log('Test: Task List for Chat - Starting'); await createChatTask(testManager.chatPage, process.env[`${testManager.projectName}_CHAT_URL`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-chat').first(); @@ -226,9 +253,11 @@ export default function createTaskListTests() { await testManager.agent1Page.waitForTimeout(2000); await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); await waitForState(testManager.agent1Page, USER_STATES.AVAILABLE); + log('Test: Task List for Chat - Complete'); }); test('Verify Task List for incoming Email Task', async () => { + log('Test: Task List for Email - Starting'); await createEmailTask(process.env[`${testManager.projectName}_EMAIL_ENTRY_POINT`]!); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); const incomingTaskDiv = testManager.agent1Page.getByTestId('samples:incoming-task-email').first(); @@ -278,9 +307,11 @@ export default function createTaskListTests() { await testManager.agent1Page.waitForTimeout(2000); await submitWrapup(testManager.agent1Page, WRAPUP_REASONS.SALE); await waitForState(testManager.agent1Page, USER_STATES.AVAILABLE); + log('Test: Task List for Email - Complete'); }); test('Task List Test with Multiple Tasks', async () => { + log('Test: Multiple Tasks - Starting'); await changeUserState(testManager.agent1Page, USER_STATES.MEETING); await waitForState(testManager.agent1Page, USER_STATES.MEETING); await Promise.all([ @@ -323,9 +354,8 @@ export default function createTaskListTests() { ); capturedLogs.length = 0; } + log('Test: Multiple Tasks - Complete'); }); - test.afterAll(async () => { - await testManager.cleanup(); - }); + // No afterAll here - suite handles cleanup } diff --git a/playwright/tests/user-state-test.spec.ts b/playwright/tests/user-state-test.spec.ts index 159497e3d..d841ecb20 100644 --- a/playwright/tests/user-state-test.spec.ts +++ b/playwright/tests/user-state-test.spec.ts @@ -11,13 +11,15 @@ import { } from '../Utils/userStateUtils'; import {USER_STATES, THEME_COLORS, LOGIN_MODE} from '../constants'; import {TestManager} from '../test-manager'; +import {createLogger} from '../Utils/helperUtils'; -// Shared login and setup before all tests +const log = createLogger('UserState'); export default function createUserStateTests() { let testManager: TestManager; test.beforeAll(async ({browser}, testInfo) => { + log('beforeAll: Setting up test manager'); const projectName = testInfo.project.name; testManager = new TestManager(projectName); await testManager.basicSetup(browser); @@ -44,28 +46,34 @@ export default function createUserStateTests() { }); test.afterAll(async () => { + log('afterAll: Cleanup starting'); if (testManager) { await testManager.cleanup(); } + log('afterAll: Cleanup complete'); }); test('should verify initial state is Meeting', async () => { + log('Test: Verify initial state - Starting'); const state = await getCurrentState(testManager.agent1Page); if (state !== USER_STATES.MEETING) throw new Error('Initial state is not Meeting'); + log('Test: Verify initial state - Complete'); }); test('should verify Meeting state theme color', async () => { + log('Test: Meeting theme color - Starting'); const meetingThemeElement = testManager.agent1Page.getByTestId('state-select'); const meetingThemeColor = await meetingThemeElement.evaluate((el) => getComputedStyle(el).backgroundColor); expect(meetingThemeColor).toBe(THEME_COLORS.MEETING); + log('Test: Meeting theme color - Complete'); }); test('should change state to Available and verify theme and timer reset', async () => { + log('Test: Change to Available - Starting'); await verifyCurrentState(testManager.agent1Page, USER_STATES.MEETING); - await testManager.agent1Page.waitForTimeout(5000); + await testManager.agent1Page.waitForTimeout(10000); const timerBefore = await getStateElapsedTime(testManager.agent1Page); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); - await testManager.agent1Page.waitForTimeout(3000); const timerAfter = await getStateElapsedTime(testManager.agent1Page); const parseTimer = (timer: string) => { @@ -78,9 +86,11 @@ export default function createUserStateTests() { const themeElement = testManager.agent1Page.getByTestId('state-select'); const themeColor = await themeElement.evaluate((el) => getComputedStyle(el).backgroundColor); expect(themeColor).toBe(THEME_COLORS.AVAILABLE); + log('Test: Change to Available - Complete'); }); test('should verify existence and order in which callback and API success are logged for Available state', async () => { + log('Test: Callback order for Available - Starting'); await changeUserState(testManager.agent1Page, USER_STATES.MEETING); await testManager.agent1Page.waitForTimeout(3000); testManager.consoleMessages.length = 0; @@ -92,9 +102,11 @@ export default function createUserStateTests() { testManager.consoleMessages ); if (!isCallbackSuccessful) throw new Error('Callback for Available state not successful'); + log('Test: Callback order for Available - Complete'); }); test('should verify state persistence after page reload', async () => { + log('Test: State persistence after reload - Starting'); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); await verifyCurrentState(testManager.agent1Page, USER_STATES.AVAILABLE); await testManager.agent1Page.waitForTimeout(3000); @@ -115,9 +127,11 @@ export default function createUserStateTests() { const state = await getCurrentState(testManager.agent1Page); if (state !== USER_STATES.AVAILABLE) throw new Error('State is not Available after reload'); + log('Test: State persistence after reload - Complete'); }); test('should test multi-session synchronization', async () => { + log('Test: Multi-session sync - Starting'); // Create multi-session page since basicSetup doesn't include it if (!testManager.multiSessionAgent1Page) { if (!testManager.multiSessionContext) { @@ -152,9 +166,14 @@ export default function createUserStateTests() { if (Math.abs(timer1Parsed - timer2Parsed) > 1) { throw new Error(`Multi-session timer synchronization failed: Primary=${timer1Parsed}, Secondary=${timer2Parsed}`); } + log('Test: Multi-session sync - Complete'); }); test('should test idle state transition and dual timer', async () => { + log('Test: Idle state + dual timer - Starting'); + // Dismiss any open dropdowns/popovers from previous tests + await testManager.agent1Page.keyboard.press('Escape'); + await testManager.agent1Page.waitForTimeout(500); await verifyCurrentState(testManager.agent1Page, USER_STATES.MEETING); await testManager.agent1Page.waitForTimeout(2000); testManager.consoleMessages.length = 0; @@ -192,5 +211,6 @@ export default function createUserStateTests() { expect(secondTimer.length === 2 || secondTimer.length === 3).toBe(true); await changeUserState(testManager.agent1Page, USER_STATES.AVAILABLE); + log('Test: Idle state + dual timer - Complete'); }); } diff --git a/widgets-samples/cc/samples-cc-react-app/src/App.tsx b/widgets-samples/cc/samples-cc-react-app/src/App.tsx index 9fcc4b388..f54cc954d 100644 --- a/widgets-samples/cc/samples-cc-react-app/src/App.tsx +++ b/widgets-samples/cc/samples-cc-react-app/src/App.tsx @@ -180,8 +180,12 @@ function App() { const onTaskSelected = ({task, isClicked}) => { console.log('onTaskSelected invoked for task:', task, 'isClicked:', isClicked); + const callAssociatedDetails = task?.data?.interaction?.callAssociatedDetails; + const mediaType = task?.data?.interaction?.mediaType; + const isSocial = mediaType === 'social'; + const title = isSocial ? callAssociatedDetails?.customerName : callAssociatedDetails?.ani; console.log( - `onTaskSelected invoked for task with title : ${task?.data?.interaction?.callAssociatedDetails?.ani}, and mediaType : ${task?.data?.mediaType}` + `onTaskSelected invoked for task with title : ${title}, and mediaType : ${mediaType}` ); }; @@ -876,7 +880,7 @@ function App() { setCollapsedTasks((prev) => prev.filter((id) => id !== task.data.interactionId)); } }} - data-testid={`samples:incoming-task-${task.data.mediaType}`} + data-testid={`samples:incoming-task-${task.data.interaction?.mediaType}`} > <>