Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
6b81885
Add User State Widget Functionality Test Suite
Jun 16, 2025
e95adf7
fix(cc-widgets): add timeout to config
PrayagGP Jun 17, 2025
a6494e2
fix(cc-widgets): change login method
PrayagGP Jun 17, 2025
7d675c9
fix(cc-widgets): fix element locators
PrayagGP Jun 17, 2025
63c1ed7
fix(cc-widgets): added data-testid for some checkoxes and updated uti…
PrayagGP Jun 17, 2025
de99f62
fix(cc-widgets): removed console logs from utils
PrayagGP Jun 17, 2025
1c05232
add(cc-widgets): created a testfile that uses BeforeEach login
PrayagGP Jun 17, 2025
c37d18b
add(cc-widgets): created a testfile that uses BeforeAll login
PrayagGP Jun 17, 2025
b8a5b64
fix(cc-widgets): add a constants file
PrayagGP Jun 18, 2025
d2be589
fix(cc-widgets): updated the files based on constants.ts
PrayagGP Jun 18, 2025
d7d31cc
fix(cc-widgets): updated user-state-test.spec.ts
PrayagGP Jun 18, 2025
3d5b734
fix(cc-widgets): updated the testfile by removing some redundant code
PrayagGP Jun 18, 2025
4d11615
fix(cc-widgets): updated the files with constants with respect to the…
PrayagGP Jun 19, 2025
807db7c
fix(cc-widgets): renamed the utility file to stationUtils
PrayagGP Jun 19, 2025
382b982
fix(cc-widgets): removed redundant tests
PrayagGP Jun 19, 2025
b25beb9
fix(cc-widgets): updated it with a constant for the url
PrayagGP Jun 19, 2025
578f6a9
fix(cc-widgets): updated the test file name
PrayagGP Jun 19, 2025
47f7af6
fix(cc-widgets): made the timeouts uniform
PrayagGP Jun 19, 2025
8fb3aec
fix(cc-widgets): updated the testfile for timer sync
PrayagGP Jun 19, 2025
7f7d497
fix(cc-widgets): updated the files by changing the names of methods a…
PrayagGP Jun 23, 2025
f205c98
fix(cc-widgets): updated the files by changing the names of constants…
PrayagGP Jun 23, 2025
a277cb1
fix(cc-widgets): updated the workflow with new env variables
PrayagGP Jun 23, 2025
b9bfd84
fix(cc-widgets): updated the data-testid for buttons
PrayagGP Jun 23, 2025
adfee7f
fix(cc-widgets): updated the file to close the pages once the test ends
PrayagGP Jun 23, 2025
7270aee
fix(cc-widgets): updated the file to close the pages once the test ends
PrayagGP Jun 23, 2025
319df66
fix(cc-widgets): added new functions for relogin and multi-session
PrayagGP Jun 24, 2025
b5c61f5
fix(cc-widgets): updated the files with methods for relogin and multi…
PrayagGP Jun 24, 2025
ad79663
fix(cc-widgets): updated the file to provide trace only on failure
PrayagGP Jun 25, 2025
339ef3f
fix(cc-widgets): removed unnecessary changes to the code
PrayagGP Jun 25, 2025
af40373
fix(cc-widgets): updated the data-testids according to the convention
PrayagGP Jun 25, 2025
3a1f925
fix(cc-widgets): updated the datatestids to follow the convention
PrayagGP Jun 25, 2025
4f805b2
fix(cc-widgets): updated the datatestids to follow the convention
PrayagGP Jun 25, 2025
173d848
Merge branch 'upstream/ccwidgets' into ccwidgets_UserStateTest
PrayagGP Jun 25, 2025
9e77c70
fix(cc-widgets): updated the files with the new changes to the main repo
PrayagGP Jun 25, 2025
ac15df5
fix(cc-widgets): removed unnecessary changes from the file
PrayagGP Jun 25, 2025
da1197c
fix(cc-widgets): removed unnecessary changes from the file
PrayagGP Jun 25, 2025
d7f682c
fix(cc-widgets): add data-testids for all the widgets with checkboxes
PrayagGP Jun 25, 2025
851f496
fix(cc-widgets): add data-testid for webex login button
PrayagGP Jun 25, 2025
8b47756
fix(cc-widgets): optimized the test
PrayagGP Jun 25, 2025
63c9e06
fix(cc-widgets): removed all data-testids
PrayagGP Jun 25, 2025
6a64bd6
fix(cc-widgets): added data-testids to buttons and checkboxes
PrayagGP Jun 25, 2025
b0df09b
fix(cc-widgets): updated the workflow
PrayagGP Jun 25, 2025
6a35e37
fix(cc-widgets): updated the workflow
PrayagGP Jun 25, 2025
327ee08
fix(cc-widgets): updated the workflow
PrayagGP Jun 25, 2025
95927fa
Merge branch 'ccwidgets' into ccwidgets_UserStateTest
PrayagGP Jun 25, 2025
d9e2959
fix(cc-widgets): added a new method for login to any mode and using a…
PrayagGP Jun 27, 2025
1dd64b3
Merge branch 'ccwidgets_UserStateTest' of https://github.com/PrayagGP…
PrayagGP Jun 27, 2025
ae90cac
fix(cc-widgets): add constants for login modes
PrayagGP Jun 27, 2025
46dc2ef
fix(cc-widgets): add separate method for Access Token login and modif…
PrayagGP Jun 27, 2025
d6486de
fix(cc-widgets): add typedoc and improve logic to search for console …
PrayagGP Jun 27, 2025
e854c78
fix(cc-widgets): format the code
PrayagGP Jun 27, 2025
27eb062
fix(cc-widgets): format the code
PrayagGP Jun 27, 2025
10e419e
fix(cc-widgets): format the code
PrayagGP Jun 27, 2025
6686e8f
fix(cc-widgets): format the code
PrayagGP Jun 27, 2025
68d1eda
fix(cc-widgets): update the names of env variables
PrayagGP Jun 29, 2025
14c47ce
fix(cc-widgets): add timeouts to waitFor visible state
PrayagGP Jun 30, 2025
f70046e
Merge branch 'ccwidgets' into ccwidgets_UserStateTest
rsarika Jun 30, 2025
9262a5b
fix(cc-widgets): update extension number
PrayagGP Jun 30, 2025
f300b69
fix(cc-widgets): update the workflow
PrayagGP Jun 30, 2025
7331fa7
Merge remote-tracking branch 'upstream/ccwidgets' into ccwidgets_User…
PrayagGP Jun 30, 2025
1e9be03
Merge branch 'ccwidgets_UserStateTest' of https://github.com/PrayagGP…
PrayagGP Jun 30, 2025
ac050e0
fix(cc-widgets): remove auxcodeID logic and use a more consistent app…
PrayagGP Jun 30, 2025
6b2e070
fix(cc-widgets): remove auxcodeID logic and use a more consistent app…
PrayagGP Jun 30, 2025
db4a394
fix(cc-widgets): update files with modified env variables
PrayagGP Jun 30, 2025
8593ade
Merge branch 'ccwidgets' into ccwidgets_UserStateTest
rsarika Jul 1, 2025
9c5a359
fix(cc-widgets): add more delays
PrayagGP Jul 1, 2025
01a19f3
Merge branch 'ccwidgets_UserStateTest' of https://github.com/PrayagGP…
PrayagGP Jul 1, 2025
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
3 changes: 2 additions & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ dotenv.config({path: path.resolve(__dirname, '.env')});
*/
export default defineConfig({
testDir: './playwright',

/* Maximum time one test can run for. */
timeout: 180000,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did we come up with number for timeout ?

@riteshfyi riteshfyi Jun 25, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a single test, we can have these events :
Initializing the first agent: This takes 30 seconds.
Initializing and calling from another agent: This requires 40 seconds.
Waiting for an element to appear: .waitFor({ state: 'visible' }) action has a default timeout of 30 seconds. incoming tasks may take some time to appear, so we are going to use it.
Logging into the extension: This takes an additional 20 seconds.
Adding these together, the total overhead time amounts to 120 seconds (30 + 40 + 30 + 20). we have added a buffer of 60 seconds to account for any extra time needed for tasks during the test.

Comment on lines +13 to +14

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for adding this? IF we are keeping this ensure all our waitFor's have a timeout

@PrayagGP PrayagGP Jun 30, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I have added timeouts for the waitFor

/* Run your local dev server before starting the tests */
webServer: {
command: 'yarn workspace samples-cc-react-app serve',
Expand Down
183 changes: 183 additions & 0 deletions playwright/Utils/initUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import {Page, expect, BrowserContext} from '@playwright/test';
import dotenv from 'dotenv';
import {BASE_URL} from '../constants';

dotenv.config();

/**
* Performs login using an access token from environment variables
* @param page - The Playwright page object
* @param agentId - Agent identifier to get access token for (e.g., 'AGENT1', 'AGENT2')
* @description Requires PW_{agentId}_ACCESS_TOKEN environment variable to be set
* @throws {Error} When PW_{agentId}_ACCESS_TOKEN environment variable is not defined
* @example
* ```typescript
* // Ensure PW_AGENT1_ACCESS_TOKEN is set in .env file
* await loginViaAccessToken(page, 'AGENT1');
*
* // Different agents with their own access tokens
* await loginViaAccessToken(page, 'AGENT2'); // Uses PW_AGENT2_ACCESS_TOKEN
* await loginViaAccessToken(page, 'ADMIN'); // Uses PW_ADMIN_ACCESS_TOKEN
* ```
*/
export const loginViaAccessToken = async (page: Page, agentId: string): Promise<void> => {
await page.goto(BASE_URL);
const accessToken = process.env[`PW_${agentId}_ACCESS_TOKEN`];
await page.getByRole('textbox').click();
if (!accessToken) {
throw new Error(`PW_${agentId}_ACCESS_TOKEN is not defined, OAuth failed`);
}
await page.getByRole('textbox').fill(accessToken);
};

/**
* Performs OAuth login with Webex using agent credentials from environment variables
* @param page - The Playwright page object
* @param agentId - Agent identifier to validate against environment variables (e.g., 'AGENT1', 'AGENT2')
* @description Validates credentials against PW_{agentId}_USERNAME and PW_{agentId}_PASSWORD
* @throws {Error} When agent credentials are not found in environment variables
* @example
* ```typescript
* // OAuth login with agent credentials from environment variables
* await oauthLogin(page, 'AGENT1'); // validates against PW_AGENT1_USERNAME/PW_AGENT1_PASSWORD
* await oauthLogin(page, 'AGENT2'); // validates against PW_AGENT2_USERNAME/PW_AGENT2_PASSWORD
* await oauthLogin(page, 'ADMIN'); // validates against PW_ADMIN_USERNAME/PW_ADMIN_PASSWORD
* ```
*/
export const oauthLogin = async (page: Page, agentId: string): Promise<void> => {
// Check 1: Validate agentId parameter is provided
if (!agentId) {
throw new Error('Agent ID parameter is required');
}

// Check 2: Validate agentId is not empty string
if (agentId.trim() === '') {
throw new Error('Agent ID cannot be empty string');
}

// Check 3: Get credentials from environment variables
const username = process.env[`PW_${agentId}_USERNAME`];
const password = process.env[`PW_PASSWORD`];
// Check 4: Validate environment variables are set
if (!username || !password) {
throw new Error(`Environment variables PW_${agentId}_USERNAME and PW_PASSWORD must be set`);
}

await page.goto(BASE_URL);
await page.locator('#select-base-triggerid').getByText('Access Token').click();
await page.getByTestId('samples:login_option_oauth').getByText('Login with Webex').click();
await page.getByTestId('samples:login_with_webex_button').click();
await page.getByRole('textbox', {name: 'name@example.com'}).fill(username);
await page.getByRole('link', {name: 'Sign in'}).click();
await page.getByRole('textbox', {name: 'Password'}).fill(password);
await page.getByRole('button', {name: 'Sign in'}).click();
};

/**
* Enables all available contact center widgets
* @param page - The Playwright page object
* @description Checks all widget checkboxes including station login, user state, tasks, and call controls
* @example
* ```typescript
* await enableAllWidgets(page);
* await initialiseWidgets(page); // Now all widgets will be available
* ```
*/
export const enableAllWidgets = async (page: Page): Promise<void> => {
await page.getByTestId('samples:widget-stationLogin').check();
await page.getByTestId('samples:widget-userState').check();
await page.getByTestId('samples:widget-incomingTask').check();
await page.getByTestId('samples:widget-taskList').check();
await page.getByTestId('samples:widget-callControl').check();
await page.getByTestId('samples:widget-callControlCAD').check();
await page.getByTestId('samples:widget-outdialCall').check();
};

/**
* Enables multi-login functionality for the SDK
* @param page - The Playwright page object
* @description Must be called before SDK initialization to take effect
* @example
* ```typescript
* await enableMultiLogin(page);
* await initialiseWidgets(page); // Multi-login is now enabled
* ```
*/
export const enableMultiLogin = async (page: Page): Promise<void> => {
await page.getByTestId('samples:multi-login-enable-checkbox').check();
};

/**
* Disables multi-login functionality for the SDK
* @param page - The Playwright page object
* @description Must be called before SDK initialization to take effect
* @example
* ```typescript
* await disableMultiLogin(page);
* await initialiseWidgets(page); // Multi-login is now disabled
* ```
*/
export const disableMultiLogin = async (page: Page): Promise<void> => {
await page.getByTestId('samples:multi-login-enable-checkbox').uncheck();
};

/**
* Initializes the widgets by clicking the init widgets button and waiting for station-login widget to be visible
* @param page - The Playwright page object
* @description The station-login widget should be checked/enabled before using this function
* @throws {Error} When station-login widget is not visible after initialization
* @example
* ```typescript
* // Ensure station-login widget is checked first
* await page.getByTestId('samples:widget-stationLogin').check();
* await initialiseWidgets(page);
* ```
*/
export const initialiseWidgets = async (page: Page): Promise<void> => {
await page.getByTestId('samples:init-widgets-button').click();

await page.getByTestId('station-login-widget').waitFor({state: 'visible', timeout: 50000});
};

/**
* Reloads the page and reinitializes widgets to simulate agent relogin
* @param page - The Playwright page object
* @description Useful for testing state persistence after page reload
* @throws {Error} When widget reinitialization fails after reload
* @example
* ```typescript
* // Test state persistence
* await changeUserState(page, 'Available');
* await agentRelogin(page); // State should persist after reload
* ```
*/
// Helper method for agent relogin - simulates user login along with page reload
export const agentRelogin = async (page: Page): Promise<void> => {
await page.reload();
await initialiseWidgets(page);
};

/**
* Creates a new page in the same browser context for multi-login testing
* @param context - The Playwright browser context
* @returns Promise<Page> - The new page with widgets initialized
* @description Useful for testing multi-login scenarios
* @throws {Error} When widget initialization fails on the new page
* @example
* ```typescript
* const context = await browser.newContext();
* const primaryPage = await context.newPage();
* const secondaryPage = await setupMultiLoginPage(context);
*
* // Test state synchronization between pages
* await changeUserState(primaryPage, 'Available');
* await verifyCurrentState(secondaryPage, 'Available');
* ```
*/
// Helper method for multisession - creates new page and initializes widgets in same context
export const setupMultiLoginPage = async (context: BrowserContext): Promise<Page> => {
const multiLoginPage = await context.newPage();
await multiLoginPage.goto(BASE_URL);
await initialiseWidgets(multiLoginPage);
return multiLoginPage;
};
Comment thread
rsarika marked this conversation as resolved.
156 changes: 156 additions & 0 deletions playwright/Utils/stationLoginUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import {Page, expect} from '@playwright/test';
import dotenv from 'dotenv';
import {LOGIN_MODE} from '../constants';

dotenv.config();

/**
* Performs desktop login for contact center agents
* @param page - The Playwright page object
* @throws {Error} When login fails or required elements are not found
* @example
* ```typescript
* await desktopLogin(page);
* ```
*/
export const desktopLogin = async (page: Page): Promise<void> => {
await page.getByTestId('login-option-select').locator('#select-base-triggerid svg').click();
await page.getByTestId('login-option-Desktop').click();
await page.getByTestId('teams-select-dropdown').locator('#select-base-triggerid div').click();
await page.waitForTimeout(200);
await page.locator('[data-testid^="teams-dropdown-"]').nth(0).locator('span, div').first().click();
await page.waitForTimeout(200);

await page.getByTestId('login-button').click();
};

/**
* Performs extension-based login for contact center agents
* @param page - The Playwright page object
* @param extensionNumber - Optional extension number. Falls back to PW_EXTENSION_NUMBER env variable
* @throws {Error} When extension number is not provided or empty
* @throws {Error} When login fails or required elements are not found
* @example
* ```typescript
* // Using environment variable
* await extensionLogin(page);
*
* // Using custom extension number
* await extensionLogin(page, "1234");
* ```
*/
export const extensionLogin = async (page: Page, extensionNumber?: string): Promise<void> => {
const number = extensionNumber ?? process.env.PW_AGENT1_EXTENSION_NUMBER;
if (!number) {
throw new Error('PW_AGENT1_EXTENSION_NUMBER must be provided');
}

if (number.trim() === '') {
throw new Error('Extension number is empty. Please provide a valid extension number.');
}

await page.getByTestId('login-option-select').locator('#select-base-triggerid svg').click();
await page.getByTestId('login-option-Extension').click();
await page.getByTestId('dial-number-input').locator('input').fill(number);
await page.getByTestId('teams-select-dropdown').locator('#select-base-triggerid div').click();
await page.waitForTimeout(200);
await page.locator('[data-testid^="teams-dropdown-"]').nth(0).locator('span, div').first().click();
await page.getByTestId('login-button').click();
};

/**
* Performs dial number-based login for contact center agents
* @param page - The Playwright page object
* @param dialNumber - Optional dial number. Falls back to PW_DIAL_NUMBER env variable
* @throws {Error} When dial number is not provided or empty
* @throws {Error} When login fails or required elements are not found
* @example
* ```typescript
* // Using environment variable
* await dialLogin(page);
*
* // Using custom dial number
* await dialLogin(page, "+1234567890");
* ```
*/
export const dialLogin = async (page: Page, dialNumber?: string): Promise<void> => {
const number = dialNumber ?? process.env.PW_DIAL_NUMBER;
if (!number) {
throw new Error('PW_DIAL_NUMBER is not defined in the .env file');
}

if (number.trim() === '') {
throw new Error('Dial number is empty. Please provide a valid dial number.');
}

await page.getByTestId('login-option-select').locator('#select-base-triggerid svg').click();
await page.getByTestId('login-option-Dial Number').click();
await page.getByTestId('dial-number-input').locator('div').nth(1).click();
await page.getByTestId('dial-number-input').locator('input').fill(number);
await page.getByTestId('teams-select-dropdown').locator('#select-base-triggerid div').click();
await page.waitForTimeout(200);
await page.locator('[data-testid^="teams-dropdown-"]').nth(0).locator('span, div').first().click();
await page.getByTestId('login-button').click();
};

/**
* Performs station logout for contact center agents
* @param page - The Playwright page object
* @throws {Error} When logout fails or button remains visible after logout
* @example
* ```typescript
* await stationLogout(page);
* ```
*/
export const stationLogout = async (page: Page): Promise<void> => {
// Ensure the logout button is visible before clicking
const logoutButton = page.getByTestId('samples:station-logout-button');
const isLogoutButtonVisible = await logoutButton.isVisible().catch(() => false);
if (!isLogoutButtonVisible) {
throw new Error('Station logout button is not visible. Cannot perform logout.');
}
await page.getByTestId('samples:station-logout-button').click();
//check if the station logout button is hidden after logouts
const isLogoutButtonHidden = await page
.getByTestId('samples:station-logout-button')
.waitFor({state: 'hidden'})
.then(() => true)
.catch(() => false);
if (!isLogoutButtonHidden) {
throw new Error('Station logout button is still visible after logout');
}
};

/**
* Unified telephony login function that supports multiple login modes
* @param page - The Playwright page object
* @param mode - The login mode (Desktop, Extension, or Dial Number)
* @param number - Optional number for Extension or Dial Number modes
* @throws {Error} When unsupported login mode is provided
* @throws {Error} When number is required but not provided
* @example
* ```typescript
* // Desktop login
* await telephonyLogin(page, LOGIN_MODE.DESKTOP);
*
* // Extension login with env variable
* await telephonyLogin(page, LOGIN_MODE.EXTENSION);
*
* // Extension login with custom number
* await telephonyLogin(page, LOGIN_MODE.EXTENSION, "1234");
*
* // Dial number login with custom number
* await telephonyLogin(page, LOGIN_MODE.DIAL_NUMBER, "+1234567890");
* ```
*/
export const telephonyLogin = async (page: Page, mode: string, number?: string): Promise<void> => {
if (mode === LOGIN_MODE.DESKTOP) {
await desktopLogin(page);
} else if (mode === LOGIN_MODE.EXTENSION) {
await extensionLogin(page, number);
} else if (mode === LOGIN_MODE.DIAL_NUMBER) {
await dialLogin(page, number);
} else {
throw new Error(`Unsupported login mode: ${mode}. Use one of: ${Object.values(LOGIN_MODE).join(', ')}`);
}
};
Loading
Loading