Skip to content

Commit 999cb53

Browse files
PrayagGPprayprab_ciscorsarika
authored
feat(cc-widgets): UI Automation for State Change (#476)
Co-authored-by: prayprab_cisco <prayprab@cisco.com> Co-authored-by: rsarika <95286093+rsarika@users.noreply.github.com>
1 parent ec1c3d3 commit 999cb53

9 files changed

Lines changed: 813 additions & 46 deletions

File tree

playwright.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ dotenv.config({path: path.resolve(__dirname, '.env')});
1010
*/
1111
export default defineConfig({
1212
testDir: './playwright',
13-
13+
/* Maximum time one test can run for. */
14+
timeout: 180000,
1415
/* Run your local dev server before starting the tests */
1516
webServer: {
1617
command: 'yarn workspace samples-cc-react-app serve',

playwright/Utils/initUtils.ts

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import {Page, expect, BrowserContext} from '@playwright/test';
2+
import dotenv from 'dotenv';
3+
import {BASE_URL} from '../constants';
4+
5+
dotenv.config();
6+
7+
/**
8+
* Performs login using an access token from environment variables
9+
* @param page - The Playwright page object
10+
* @param agentId - Agent identifier to get access token for (e.g., 'AGENT1', 'AGENT2')
11+
* @description Requires PW_{agentId}_ACCESS_TOKEN environment variable to be set
12+
* @throws {Error} When PW_{agentId}_ACCESS_TOKEN environment variable is not defined
13+
* @example
14+
* ```typescript
15+
* // Ensure PW_AGENT1_ACCESS_TOKEN is set in .env file
16+
* await loginViaAccessToken(page, 'AGENT1');
17+
*
18+
* // Different agents with their own access tokens
19+
* await loginViaAccessToken(page, 'AGENT2'); // Uses PW_AGENT2_ACCESS_TOKEN
20+
* await loginViaAccessToken(page, 'ADMIN'); // Uses PW_ADMIN_ACCESS_TOKEN
21+
* ```
22+
*/
23+
export const loginViaAccessToken = async (page: Page, agentId: string): Promise<void> => {
24+
await page.goto(BASE_URL);
25+
const accessToken = process.env[`PW_${agentId}_ACCESS_TOKEN`];
26+
await page.getByRole('textbox').click();
27+
if (!accessToken) {
28+
throw new Error(`PW_${agentId}_ACCESS_TOKEN is not defined, OAuth failed`);
29+
}
30+
await page.getByRole('textbox').fill(accessToken);
31+
};
32+
33+
/**
34+
* Performs OAuth login with Webex using agent credentials from environment variables
35+
* @param page - The Playwright page object
36+
* @param agentId - Agent identifier to validate against environment variables (e.g., 'AGENT1', 'AGENT2')
37+
* @description Validates credentials against PW_{agentId}_USERNAME and PW_{agentId}_PASSWORD
38+
* @throws {Error} When agent credentials are not found in environment variables
39+
* @example
40+
* ```typescript
41+
* // OAuth login with agent credentials from environment variables
42+
* await oauthLogin(page, 'AGENT1'); // validates against PW_AGENT1_USERNAME/PW_AGENT1_PASSWORD
43+
* await oauthLogin(page, 'AGENT2'); // validates against PW_AGENT2_USERNAME/PW_AGENT2_PASSWORD
44+
* await oauthLogin(page, 'ADMIN'); // validates against PW_ADMIN_USERNAME/PW_ADMIN_PASSWORD
45+
* ```
46+
*/
47+
export const oauthLogin = async (page: Page, agentId: string): Promise<void> => {
48+
// Check 1: Validate agentId parameter is provided
49+
if (!agentId) {
50+
throw new Error('Agent ID parameter is required');
51+
}
52+
53+
// Check 2: Validate agentId is not empty string
54+
if (agentId.trim() === '') {
55+
throw new Error('Agent ID cannot be empty string');
56+
}
57+
58+
// Check 3: Get credentials from environment variables
59+
const username = process.env[`PW_${agentId}_USERNAME`];
60+
const password = process.env[`PW_PASSWORD`];
61+
// Check 4: Validate environment variables are set
62+
if (!username || !password) {
63+
throw new Error(`Environment variables PW_${agentId}_USERNAME and PW_PASSWORD must be set`);
64+
}
65+
66+
await page.goto(BASE_URL);
67+
await page.locator('#select-base-triggerid').getByText('Access Token').click();
68+
await page.getByTestId('samples:login_option_oauth').getByText('Login with Webex').click();
69+
await page.getByTestId('samples:login_with_webex_button').click();
70+
await page.getByRole('textbox', {name: 'name@example.com'}).fill(username);
71+
await page.getByRole('link', {name: 'Sign in'}).click();
72+
await page.getByRole('textbox', {name: 'Password'}).fill(password);
73+
await page.getByRole('button', {name: 'Sign in'}).click();
74+
};
75+
76+
/**
77+
* Enables all available contact center widgets
78+
* @param page - The Playwright page object
79+
* @description Checks all widget checkboxes including station login, user state, tasks, and call controls
80+
* @example
81+
* ```typescript
82+
* await enableAllWidgets(page);
83+
* await initialiseWidgets(page); // Now all widgets will be available
84+
* ```
85+
*/
86+
export const enableAllWidgets = async (page: Page): Promise<void> => {
87+
await page.getByTestId('samples:widget-stationLogin').check();
88+
await page.getByTestId('samples:widget-userState').check();
89+
await page.getByTestId('samples:widget-incomingTask').check();
90+
await page.getByTestId('samples:widget-taskList').check();
91+
await page.getByTestId('samples:widget-callControl').check();
92+
await page.getByTestId('samples:widget-callControlCAD').check();
93+
await page.getByTestId('samples:widget-outdialCall').check();
94+
};
95+
96+
/**
97+
* Enables multi-login functionality for the SDK
98+
* @param page - The Playwright page object
99+
* @description Must be called before SDK initialization to take effect
100+
* @example
101+
* ```typescript
102+
* await enableMultiLogin(page);
103+
* await initialiseWidgets(page); // Multi-login is now enabled
104+
* ```
105+
*/
106+
export const enableMultiLogin = async (page: Page): Promise<void> => {
107+
await page.getByTestId('samples:multi-login-enable-checkbox').check();
108+
};
109+
110+
/**
111+
* Disables multi-login functionality for the SDK
112+
* @param page - The Playwright page object
113+
* @description Must be called before SDK initialization to take effect
114+
* @example
115+
* ```typescript
116+
* await disableMultiLogin(page);
117+
* await initialiseWidgets(page); // Multi-login is now disabled
118+
* ```
119+
*/
120+
export const disableMultiLogin = async (page: Page): Promise<void> => {
121+
await page.getByTestId('samples:multi-login-enable-checkbox').uncheck();
122+
};
123+
124+
/**
125+
* Initializes the widgets by clicking the init widgets button and waiting for station-login widget to be visible
126+
* @param page - The Playwright page object
127+
* @description The station-login widget should be checked/enabled before using this function
128+
* @throws {Error} When station-login widget is not visible after initialization
129+
* @example
130+
* ```typescript
131+
* // Ensure station-login widget is checked first
132+
* await page.getByTestId('samples:widget-stationLogin').check();
133+
* await initialiseWidgets(page);
134+
* ```
135+
*/
136+
export const initialiseWidgets = async (page: Page): Promise<void> => {
137+
await page.getByTestId('samples:init-widgets-button').click();
138+
139+
await page.getByTestId('station-login-widget').waitFor({state: 'visible', timeout: 50000});
140+
};
141+
142+
/**
143+
* Reloads the page and reinitializes widgets to simulate agent relogin
144+
* @param page - The Playwright page object
145+
* @description Useful for testing state persistence after page reload
146+
* @throws {Error} When widget reinitialization fails after reload
147+
* @example
148+
* ```typescript
149+
* // Test state persistence
150+
* await changeUserState(page, 'Available');
151+
* await agentRelogin(page); // State should persist after reload
152+
* ```
153+
*/
154+
// Helper method for agent relogin - simulates user login along with page reload
155+
export const agentRelogin = async (page: Page): Promise<void> => {
156+
await page.reload();
157+
await initialiseWidgets(page);
158+
};
159+
160+
/**
161+
* Creates a new page in the same browser context for multi-login testing
162+
* @param context - The Playwright browser context
163+
* @returns Promise<Page> - The new page with widgets initialized
164+
* @description Useful for testing multi-login scenarios
165+
* @throws {Error} When widget initialization fails on the new page
166+
* @example
167+
* ```typescript
168+
* const context = await browser.newContext();
169+
* const primaryPage = await context.newPage();
170+
* const secondaryPage = await setupMultiLoginPage(context);
171+
*
172+
* // Test state synchronization between pages
173+
* await changeUserState(primaryPage, 'Available');
174+
* await verifyCurrentState(secondaryPage, 'Available');
175+
* ```
176+
*/
177+
// Helper method for multisession - creates new page and initializes widgets in same context
178+
export const setupMultiLoginPage = async (context: BrowserContext): Promise<Page> => {
179+
const multiLoginPage = await context.newPage();
180+
await multiLoginPage.goto(BASE_URL);
181+
await initialiseWidgets(multiLoginPage);
182+
return multiLoginPage;
183+
};
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import {Page, expect} from '@playwright/test';
2+
import dotenv from 'dotenv';
3+
import {LOGIN_MODE} from '../constants';
4+
5+
dotenv.config();
6+
7+
/**
8+
* Performs desktop login for contact center agents
9+
* @param page - The Playwright page object
10+
* @throws {Error} When login fails or required elements are not found
11+
* @example
12+
* ```typescript
13+
* await desktopLogin(page);
14+
* ```
15+
*/
16+
export const desktopLogin = async (page: Page): Promise<void> => {
17+
await page.getByTestId('login-option-select').locator('#select-base-triggerid svg').click();
18+
await page.getByTestId('login-option-Desktop').click();
19+
await page.getByTestId('teams-select-dropdown').locator('#select-base-triggerid div').click();
20+
await page.waitForTimeout(200);
21+
await page.locator('[data-testid^="teams-dropdown-"]').nth(0).locator('span, div').first().click();
22+
await page.waitForTimeout(200);
23+
24+
await page.getByTestId('login-button').click();
25+
};
26+
27+
/**
28+
* Performs extension-based login for contact center agents
29+
* @param page - The Playwright page object
30+
* @param extensionNumber - Optional extension number. Falls back to PW_EXTENSION_NUMBER env variable
31+
* @throws {Error} When extension number is not provided or empty
32+
* @throws {Error} When login fails or required elements are not found
33+
* @example
34+
* ```typescript
35+
* // Using environment variable
36+
* await extensionLogin(page);
37+
*
38+
* // Using custom extension number
39+
* await extensionLogin(page, "1234");
40+
* ```
41+
*/
42+
export const extensionLogin = async (page: Page, extensionNumber?: string): Promise<void> => {
43+
const number = extensionNumber ?? process.env.PW_AGENT1_EXTENSION_NUMBER;
44+
if (!number) {
45+
throw new Error('PW_AGENT1_EXTENSION_NUMBER must be provided');
46+
}
47+
48+
if (number.trim() === '') {
49+
throw new Error('Extension number is empty. Please provide a valid extension number.');
50+
}
51+
52+
await page.getByTestId('login-option-select').locator('#select-base-triggerid svg').click();
53+
await page.getByTestId('login-option-Extension').click();
54+
await page.getByTestId('dial-number-input').locator('input').fill(number);
55+
await page.getByTestId('teams-select-dropdown').locator('#select-base-triggerid div').click();
56+
await page.waitForTimeout(200);
57+
await page.locator('[data-testid^="teams-dropdown-"]').nth(0).locator('span, div').first().click();
58+
await page.getByTestId('login-button').click();
59+
};
60+
61+
/**
62+
* Performs dial number-based login for contact center agents
63+
* @param page - The Playwright page object
64+
* @param dialNumber - Optional dial number. Falls back to PW_DIAL_NUMBER env variable
65+
* @throws {Error} When dial number is not provided or empty
66+
* @throws {Error} When login fails or required elements are not found
67+
* @example
68+
* ```typescript
69+
* // Using environment variable
70+
* await dialLogin(page);
71+
*
72+
* // Using custom dial number
73+
* await dialLogin(page, "+1234567890");
74+
* ```
75+
*/
76+
export const dialLogin = async (page: Page, dialNumber?: string): Promise<void> => {
77+
const number = dialNumber ?? process.env.PW_DIAL_NUMBER;
78+
if (!number) {
79+
throw new Error('PW_DIAL_NUMBER is not defined in the .env file');
80+
}
81+
82+
if (number.trim() === '') {
83+
throw new Error('Dial number is empty. Please provide a valid dial number.');
84+
}
85+
86+
await page.getByTestId('login-option-select').locator('#select-base-triggerid svg').click();
87+
await page.getByTestId('login-option-Dial Number').click();
88+
await page.getByTestId('dial-number-input').locator('div').nth(1).click();
89+
await page.getByTestId('dial-number-input').locator('input').fill(number);
90+
await page.getByTestId('teams-select-dropdown').locator('#select-base-triggerid div').click();
91+
await page.waitForTimeout(200);
92+
await page.locator('[data-testid^="teams-dropdown-"]').nth(0).locator('span, div').first().click();
93+
await page.getByTestId('login-button').click();
94+
};
95+
96+
/**
97+
* Performs station logout for contact center agents
98+
* @param page - The Playwright page object
99+
* @throws {Error} When logout fails or button remains visible after logout
100+
* @example
101+
* ```typescript
102+
* await stationLogout(page);
103+
* ```
104+
*/
105+
export const stationLogout = async (page: Page): Promise<void> => {
106+
// Ensure the logout button is visible before clicking
107+
const logoutButton = page.getByTestId('samples:station-logout-button');
108+
const isLogoutButtonVisible = await logoutButton.isVisible().catch(() => false);
109+
if (!isLogoutButtonVisible) {
110+
throw new Error('Station logout button is not visible. Cannot perform logout.');
111+
}
112+
await page.getByTestId('samples:station-logout-button').click();
113+
//check if the station logout button is hidden after logouts
114+
const isLogoutButtonHidden = await page
115+
.getByTestId('samples:station-logout-button')
116+
.waitFor({state: 'hidden'})
117+
.then(() => true)
118+
.catch(() => false);
119+
if (!isLogoutButtonHidden) {
120+
throw new Error('Station logout button is still visible after logout');
121+
}
122+
};
123+
124+
/**
125+
* Unified telephony login function that supports multiple login modes
126+
* @param page - The Playwright page object
127+
* @param mode - The login mode (Desktop, Extension, or Dial Number)
128+
* @param number - Optional number for Extension or Dial Number modes
129+
* @throws {Error} When unsupported login mode is provided
130+
* @throws {Error} When number is required but not provided
131+
* @example
132+
* ```typescript
133+
* // Desktop login
134+
* await telephonyLogin(page, LOGIN_MODE.DESKTOP);
135+
*
136+
* // Extension login with env variable
137+
* await telephonyLogin(page, LOGIN_MODE.EXTENSION);
138+
*
139+
* // Extension login with custom number
140+
* await telephonyLogin(page, LOGIN_MODE.EXTENSION, "1234");
141+
*
142+
* // Dial number login with custom number
143+
* await telephonyLogin(page, LOGIN_MODE.DIAL_NUMBER, "+1234567890");
144+
* ```
145+
*/
146+
export const telephonyLogin = async (page: Page, mode: string, number?: string): Promise<void> => {
147+
if (mode === LOGIN_MODE.DESKTOP) {
148+
await desktopLogin(page);
149+
} else if (mode === LOGIN_MODE.EXTENSION) {
150+
await extensionLogin(page, number);
151+
} else if (mode === LOGIN_MODE.DIAL_NUMBER) {
152+
await dialLogin(page, number);
153+
} else {
154+
throw new Error(`Unsupported login mode: ${mode}. Use one of: ${Object.values(LOGIN_MODE).join(', ')}`);
155+
}
156+
};

0 commit comments

Comments
 (0)