Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions playwright/Utils/advancedTaskControlUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export async function consultOrTransfer(
action: 'consult' | 'transfer',
value: string
): Promise<void> {
await page.bringToFront();
await openConsultOrTransferMenu(page, action);
const popover = await getPopover(page);

Expand All @@ -139,14 +140,13 @@ export async function consultOrTransfer(

// ===== Internal helper functions =====
async function openConsultOrTransferMenu(page: Page, action: 'consult' | 'transfer'): Promise<void> {
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});
}
}

Expand Down
470 changes: 341 additions & 129 deletions playwright/Utils/helperUtils.ts

Large diffs are not rendered by default.

135 changes: 109 additions & 26 deletions playwright/Utils/incomingTaskUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down Expand Up @@ -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');
}
}

/**
Expand All @@ -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();
Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -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.');
Expand All @@ -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.');
Expand All @@ -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');
}
Expand Down Expand Up @@ -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});
Expand Down Expand Up @@ -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});
Expand Down
11 changes: 7 additions & 4 deletions playwright/Utils/stationLoginUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,14 @@ export const dialLogin = async (page: Page, dialNumber?: string): Promise<void>
* ```
*/
export const stationLogout = async (page: Page): Promise<void> => {
// 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')
Expand All @@ -147,6 +148,8 @@ export const stationLogout = async (page: Page): Promise<void> => {
} catch (e) {
throw new Error(`Station logout failed: ${e instanceof Error ? e.message : 'Unknown error'}`);
}
} else {
await page.waitForTimeout(5000);
}
};

Expand Down
6 changes: 5 additions & 1 deletion playwright/Utils/userStateUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ dotenv.config();
export const changeUserState = async (page: Page, userState: string): Promise<void> => {
// 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')
Expand All @@ -40,7 +41,7 @@ export const changeUserState = async (page: Page, userState: string): Promise<vo
}

await stateItem.click({timeout: AWAIT_TIMEOUT});
await page.waitForTimeout(1000);
await page.waitForTimeout(3000);
};

/**
Expand All @@ -54,6 +55,7 @@ export const changeUserState = async (page: Page, userState: string): Promise<vo
* ```
*/
export const getCurrentState = async (page: Page): Promise<string> => {
await page.bringToFront();
const stateName = await page
.getByTestId('state-select')
.getByTestId('state-name')
Expand All @@ -74,6 +76,7 @@ export const getCurrentState = async (page: Page): Promise<string> => {
* ```
*/
export const verifyCurrentState = async (page: Page, expectedState: string): Promise<void> => {
await page.bringToFront();
const currentState = await getCurrentState(page);
if (currentState !== expectedState) {
throw new Error(`Expected state "${expectedState}" but found "${currentState}".`);
Expand All @@ -93,6 +96,7 @@ export const verifyCurrentState = async (page: Page, expectedState: string): Pro
* ```
*/
export const getStateElapsedTime = async (page: Page): Promise<string> => {
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();
Expand Down
15 changes: 13 additions & 2 deletions playwright/Utils/wrapupUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,26 @@ export async function submitWrapup(page: Page, reason: WrapupReason): Promise<vo
if (!reason || reason.trim() === '') {
throw new Error('Wrapup reason is required');
}
await page.bringToFront();

// Dismiss any open popovers that might be blocking interactions
await page.keyboard.press('Escape');
await page.waitForTimeout(UI_SETTLE_TIMEOUT);

const wrapupBox = page.getByTestId('call-control:wrapup-button');
const isWrapupBoxVisible = await wrapupBox
.first()
.waitFor({state: 'visible', timeout: WRAPUP_TIMEOUT})
.then(() => 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);
Expand Down
2 changes: 1 addition & 1 deletion playwright/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions playwright/suites/dial-number-tests.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
2 changes: 1 addition & 1 deletion playwright/suites/digital-incoming-task-tests.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
11 changes: 11 additions & 0 deletions playwright/test-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
};
Loading
Loading