Skip to content

Commit 7480fcd

Browse files
PrayagGPrsarika
andauthored
feat(cc-widgets): UI Automation for Station Login Tests (#484)
Co-authored-by: rsarika <95286093+rsarika@users.noreply.github.com>
1 parent 1600659 commit 7480fcd

7 files changed

Lines changed: 546 additions & 13 deletions

File tree

playwright/Utils/helperUtils.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* Parses a time string in MM:SS format and converts it to total seconds
3+
* @param timeString - Time string in format "MM:SS" (e.g., "01:30" for 1 minute 30 seconds)
4+
* @returns Total number of seconds
5+
* @example
6+
* ```typescript
7+
* parseTimeString("01:30"); // Returns 90 (1 minute 30 seconds)
8+
* parseTimeString("00:45"); // Returns 45 (45 seconds)
9+
* parseTimeString("10:00"); // Returns 600 (10 minutes)
10+
* ```
11+
*/
12+
export function parseTimeString(timeString: string): number {
13+
const parts = timeString.split(':');
14+
const minutes = parseInt(parts[0], 10) || 0;
15+
const seconds = parseInt(parts[1], 10) || 0;
16+
return minutes * 60 + seconds;
17+
}
18+
19+
/**
20+
* Waits for WebSocket disconnection by monitoring console messages for specific disconnection indicators
21+
* @param consoleMessages - Array of console messages to monitor
22+
* @param timeoutMs - Maximum time to wait for disconnection in milliseconds (default: 15000)
23+
* @returns Promise<boolean> - True if disconnection is detected, false if timeout is reached
24+
* @description Monitors for network disconnection messages or WebSocket offline status changes
25+
* @example
26+
* ```typescript
27+
* consoleMessages.length = 0; // Clear existing messages
28+
* await page.context().setOffline(true);
29+
* const isDisconnected = await waitForWebSocketDisconnection(consoleMessages);
30+
* expect(isDisconnected).toBe(true);
31+
* ```
32+
*/
33+
export async function waitForWebSocketDisconnection(consoleMessages: string[], timeoutMs: number = 15000): Promise<boolean> {
34+
const startTime = Date.now();
35+
while (Date.now() - startTime < timeoutMs) {
36+
const webSocketDisconnectLog = consoleMessages.find(
37+
(msg) =>
38+
msg.includes('Failed to load resource: net::ERR_INTERNET_DISCONNECTED') ||
39+
msg.includes('[WebSocketStatus] event=checkOnlineStatus | online status= false')
40+
);
41+
if (webSocketDisconnectLog) {
42+
return true;
43+
}
44+
await new Promise((resolve) => setTimeout(resolve, 100));
45+
}
46+
return false;
47+
}
48+
49+
/**
50+
* Waits for WebSocket reconnection by monitoring console messages for online status changes
51+
* @param consoleMessages - Array of console messages to monitor
52+
* @param timeoutMs - Maximum time to wait for reconnection in milliseconds (default: 15000)
53+
* @returns Promise<boolean> - True if reconnection is detected, false if timeout is reached
54+
* @description Monitors for WebSocket online status change messages indicating successful reconnection
55+
* @example
56+
* ```typescript
57+
* consoleMessages.length = 0; // Clear existing messages
58+
* await page.context().setOffline(false);
59+
* const isReconnected = await waitForWebSocketReconnection(consoleMessages);
60+
* expect(isReconnected).toBe(true);
61+
* ```
62+
*/
63+
export async function waitForWebSocketReconnection(consoleMessages: string[], timeoutMs: number = 15000): Promise<boolean> {
64+
const startTime = Date.now();
65+
while (Date.now() - startTime < timeoutMs) {
66+
const webSocketReconnectLog = consoleMessages.find((msg) =>
67+
msg.includes('[WebSocketStatus] event=checkOnlineStatus | online status= true')
68+
);
69+
if (webSocketReconnectLog) {
70+
return true;
71+
}
72+
await new Promise((resolve) => setTimeout(resolve, 100));
73+
}
74+
return false;
75+
}

playwright/Utils/initUtils.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,9 @@ export const disableMultiLogin = async (page: Page): Promise<void> => {
124124
/**
125125
* Initializes the widgets by clicking the init widgets button and waiting for station-login widget to be visible
126126
* @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
127+
* @description The station-login widget should be checked/enabled before using this function.
128+
* If the widget is not visible after 50 seconds, retries once more with another 50-second timeout.
129+
* @throws {Error} When station-login widget is not visible after two initialization attempts (100 seconds total)
129130
* @example
130131
* ```typescript
131132
* // Ensure station-login widget is checked first
@@ -136,7 +137,19 @@ export const disableMultiLogin = async (page: Page): Promise<void> => {
136137
export const initialiseWidgets = async (page: Page): Promise<void> => {
137138
await page.getByTestId('samples:init-widgets-button').click();
138139

139-
await page.getByTestId('station-login-widget').waitFor({state: 'visible', timeout: 50000});
140+
try {
141+
await page.getByTestId('station-login-widget').waitFor({state: 'visible', timeout: 50000});
142+
} catch (error) {
143+
// First attempt failed, try clicking init widgets button again
144+
await page.getByTestId('samples:init-widgets-button').click();
145+
146+
try {
147+
await page.getByTestId('station-login-widget').waitFor({state: 'visible', timeout: 50000});
148+
} catch (secondError) {
149+
// Second attempt also failed, throw error
150+
throw new Error('Station login widget failed to become visible after two initialization attempts (100 seconds total)');
151+
}
152+
}
140153
};
141154

142155
/**

playwright/Utils/stationLoginUtils.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Page, expect} from '@playwright/test';
22
import dotenv from 'dotenv';
3-
import {LOGIN_MODE} from '../constants';
3+
import {LOGIN_MODE, LONG_WAIT} from '../constants';
44

55
dotenv.config();
66

@@ -153,4 +153,45 @@ export const telephonyLogin = async (page: Page, mode: string, number?: string):
153153
} else {
154154
throw new Error(`Unsupported login mode: ${mode}. Use one of: ${Object.values(LOGIN_MODE).join(', ')}`);
155155
}
156-
};
156+
}
157+
158+
/**
159+
* Verifies that the login mode selector displays the expected login mode
160+
* @param page - The Playwright page object
161+
* @param expectedMode - The expected login mode text to verify (e.g., 'Dial Number', 'Extension', 'Desktop')
162+
* @description Checks the login option select element's trigger text to ensure it matches the expected mode
163+
* @throws {Error} When the login mode doesn't match the expected value
164+
* @example
165+
* ```typescript
166+
* await verifyLoginMode(page, LOGIN_MODE.DIAL_NUMBER);
167+
* await verifyLoginMode(page, LOGIN_MODE.EXTENSION);
168+
* await verifyLoginMode(page, LOGIN_MODE.DESKTOP);
169+
* ```
170+
*/
171+
export async function verifyLoginMode(page: Page, expectedMode: string): Promise<void> {
172+
await expect(page.getByTestId('login-option-select').locator('#select-base-triggerid')).toContainText(expectedMode);
173+
}
174+
175+
/**
176+
* Ensures the user state widget is visible by checking its current state and logging in if necessary
177+
* @param page - The Playwright page object
178+
* @param loginMode - The login mode to use if login is required (from LOGIN_MODE constants)
179+
* @description Checks if the state-select widget is visible; if not, performs telephony login and waits for it to appear
180+
* @throws {Error} When telephony login fails or state widget doesn't become visible
181+
* @example
182+
* ```typescript
183+
* await ensureUserStateVisible(page, LOGIN_MODE.DIAL_NUMBER);
184+
* await ensureUserStateVisible(page, LOGIN_MODE.EXTENSION);
185+
* await ensureUserStateVisible(page, LOGIN_MODE.DESKTOP);
186+
* ```
187+
*/
188+
export async function ensureUserStateVisible(page: Page, loginMode: string): Promise<void> {
189+
const isUserStateWidgetVisible = await page
190+
.getByTestId('state-select')
191+
.isVisible()
192+
.catch(() => false);
193+
if (!isUserStateWidgetVisible) {
194+
await telephonyLogin(page, loginMode);
195+
await expect(page.getByTestId('state-select')).toBeVisible({timeout: LONG_WAIT});
196+
}
197+
}

playwright/global.setup.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ import {test as setup} from '@playwright/test';
22
import {oauthLogin} from './Utils/initUtils';
33
const fs = require('fs');
44
const path = require('path');
5-
import dotenv from 'dotenv';
6-
7-
dotenv.config();
85

96
setup('OAuth', async ({browser}) => {
107
const agentId = 'AGENT1'; // Configure which agent to set up

playwright/login-user-state.spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import {test, expect} from '@playwright/test';
22
import fs from 'fs';
3-
import dotenv from 'dotenv';
43

5-
dotenv.config();
64
test.describe('Login and User State tests', async () => {
75
test('Login: should login using Extension login option', async ({page}) => {
86
await page.goto('http://localhost:3000/');

0 commit comments

Comments
 (0)