From eb5077d8199d67cab8ae7fc777dd35c0bcaf8371 Mon Sep 17 00:00:00 2001 From: PrayagGP Date: Wed, 2 Jul 2025 00:48:13 +0530 Subject: [PATCH 01/10] fix(cc-widgets): add station-login tests --- playwright/station-login-test.spec.ts | 458 ++++++++++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100644 playwright/station-login-test.spec.ts diff --git a/playwright/station-login-test.spec.ts b/playwright/station-login-test.spec.ts new file mode 100644 index 000000000..42bb175dd --- /dev/null +++ b/playwright/station-login-test.spec.ts @@ -0,0 +1,458 @@ +import {test, expect, Page, BrowserContext} from '@playwright/test'; +import { + enableAllWidgets, + enableMultiLogin, + initialiseWidgets, + agentRelogin, + setupMultiLoginPage, + loginViaAccessToken, +} from './Utils/initUtils'; +import {stationLogout, telephonyLogin} from './Utils/stationLoginUtils'; +import { + getCurrentState, + changeUserState, + verifyCurrentState, + getStateElapsedTime, + validateConsoleStateChange, + checkCallbackSequence, +} from './Utils/userStateUtils'; +import {USER_STATES, THEME_COLORS, LOGIN_MODE, LONG_WAIT} from './constants'; +import dotenv from 'dotenv'; + +dotenv.config(); + +let page: Page; +let context: BrowserContext; +let consoleMessages: string[] = []; + +function parseTimeString(timeString: string): number { + const parts = timeString.split(':'); + const minutes = parseInt(parts[0], 10) || 0; + const seconds = parseInt(parts[1], 10) || 0; + return minutes * 60 + seconds; +} + +async function waitForWebSocketDisconnection(consoleMessages: string[], timeoutMs: number = 15000): Promise { + const startTime = Date.now(); + while (Date.now() - startTime < timeoutMs) { + const webSocketDisconnectLog = consoleMessages.find( + (msg) => + msg.includes('Failed to load resource: net::ERR_INTERNET_DISCONNECTED') || + msg.includes('[WebSocketStatus] event=checkOnlineStatus | online status= false') + ); + if (webSocketDisconnectLog) { + return true; + } + await new Promise((resolve) => setTimeout(resolve, 100)); + } + return false; +} + +async function waitForWebSocketReconnection(consoleMessages: string[], timeoutMs: number = 15000): Promise { + const startTime = Date.now(); + while (Date.now() - startTime < timeoutMs) { + const webSocketReconnectLog = consoleMessages.find((msg) => + msg.includes('[WebSocketStatus] event=checkOnlineStatus | online status= true') + ); + if (webSocketReconnectLog) { + return true; + } + await new Promise((resolve) => setTimeout(resolve, 100)); + } + return false; +} + +// Helper function to verify login mode +async function verifyLoginMode(page: Page, expectedMode: string): Promise { + await expect(page.getByTestId('login-option-select').locator('#select-base-triggerid')).toContainText(expectedMode); +} + +// Helper function to ensure user state widget is visible and login if needed +async function ensureUserStateVisible(page: Page, loginMode: string): Promise { + const isUserStateWidgetVisible = await page + .getByTestId('state-select') + .isVisible() + .catch(() => false); + if (!isUserStateWidgetVisible) { + await telephonyLogin(page, loginMode); + await expect(page.getByTestId('state-select')).toBeVisible({timeout: LONG_WAIT}); + } +} + +test.describe('Station Login Tests - Dial Number Mode', () => { + test.beforeAll(async ({browser}) => { + context = await browser.newContext(); + page = await context.newPage(); + consoleMessages = []; + page.on('console', (msg) => consoleMessages.push(msg.text())); + await loginViaAccessToken(page, 'AGENT1'); + await enableMultiLogin(page); + await enableAllWidgets(page); + await initialiseWidgets(page); + const isLogoutButtonVisible = await page + .getByTestId('samples:station-logout-button') + .isVisible() + .catch(() => false); + if (isLogoutButtonVisible) { + await stationLogout(page); + } + await expect(page.getByTestId('station-login-widget')).toBeVisible(); + }); + + test.afterAll(async () => { + if (page) { + const isLogoutButtonVisible = await page + .getByTestId('samples:station-logout-button') + .isVisible() + .catch(() => false); + if (isLogoutButtonVisible) { + await stationLogout(page); + } + await context.close(); + } + }); + + test.beforeEach(async () => { + consoleMessages.length = 0; + }); + + test('should login with Dial Number mode and verify all fields are visible', async () => { + await expect(page.getByTestId('station-login-widget')).toBeVisible(); + const loginModeSelector = page.getByTestId('login-option-select'); + await expect(loginModeSelector).toBeVisible(); + const phoneNumberInput = page.getByTestId('dial-number-input'); + await expect(phoneNumberInput).toBeVisible(); + const teamSelectionDropdown = page.getByTestId('teams-select-dropdown'); + await expect(teamSelectionDropdown).toBeVisible(); + const saveAndContinueButton = page.getByTestId('login-button'); + await expect(saveAndContinueButton).toBeVisible(); + await expect(saveAndContinueButton).toContainText('Save & Continue'); + await telephonyLogin(page, LOGIN_MODE.DIAL_NUMBER); + await expect(page.getByTestId('state-select')).toBeVisible({timeout: LONG_WAIT}); + await verifyLoginMode(page, 'Dial Number'); + }); + + test('should handle page reload and maintain Dial Number login state', async () => { + await ensureUserStateVisible(page, LOGIN_MODE.DIAL_NUMBER); + await agentRelogin(page); + await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await verifyLoginMode(page, 'Dial Number'); + const dialNumber = process.env.PW_AGENT1_DIAL_NUMBER; + if (dialNumber) { + await expect(page.getByTestId('dial-number-input').locator('input')).toHaveValue(dialNumber); + } + await expect(page.getByTestId('state-select')).toBeVisible(); + }); + + test('should retain user state timer and switch to Meeting state after network disconnection with Dial Number mode', async () => { + await ensureUserStateVisible(page, LOGIN_MODE.DIAL_NUMBER); + await changeUserState(page, USER_STATES.MEETING); + await verifyCurrentState(page, USER_STATES.MEETING); + const timerBeforeDisconnection = await getStateElapsedTime(page); + const secondsBeforeDisconnection = parseTimeString(timerBeforeDisconnection); + await page.waitForTimeout(15000); + consoleMessages.length = 0; + await page.context().setOffline(true); + await page.waitForTimeout(3000); + const isWebSocketDisconnected = await waitForWebSocketDisconnection(consoleMessages); + expect(isWebSocketDisconnected).toBe(true); + await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await verifyLoginMode(page, 'Dial Number'); + consoleMessages.length = 0; + await page.context().setOffline(false); + await page.waitForTimeout(3000); + const isWebSocketReconnected = await waitForWebSocketReconnection(consoleMessages); + expect(isWebSocketReconnected).toBe(true); + await verifyCurrentState(page, USER_STATES.MEETING); + const timerAfterReconnection = await getStateElapsedTime(page); + const secondsAfterReconnection = parseTimeString(timerAfterReconnection); + expect(secondsAfterReconnection).toBeGreaterThan(secondsBeforeDisconnection); + await verifyLoginMode(page, 'Dial Number'); + }); + + // 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 () => { + await ensureUserStateVisible(page, LOGIN_MODE.DIAL_NUMBER); + await changeUserState(page, USER_STATES.AVAILABLE); + await verifyCurrentState(page, USER_STATES.AVAILABLE); + await page.waitForTimeout(15000); + const timerBeforeDisconnection = await getStateElapsedTime(page); + const secondsBeforeDisconnection = parseTimeString(timerBeforeDisconnection); + consoleMessages.length = 0; + await page.context().setOffline(true); + const isWebSocketDisconnected = await waitForWebSocketDisconnection(consoleMessages); + expect(isWebSocketDisconnected).toBe(true); + await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await verifyLoginMode(page, 'Dial Number'); + consoleMessages.length = 0; + await page.context().setOffline(false); + const isWebSocketReconnected = await waitForWebSocketReconnection(consoleMessages); + expect(isWebSocketReconnected).toBe(true); + await verifyCurrentState(page, USER_STATES.AVAILABLE); + const timerAfterReconnection = await getStateElapsedTime(page); + const secondsAfterReconnection = parseTimeString(timerAfterReconnection); + expect(secondsAfterReconnection).toBeLessThan(secondsBeforeDisconnection); + await page.waitForTimeout(10000); + const agentStateChangeLog = consoleMessages.find( + (msg) => msg.includes('AGENT Status set successfully') || msg.includes('Agent state changed successfully') + ); + expect(agentStateChangeLog).toBeTruthy(); + await verifyLoginMode(page, 'Dial Number'); + }); + + test('should support multi-session Dial Number login synchronization', async () => { + await ensureUserStateVisible(page, LOGIN_MODE.DIAL_NUMBER); + const multiSessionPage = await setupMultiLoginPage(context); + await verifyLoginMode(multiSessionPage, 'Dial Number'); + //Verify if signing out from one session logs out the other session + await multiSessionPage.getByTestId('samples:station-logout-button').click(); + await page.waitForTimeout(2000); + const isLogoutButtonVisible = await page + .getByTestId('samples:station-logout-button') + .isVisible() + .catch(() => false); + expect(isLogoutButtonVisible).toBe(false); + + await multiSessionPage.close(); + }); +}); + +test.describe('Station Login Tests - Extension Mode', () => { + test.beforeAll(async ({browser}) => { + context = await browser.newContext(); + page = await context.newPage(); + consoleMessages = []; + page.on('console', (msg) => consoleMessages.push(msg.text())); + await loginViaAccessToken(page, 'AGENT1'); + await enableMultiLogin(page); + await enableAllWidgets(page); + await initialiseWidgets(page); + const isLogoutButtonVisible = await page + .getByTestId('samples:station-logout-button') + .isVisible() + .catch(() => false); + if (isLogoutButtonVisible) { + await stationLogout(page); + } + }); + + test.afterAll(async () => { + if (page) { + const isLogoutButtonVisible = await page + .getByTestId('samples:station-logout-button') + .isVisible() + .catch(() => false); + if (isLogoutButtonVisible) { + await stationLogout(page); + } + await context.close(); + } + }); + + test.beforeEach(async () => { + consoleMessages.length = 0; + }); + + test('should login with Extension mode and verify all fields are visible', async () => { + await expect(page.getByTestId('station-login-widget')).toBeVisible(); + const loginModeSelector = page.getByTestId('login-option-select'); + await expect(loginModeSelector).toBeVisible(); + const phoneNumberInput = page.getByTestId('dial-number-input'); + await expect(phoneNumberInput).toBeVisible(); + const teamSelectionDropdown = page.getByTestId('teams-select-dropdown'); + await expect(teamSelectionDropdown).toBeVisible(); + const saveAndContinueButton = page.getByTestId('login-button'); + await expect(saveAndContinueButton).toBeVisible(); + await expect(saveAndContinueButton).toContainText('Save & Continue'); + await telephonyLogin(page, LOGIN_MODE.EXTENSION); + await expect(page.getByTestId('state-select')).toBeVisible({timeout: LONG_WAIT}); + await verifyLoginMode(page, 'Extension'); + }); + + test('should handle page reload and maintain Extension login state', async () => { + await ensureUserStateVisible(page, LOGIN_MODE.EXTENSION); + await agentRelogin(page); + await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await verifyLoginMode(page, 'Extension'); + const extensionNumber = process.env.PW_AGENT1_EXTENSION; + if (extensionNumber) { + await expect(page.getByTestId('dial-number-input').locator('input')).toHaveValue(extensionNumber); + } + await expect(page.getByTestId('state-select')).toBeVisible(); + }); + + test('should retain user state timer and switch to Meeting state after network disconnection with Extension mode', async () => { + await ensureUserStateVisible(page, LOGIN_MODE.EXTENSION); + await changeUserState(page, USER_STATES.MEETING); + await verifyCurrentState(page, USER_STATES.MEETING); + const timerBeforeDisconnection = await getStateElapsedTime(page); + const secondsBeforeDisconnection = parseTimeString(timerBeforeDisconnection); + await page.waitForTimeout(3000); + consoleMessages.length = 0; + await page.context().setOffline(true); + const isWebSocketDisconnected = await waitForWebSocketDisconnection(consoleMessages); + expect(isWebSocketDisconnected).toBe(true); + await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await verifyLoginMode(page, 'Extension'); + consoleMessages.length = 0; + await page.context().setOffline(false); + const isWebSocketReconnected = await waitForWebSocketReconnection(consoleMessages); + expect(isWebSocketReconnected).toBe(true); + await verifyCurrentState(page, USER_STATES.MEETING); + const timerAfterReconnection = await getStateElapsedTime(page); + const secondsAfterReconnection = parseTimeString(timerAfterReconnection); + expect(secondsAfterReconnection).toBeGreaterThan(secondsBeforeDisconnection); + await verifyLoginMode(page, 'Extension'); + }); + + // 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 () => { + await ensureUserStateVisible(page, LOGIN_MODE.EXTENSION); + await changeUserState(page, USER_STATES.AVAILABLE); + await verifyCurrentState(page, USER_STATES.AVAILABLE); + await page.waitForTimeout(15000); + const timerBeforeDisconnection = await getStateElapsedTime(page); + const secondsBeforeDisconnection = parseTimeString(timerBeforeDisconnection); + consoleMessages.length = 0; + await page.context().setOffline(true); + const isWebSocketDisconnected = await waitForWebSocketDisconnection(consoleMessages); + expect(isWebSocketDisconnected).toBe(true); + await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await verifyLoginMode(page, 'Extension'); + consoleMessages.length = 0; + await page.context().setOffline(false); + const isWebSocketReconnected = await waitForWebSocketReconnection(consoleMessages); + expect(isWebSocketReconnected).toBe(true); + await verifyCurrentState(page, USER_STATES.AVAILABLE); + const timerAfterReconnection = await getStateElapsedTime(page); + const secondsAfterReconnection = parseTimeString(timerAfterReconnection); + expect(secondsAfterReconnection).toBeLessThan(secondsBeforeDisconnection); + await page.waitForTimeout(10000); + const agentStateChangeLog = consoleMessages.find( + (msg) => msg.includes('AGENT Status set successfully') || msg.includes('Agent state changed successfully') + ); + expect(agentStateChangeLog).toBeTruthy(); + await verifyLoginMode(page, 'Extension'); + }); + + test('should support multi-session Extension login synchronization', async () => { + await ensureUserStateVisible(page, LOGIN_MODE.EXTENSION); + const multiSessionPage = await setupMultiLoginPage(context); + await verifyLoginMode(multiSessionPage, 'Extension'); + await multiSessionPage.getByTestId('samples:station-logout-button').click(); + await page.waitForTimeout(2000); + const isLogoutButtonVisible = await page + .getByTestId('samples:station-logout-button') + .isVisible() + .catch(() => false); + expect(isLogoutButtonVisible).toBe(false); + await multiSessionPage.close(); + }); +}); + +test.describe('Station Login Tests - Desktop Mode', () => { + test.beforeAll(async ({browser}) => { + context = await browser.newContext(); + page = await context.newPage(); + consoleMessages = []; + page.on('console', (msg) => consoleMessages.push(msg.text())); + await loginViaAccessToken(page, 'AGENT1'); + await enableAllWidgets(page); + await initialiseWidgets(page); + const isLogoutButtonVisible = await page + .getByTestId('samples:station-logout-button') + .isVisible() + .catch(() => false); + if (isLogoutButtonVisible) { + await stationLogout(page); + } + await expect(page.getByTestId('station-login-widget')).toBeVisible(); + }); + + test.afterAll(async () => { + if (page) { + await stationLogout(page); + await context.close(); + } + }); + + test.beforeEach(async () => { + consoleMessages.length = 0; + }); + + test('should login with Desktop mode and verify all fields are visible', async () => { + await expect(page.getByTestId('station-login-widget')).toBeVisible(); + const loginModeSelector = page.getByTestId('login-option-select'); + await expect(loginModeSelector).toBeVisible(); + const teamSelectionDropdown = page.getByTestId('teams-select-dropdown'); + await expect(teamSelectionDropdown).toBeVisible(); + const saveAndContinueButton = page.getByTestId('login-button'); + await expect(saveAndContinueButton).toBeVisible(); + await expect(saveAndContinueButton).toContainText('Save & Continue'); + await telephonyLogin(page, LOGIN_MODE.DESKTOP); + await expect(page.getByTestId('state-select')).toBeVisible({timeout: 3000}); + await verifyLoginMode(page, 'Desktop'); + }); + + test('should handle page reload and maintain Desktop login state', async () => { + await ensureUserStateVisible(page, LOGIN_MODE.DESKTOP); + await agentRelogin(page); + await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await verifyLoginMode(page, 'Desktop'); + await expect(page.getByTestId('state-select')).toBeVisible(); + }); + + test('should retain user state timer and switch to Meeting state after network disconnection with Desktop mode', async () => { + await ensureUserStateVisible(page, LOGIN_MODE.DESKTOP); + await changeUserState(page, USER_STATES.MEETING); + await verifyCurrentState(page, USER_STATES.MEETING); + const timerBeforeDisconnection = await getStateElapsedTime(page); + const secondsBeforeDisconnection = parseTimeString(timerBeforeDisconnection); + await page.waitForTimeout(3000); + consoleMessages.length = 0; + await page.context().setOffline(true); + const isWebSocketDisconnected = await waitForWebSocketDisconnection(consoleMessages); + expect(isWebSocketDisconnected).toBe(true); + await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await verifyLoginMode(page, 'Desktop'); + consoleMessages.length = 0; + await page.context().setOffline(false); + const isWebSocketReconnected = await waitForWebSocketReconnection(consoleMessages); + expect(isWebSocketReconnected).toBe(true); + await verifyCurrentState(page, USER_STATES.MEETING); + const timerAfterReconnection = await getStateElapsedTime(page); + const secondsAfterReconnection = parseTimeString(timerAfterReconnection); + expect(secondsAfterReconnection).toBeGreaterThan(secondsBeforeDisconnection); + await verifyLoginMode(page, 'Desktop'); + }); + + // 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 () => { + await ensureUserStateVisible(page, LOGIN_MODE.DESKTOP); + await changeUserState(page, USER_STATES.AVAILABLE); + await verifyCurrentState(page, USER_STATES.AVAILABLE); + await page.waitForTimeout(15000); + const timerBeforeDisconnection = await getStateElapsedTime(page); + const secondsBeforeDisconnection = parseTimeString(timerBeforeDisconnection); + consoleMessages.length = 0; + await page.context().setOffline(true); + const isWebSocketDisconnected = await waitForWebSocketDisconnection(consoleMessages); + expect(isWebSocketDisconnected).toBe(true); + await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await verifyLoginMode(page, 'Desktop'); + consoleMessages.length = 0; + await page.context().setOffline(false); + const isWebSocketReconnected = await waitForWebSocketReconnection(consoleMessages); + expect(isWebSocketReconnected).toBe(true); + await verifyCurrentState(page, USER_STATES.AVAILABLE); + const timerAfterReconnection = await getStateElapsedTime(page); + const secondsAfterReconnection = parseTimeString(timerAfterReconnection); + expect(secondsAfterReconnection).toBeLessThan(secondsBeforeDisconnection); + await page.waitForTimeout(10000); + const agentStateChangeLog = consoleMessages.find( + (msg) => msg.includes('AGENT Status set successfully') || msg.includes('Agent state changed successfully') + ); + expect(agentStateChangeLog).toBeTruthy(); + await verifyLoginMode(page, 'Desktop'); + }); +}); From e1a46ac79b906b3fead6de955204b605b957ea22 Mon Sep 17 00:00:00 2001 From: PrayagGP Date: Wed, 2 Jul 2025 11:52:02 +0530 Subject: [PATCH 02/10] fix(cc-widgets): rename multi-session to mulit-login --- playwright/station-login-test.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/playwright/station-login-test.spec.ts b/playwright/station-login-test.spec.ts index 42bb175dd..9a303e906 100644 --- a/playwright/station-login-test.spec.ts +++ b/playwright/station-login-test.spec.ts @@ -200,7 +200,7 @@ test.describe('Station Login Tests - Dial Number Mode', () => { await verifyLoginMode(page, 'Dial Number'); }); - test('should support multi-session Dial Number login synchronization', async () => { + test('should support multi-login synchronization for Dial Number Mode ', async () => { await ensureUserStateVisible(page, LOGIN_MODE.DIAL_NUMBER); const multiSessionPage = await setupMultiLoginPage(context); await verifyLoginMode(multiSessionPage, 'Dial Number'); @@ -296,6 +296,7 @@ test.describe('Station Login Tests - Extension Mode', () => { await verifyLoginMode(page, 'Extension'); consoleMessages.length = 0; await page.context().setOffline(false); + await page.waitForTimeout(3000); const isWebSocketReconnected = await waitForWebSocketReconnection(consoleMessages); expect(isWebSocketReconnected).toBe(true); await verifyCurrentState(page, USER_STATES.MEETING); @@ -335,7 +336,7 @@ test.describe('Station Login Tests - Extension Mode', () => { await verifyLoginMode(page, 'Extension'); }); - test('should support multi-session Extension login synchronization', async () => { + test('should support multi-login synchronization for Extension Mode', async () => { await ensureUserStateVisible(page, LOGIN_MODE.EXTENSION); const multiSessionPage = await setupMultiLoginPage(context); await verifyLoginMode(multiSessionPage, 'Extension'); @@ -417,6 +418,7 @@ test.describe('Station Login Tests - Desktop Mode', () => { await verifyLoginMode(page, 'Desktop'); consoleMessages.length = 0; await page.context().setOffline(false); + await page.waitForTimeout(3000); const isWebSocketReconnected = await waitForWebSocketReconnection(consoleMessages); expect(isWebSocketReconnected).toBe(true); await verifyCurrentState(page, USER_STATES.MEETING); From c5df54413e268a0dfeff979c0b766d0b63c0a1da Mon Sep 17 00:00:00 2001 From: PrayagGP Date: Wed, 2 Jul 2025 12:19:41 +0530 Subject: [PATCH 03/10] fix(cc-widgets): add delays while handling socket reconnection --- playwright/station-login-test.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/playwright/station-login-test.spec.ts b/playwright/station-login-test.spec.ts index 9a303e906..20e2f889f 100644 --- a/playwright/station-login-test.spec.ts +++ b/playwright/station-login-test.spec.ts @@ -150,7 +150,7 @@ test.describe('Station Login Tests - Dial Number Mode', () => { await verifyCurrentState(page, USER_STATES.MEETING); const timerBeforeDisconnection = await getStateElapsedTime(page); const secondsBeforeDisconnection = parseTimeString(timerBeforeDisconnection); - await page.waitForTimeout(15000); + await page.waitForTimeout(3000); consoleMessages.length = 0; await page.context().setOffline(true); await page.waitForTimeout(3000); @@ -290,6 +290,7 @@ test.describe('Station Login Tests - Extension Mode', () => { await page.waitForTimeout(3000); consoleMessages.length = 0; await page.context().setOffline(true); + await page.waitForTimeout(3000); const isWebSocketDisconnected = await waitForWebSocketDisconnection(consoleMessages); expect(isWebSocketDisconnected).toBe(true); await expect(page.getByTestId('station-login-widget')).toBeVisible(); @@ -414,6 +415,7 @@ test.describe('Station Login Tests - Desktop Mode', () => { await page.context().setOffline(true); const isWebSocketDisconnected = await waitForWebSocketDisconnection(consoleMessages); expect(isWebSocketDisconnected).toBe(true); + await page.waitForTimeout(3000); await expect(page.getByTestId('station-login-widget')).toBeVisible(); await verifyLoginMode(page, 'Desktop'); consoleMessages.length = 0; From 4964bb46a11fa5ed25323bf78d3b8ea9cd27a2f7 Mon Sep 17 00:00:00 2001 From: PrayagGP Date: Wed, 2 Jul 2025 12:38:35 +0530 Subject: [PATCH 04/10] fix(cc-widgets): add timeouts for various expect statements --- playwright/station-login-test.spec.ts | 56 +++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/playwright/station-login-test.spec.ts b/playwright/station-login-test.spec.ts index 20e2f889f..8ede1e07c 100644 --- a/playwright/station-login-test.spec.ts +++ b/playwright/station-login-test.spec.ts @@ -96,7 +96,7 @@ test.describe('Station Login Tests - Dial Number Mode', () => { if (isLogoutButtonVisible) { await stationLogout(page); } - await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await expect(page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); }); test.afterAll(async () => { @@ -117,15 +117,15 @@ test.describe('Station Login Tests - Dial Number Mode', () => { }); test('should login with Dial Number mode and verify all fields are visible', async () => { - await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await expect(page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); const loginModeSelector = page.getByTestId('login-option-select'); - await expect(loginModeSelector).toBeVisible(); + await expect(loginModeSelector).toBeVisible({timeout: 2000}); const phoneNumberInput = page.getByTestId('dial-number-input'); - await expect(phoneNumberInput).toBeVisible(); + await expect(phoneNumberInput).toBeVisible({timeout: 2000}); const teamSelectionDropdown = page.getByTestId('teams-select-dropdown'); - await expect(teamSelectionDropdown).toBeVisible(); + await expect(teamSelectionDropdown).toBeVisible({timeout: 2000}); const saveAndContinueButton = page.getByTestId('login-button'); - await expect(saveAndContinueButton).toBeVisible(); + await expect(saveAndContinueButton).toBeVisible({timeout: 2000}); await expect(saveAndContinueButton).toContainText('Save & Continue'); await telephonyLogin(page, LOGIN_MODE.DIAL_NUMBER); await expect(page.getByTestId('state-select')).toBeVisible({timeout: LONG_WAIT}); @@ -135,13 +135,13 @@ test.describe('Station Login Tests - Dial Number Mode', () => { test('should handle page reload and maintain Dial Number login state', async () => { await ensureUserStateVisible(page, LOGIN_MODE.DIAL_NUMBER); await agentRelogin(page); - await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await expect(page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); await verifyLoginMode(page, 'Dial Number'); const dialNumber = process.env.PW_AGENT1_DIAL_NUMBER; if (dialNumber) { await expect(page.getByTestId('dial-number-input').locator('input')).toHaveValue(dialNumber); } - await expect(page.getByTestId('state-select')).toBeVisible(); + await expect(page.getByTestId('state-select')).toBeVisible({timeout: 2000}); }); test('should retain user state timer and switch to Meeting state after network disconnection with Dial Number mode', async () => { @@ -156,7 +156,7 @@ test.describe('Station Login Tests - Dial Number Mode', () => { await page.waitForTimeout(3000); const isWebSocketDisconnected = await waitForWebSocketDisconnection(consoleMessages); expect(isWebSocketDisconnected).toBe(true); - await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await expect(page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); await verifyLoginMode(page, 'Dial Number'); consoleMessages.length = 0; await page.context().setOffline(false); @@ -182,7 +182,7 @@ test.describe('Station Login Tests - Dial Number Mode', () => { await page.context().setOffline(true); const isWebSocketDisconnected = await waitForWebSocketDisconnection(consoleMessages); expect(isWebSocketDisconnected).toBe(true); - await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await expect(page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); await verifyLoginMode(page, 'Dial Number'); consoleMessages.length = 0; await page.context().setOffline(false); @@ -254,15 +254,15 @@ test.describe('Station Login Tests - Extension Mode', () => { }); test('should login with Extension mode and verify all fields are visible', async () => { - await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await expect(page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); const loginModeSelector = page.getByTestId('login-option-select'); - await expect(loginModeSelector).toBeVisible(); + await expect(loginModeSelector).toBeVisible({timeout: 2000}); const phoneNumberInput = page.getByTestId('dial-number-input'); - await expect(phoneNumberInput).toBeVisible(); + await expect(phoneNumberInput).toBeVisible({timeout: 2000}); const teamSelectionDropdown = page.getByTestId('teams-select-dropdown'); - await expect(teamSelectionDropdown).toBeVisible(); + await expect(teamSelectionDropdown).toBeVisible({timeout: 2000}); const saveAndContinueButton = page.getByTestId('login-button'); - await expect(saveAndContinueButton).toBeVisible(); + await expect(saveAndContinueButton).toBeVisible({timeout: 2000}); await expect(saveAndContinueButton).toContainText('Save & Continue'); await telephonyLogin(page, LOGIN_MODE.EXTENSION); await expect(page.getByTestId('state-select')).toBeVisible({timeout: LONG_WAIT}); @@ -272,13 +272,13 @@ test.describe('Station Login Tests - Extension Mode', () => { test('should handle page reload and maintain Extension login state', async () => { await ensureUserStateVisible(page, LOGIN_MODE.EXTENSION); await agentRelogin(page); - await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await expect(page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); await verifyLoginMode(page, 'Extension'); const extensionNumber = process.env.PW_AGENT1_EXTENSION; if (extensionNumber) { await expect(page.getByTestId('dial-number-input').locator('input')).toHaveValue(extensionNumber); } - await expect(page.getByTestId('state-select')).toBeVisible(); + await expect(page.getByTestId('state-select')).toBeVisible({timeout: 2000}); }); test('should retain user state timer and switch to Meeting state after network disconnection with Extension mode', async () => { @@ -293,7 +293,7 @@ test.describe('Station Login Tests - Extension Mode', () => { await page.waitForTimeout(3000); const isWebSocketDisconnected = await waitForWebSocketDisconnection(consoleMessages); expect(isWebSocketDisconnected).toBe(true); - await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await expect(page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); await verifyLoginMode(page, 'Extension'); consoleMessages.length = 0; await page.context().setOffline(false); @@ -319,7 +319,7 @@ test.describe('Station Login Tests - Extension Mode', () => { await page.context().setOffline(true); const isWebSocketDisconnected = await waitForWebSocketDisconnection(consoleMessages); expect(isWebSocketDisconnected).toBe(true); - await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await expect(page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); await verifyLoginMode(page, 'Extension'); consoleMessages.length = 0; await page.context().setOffline(false); @@ -368,7 +368,7 @@ test.describe('Station Login Tests - Desktop Mode', () => { if (isLogoutButtonVisible) { await stationLogout(page); } - await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await expect(page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); }); test.afterAll(async () => { @@ -383,13 +383,13 @@ test.describe('Station Login Tests - Desktop Mode', () => { }); test('should login with Desktop mode and verify all fields are visible', async () => { - await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await expect(page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); const loginModeSelector = page.getByTestId('login-option-select'); - await expect(loginModeSelector).toBeVisible(); + await expect(loginModeSelector).toBeVisible({timeout: 2000}); const teamSelectionDropdown = page.getByTestId('teams-select-dropdown'); - await expect(teamSelectionDropdown).toBeVisible(); + await expect(teamSelectionDropdown).toBeVisible({timeout: 2000}); const saveAndContinueButton = page.getByTestId('login-button'); - await expect(saveAndContinueButton).toBeVisible(); + await expect(saveAndContinueButton).toBeVisible({timeout: 2000}); await expect(saveAndContinueButton).toContainText('Save & Continue'); await telephonyLogin(page, LOGIN_MODE.DESKTOP); await expect(page.getByTestId('state-select')).toBeVisible({timeout: 3000}); @@ -399,9 +399,9 @@ test.describe('Station Login Tests - Desktop Mode', () => { test('should handle page reload and maintain Desktop login state', async () => { await ensureUserStateVisible(page, LOGIN_MODE.DESKTOP); await agentRelogin(page); - await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await expect(page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); await verifyLoginMode(page, 'Desktop'); - await expect(page.getByTestId('state-select')).toBeVisible(); + await expect(page.getByTestId('state-select')).toBeVisible({timeout: 2000}); }); test('should retain user state timer and switch to Meeting state after network disconnection with Desktop mode', async () => { @@ -416,7 +416,7 @@ test.describe('Station Login Tests - Desktop Mode', () => { const isWebSocketDisconnected = await waitForWebSocketDisconnection(consoleMessages); expect(isWebSocketDisconnected).toBe(true); await page.waitForTimeout(3000); - await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await expect(page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); await verifyLoginMode(page, 'Desktop'); consoleMessages.length = 0; await page.context().setOffline(false); @@ -442,7 +442,7 @@ test.describe('Station Login Tests - Desktop Mode', () => { await page.context().setOffline(true); const isWebSocketDisconnected = await waitForWebSocketDisconnection(consoleMessages); expect(isWebSocketDisconnected).toBe(true); - await expect(page.getByTestId('station-login-widget')).toBeVisible(); + await expect(page.getByTestId('station-login-widget')).toBeVisible({timeout: 2000}); await verifyLoginMode(page, 'Desktop'); consoleMessages.length = 0; await page.context().setOffline(false); From c8b509c753e42e1420786b9872b56b9165a28a88 Mon Sep 17 00:00:00 2001 From: PrayagGP Date: Wed, 2 Jul 2025 16:20:24 +0530 Subject: [PATCH 05/10] fix(cc-widgets): add a second attempt of intialising widgets --- playwright/Utils/initUtils.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/playwright/Utils/initUtils.ts b/playwright/Utils/initUtils.ts index 848b9e25e..9b9c6bcd0 100644 --- a/playwright/Utils/initUtils.ts +++ b/playwright/Utils/initUtils.ts @@ -124,8 +124,9 @@ export const disableMultiLogin = async (page: Page): Promise => { /** * 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 + * @description The station-login widget should be checked/enabled before using this function. + * If the widget is not visible after 50 seconds, retries once more with another 50-second timeout. + * @throws {Error} When station-login widget is not visible after two initialization attempts (100 seconds total) * @example * ```typescript * // Ensure station-login widget is checked first @@ -136,7 +137,19 @@ export const disableMultiLogin = async (page: Page): Promise => { export const initialiseWidgets = async (page: Page): Promise => { await page.getByTestId('samples:init-widgets-button').click(); - await page.getByTestId('station-login-widget').waitFor({state: 'visible', timeout: 50000}); + try { + await page.getByTestId('station-login-widget').waitFor({state: 'visible', timeout: 50000}); + } catch (error) { + // First attempt failed, try clicking init widgets button again + await page.getByTestId('samples:init-widgets-button').click(); + + try { + await page.getByTestId('station-login-widget').waitFor({state: 'visible', timeout: 50000}); + } catch (secondError) { + // Second attempt also failed, throw error + throw new Error('Station login widget failed to become visible after two initialization attempts (100 seconds total)'); + } + } }; /** From c34453daceb48bd09e76952a23d24c904440bcf6 Mon Sep 17 00:00:00 2001 From: PrayagGP Date: Wed, 2 Jul 2025 17:03:44 +0530 Subject: [PATCH 06/10] fix(cc-widgets): add descriptions for helper functions --- playwright/station-login-test.spec.ts | 65 +++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/playwright/station-login-test.spec.ts b/playwright/station-login-test.spec.ts index 8ede1e07c..06195e668 100644 --- a/playwright/station-login-test.spec.ts +++ b/playwright/station-login-test.spec.ts @@ -25,6 +25,17 @@ let page: Page; let context: BrowserContext; let consoleMessages: string[] = []; +/** + * 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) + * @returns Total number of seconds + * @example + * ```typescript + * parseTimeString("01:30"); // Returns 90 (1 minute 30 seconds) + * parseTimeString("00:45"); // Returns 45 (45 seconds) + * parseTimeString("10:00"); // Returns 600 (10 minutes) + * ``` + */ function parseTimeString(timeString: string): number { const parts = timeString.split(':'); const minutes = parseInt(parts[0], 10) || 0; @@ -32,6 +43,20 @@ function parseTimeString(timeString: string): number { return minutes * 60 + seconds; } +/** + * Waits for WebSocket disconnection by monitoring console messages for specific disconnection indicators + * @param consoleMessages - Array of console messages to monitor + * @param timeoutMs - Maximum time to wait for disconnection in milliseconds (default: 15000) + * @returns Promise - True if disconnection is detected, false if timeout is reached + * @description Monitors for network disconnection messages or WebSocket offline status changes + * @example + * ```typescript + * consoleMessages.length = 0; // Clear existing messages + * await page.context().setOffline(true); + * const isDisconnected = await waitForWebSocketDisconnection(consoleMessages); + * expect(isDisconnected).toBe(true); + * ``` + */ async function waitForWebSocketDisconnection(consoleMessages: string[], timeoutMs: number = 15000): Promise { const startTime = Date.now(); while (Date.now() - startTime < timeoutMs) { @@ -48,6 +73,20 @@ async function waitForWebSocketDisconnection(consoleMessages: string[], timeoutM return false; } +/** + * Waits for WebSocket reconnection by monitoring console messages for online status changes + * @param consoleMessages - Array of console messages to monitor + * @param timeoutMs - Maximum time to wait for reconnection in milliseconds (default: 15000) + * @returns Promise - True if reconnection is detected, false if timeout is reached + * @description Monitors for WebSocket online status change messages indicating successful reconnection + * @example + * ```typescript + * consoleMessages.length = 0; // Clear existing messages + * await page.context().setOffline(false); + * const isReconnected = await waitForWebSocketReconnection(consoleMessages); + * expect(isReconnected).toBe(true); + * ``` + */ async function waitForWebSocketReconnection(consoleMessages: string[], timeoutMs: number = 15000): Promise { const startTime = Date.now(); while (Date.now() - startTime < timeoutMs) { @@ -62,11 +101,37 @@ async function waitForWebSocketReconnection(consoleMessages: string[], timeoutMs return false; } +/** + * Verifies that the login mode selector displays the expected login mode + * @param page - The Playwright page object + * @param expectedMode - The expected login mode text to verify (e.g., 'Dial Number', 'Extension', 'Desktop') + * @description Checks the login option select element's trigger text to ensure it matches the expected mode + * @throws {Error} When the login mode doesn't match the expected value + * @example + * ```typescript + * await verifyLoginMode(page, LOGIN_MODE.DIAL_NUMBER); + * await verifyLoginMode(page, LOGIN_MODE.EXTENSION); + * await verifyLoginMode(page, LOGIN_MODE.DESKTOP); + * ``` + */ // Helper function to verify login mode async function verifyLoginMode(page: Page, expectedMode: string): Promise { await expect(page.getByTestId('login-option-select').locator('#select-base-triggerid')).toContainText(expectedMode); } +/** + * Ensures the user state widget is visible by checking its current state and logging in if necessary + * @param page - The Playwright page object + * @param loginMode - The login mode to use if login is required (from LOGIN_MODE constants) + * @description Checks if the state-select widget is visible; if not, performs telephony login and waits for it to appear + * @throws {Error} When telephony login fails or state widget doesn't become visible + * @example + * ```typescript + * await ensureUserStateVisible(page, LOGIN_MODE.DIAL_NUMBER); + * await ensureUserStateVisible(page, LOGIN_MODE.EXTENSION); + * await ensureUserStateVisible(page, LOGIN_MODE.DESKTOP); + * ``` + */ // Helper function to ensure user state widget is visible and login if needed async function ensureUserStateVisible(page: Page, loginMode: string): Promise { const isUserStateWidgetVisible = await page From 2511b0d148f93bebaa6b4e3ce400ddf6616fa764 Mon Sep 17 00:00:00 2001 From: PrayagGP Date: Wed, 2 Jul 2025 20:52:05 +0530 Subject: [PATCH 07/10] fix(cc-widgets): update test.afterAll for Desktop mode --- playwright/station-login-test.spec.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/playwright/station-login-test.spec.ts b/playwright/station-login-test.spec.ts index 06195e668..b03a7887a 100644 --- a/playwright/station-login-test.spec.ts +++ b/playwright/station-login-test.spec.ts @@ -438,7 +438,13 @@ test.describe('Station Login Tests - Desktop Mode', () => { test.afterAll(async () => { if (page) { - await stationLogout(page); + const isLogoutButtonVisible = await page + .getByTestId('samples:station-logout-button') + .isVisible() + .catch(() => false); + if (isLogoutButtonVisible) { + await stationLogout(page); + } await context.close(); } }); From 418efda06ceb057e7aa8238546f022acba875ad3 Mon Sep 17 00:00:00 2001 From: PrayagGP Date: Fri, 4 Jul 2025 20:50:58 +0530 Subject: [PATCH 08/10] fix(cc-widgets): remove unnecessary code --- playwright/global.setup.ts | 3 --- playwright/login-user-state.spec.ts | 2 -- playwright/station-login-test.spec.ts | 3 --- playwright/user-state-test.spec.ts | 3 --- 4 files changed, 11 deletions(-) diff --git a/playwright/global.setup.ts b/playwright/global.setup.ts index 01ad05265..30a29511c 100644 --- a/playwright/global.setup.ts +++ b/playwright/global.setup.ts @@ -2,9 +2,6 @@ import {test as setup} from '@playwright/test'; import {oauthLogin} from './Utils/initUtils'; const fs = require('fs'); const path = require('path'); -import dotenv from 'dotenv'; - -dotenv.config(); setup('OAuth', async ({browser}) => { const agentId = 'AGENT1'; // Configure which agent to set up diff --git a/playwright/login-user-state.spec.ts b/playwright/login-user-state.spec.ts index 8dd9960eb..8f0a38662 100644 --- a/playwright/login-user-state.spec.ts +++ b/playwright/login-user-state.spec.ts @@ -1,8 +1,6 @@ import {test, expect} from '@playwright/test'; import fs from 'fs'; -import dotenv from 'dotenv'; -dotenv.config(); test.describe('Login and User State tests', async () => { test('Login: should login using Extension login option', async ({page}) => { await page.goto('http://localhost:3000/'); diff --git a/playwright/station-login-test.spec.ts b/playwright/station-login-test.spec.ts index b03a7887a..ba2c40f77 100644 --- a/playwright/station-login-test.spec.ts +++ b/playwright/station-login-test.spec.ts @@ -17,9 +17,6 @@ import { checkCallbackSequence, } from './Utils/userStateUtils'; import {USER_STATES, THEME_COLORS, LOGIN_MODE, LONG_WAIT} from './constants'; -import dotenv from 'dotenv'; - -dotenv.config(); let page: Page; let context: BrowserContext; diff --git a/playwright/user-state-test.spec.ts b/playwright/user-state-test.spec.ts index 2ef25e5a0..3d4aecfdb 100644 --- a/playwright/user-state-test.spec.ts +++ b/playwright/user-state-test.spec.ts @@ -17,9 +17,6 @@ import { checkCallbackSequence, } from './Utils/userStateUtils'; import {USER_STATES, THEME_COLORS, LOGIN_MODE} from './constants'; -import dotenv from 'dotenv'; - -dotenv.config(); let page: Page; let context: BrowserContext; From 982d5a251c79211a750c671f7f87f300e3909e2f Mon Sep 17 00:00:00 2001 From: PrayagGP Date: Mon, 7 Jul 2025 10:51:38 +0530 Subject: [PATCH 09/10] fix(cc-widgets): shift functions from test file to util file --- playwright/Utils/helperUtils.ts | 75 ++++++++++++++++ playwright/Utils/stationLoginUtils.ts | 45 +++++++++- playwright/station-login-test.spec.ts | 122 +------------------------- 3 files changed, 120 insertions(+), 122 deletions(-) create mode 100644 playwright/Utils/helperUtils.ts diff --git a/playwright/Utils/helperUtils.ts b/playwright/Utils/helperUtils.ts new file mode 100644 index 000000000..3b4abb7fa --- /dev/null +++ b/playwright/Utils/helperUtils.ts @@ -0,0 +1,75 @@ +/** + * 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) + * @returns Total number of seconds + * @example + * ```typescript + * parseTimeString("01:30"); // Returns 90 (1 minute 30 seconds) + * parseTimeString("00:45"); // Returns 45 (45 seconds) + * parseTimeString("10:00"); // Returns 600 (10 minutes) + * ``` + */ +export function parseTimeString(timeString: string): number { + const parts = timeString.split(':'); + const minutes = parseInt(parts[0], 10) || 0; + const seconds = parseInt(parts[1], 10) || 0; + return minutes * 60 + seconds; +} + +/** + * Waits for WebSocket disconnection by monitoring console messages for specific disconnection indicators + * @param consoleMessages - Array of console messages to monitor + * @param timeoutMs - Maximum time to wait for disconnection in milliseconds (default: 15000) + * @returns Promise - True if disconnection is detected, false if timeout is reached + * @description Monitors for network disconnection messages or WebSocket offline status changes + * @example + * ```typescript + * consoleMessages.length = 0; // Clear existing messages + * await page.context().setOffline(true); + * const isDisconnected = await waitForWebSocketDisconnection(consoleMessages); + * expect(isDisconnected).toBe(true); + * ``` + */ +export async function waitForWebSocketDisconnection(consoleMessages: string[], timeoutMs: number = 15000): Promise { + const startTime = Date.now(); + while (Date.now() - startTime < timeoutMs) { + const webSocketDisconnectLog = consoleMessages.find( + (msg) => + msg.includes('Failed to load resource: net::ERR_INTERNET_DISCONNECTED') || + msg.includes('[WebSocketStatus] event=checkOnlineStatus | online status= false') + ); + if (webSocketDisconnectLog) { + return true; + } + await new Promise((resolve) => setTimeout(resolve, 100)); + } + return false; +} + +/** + * Waits for WebSocket reconnection by monitoring console messages for online status changes + * @param consoleMessages - Array of console messages to monitor + * @param timeoutMs - Maximum time to wait for reconnection in milliseconds (default: 15000) + * @returns Promise - True if reconnection is detected, false if timeout is reached + * @description Monitors for WebSocket online status change messages indicating successful reconnection + * @example + * ```typescript + * consoleMessages.length = 0; // Clear existing messages + * await page.context().setOffline(false); + * const isReconnected = await waitForWebSocketReconnection(consoleMessages); + * expect(isReconnected).toBe(true); + * ``` + */ +export async function waitForWebSocketReconnection(consoleMessages: string[], timeoutMs: number = 15000): Promise { + const startTime = Date.now(); + while (Date.now() - startTime < timeoutMs) { + const webSocketReconnectLog = consoleMessages.find((msg) => + msg.includes('[WebSocketStatus] event=checkOnlineStatus | online status= true') + ); + if (webSocketReconnectLog) { + return true; + } + await new Promise((resolve) => setTimeout(resolve, 100)); + } + return false; +} \ No newline at end of file diff --git a/playwright/Utils/stationLoginUtils.ts b/playwright/Utils/stationLoginUtils.ts index c80a70a3d..67c2413d2 100644 --- a/playwright/Utils/stationLoginUtils.ts +++ b/playwright/Utils/stationLoginUtils.ts @@ -1,6 +1,6 @@ import {Page, expect} from '@playwright/test'; import dotenv from 'dotenv'; -import {LOGIN_MODE} from '../constants'; +import {LOGIN_MODE, LONG_WAIT} from '../constants'; dotenv.config(); @@ -153,4 +153,45 @@ export const telephonyLogin = async (page: Page, mode: string, number?: string): } else { throw new Error(`Unsupported login mode: ${mode}. Use one of: ${Object.values(LOGIN_MODE).join(', ')}`); } -}; +} + +/** + * Verifies that the login mode selector displays the expected login mode + * @param page - The Playwright page object + * @param expectedMode - The expected login mode text to verify (e.g., 'Dial Number', 'Extension', 'Desktop') + * @description Checks the login option select element's trigger text to ensure it matches the expected mode + * @throws {Error} When the login mode doesn't match the expected value + * @example + * ```typescript + * await verifyLoginMode(page, LOGIN_MODE.DIAL_NUMBER); + * await verifyLoginMode(page, LOGIN_MODE.EXTENSION); + * await verifyLoginMode(page, LOGIN_MODE.DESKTOP); + * ``` + */ +export async function verifyLoginMode(page: Page, expectedMode: string): Promise { + await expect(page.getByTestId('login-option-select').locator('#select-base-triggerid')).toContainText(expectedMode); +} + +/** + * Ensures the user state widget is visible by checking its current state and logging in if necessary + * @param page - The Playwright page object + * @param loginMode - The login mode to use if login is required (from LOGIN_MODE constants) + * @description Checks if the state-select widget is visible; if not, performs telephony login and waits for it to appear + * @throws {Error} When telephony login fails or state widget doesn't become visible + * @example + * ```typescript + * await ensureUserStateVisible(page, LOGIN_MODE.DIAL_NUMBER); + * await ensureUserStateVisible(page, LOGIN_MODE.EXTENSION); + * await ensureUserStateVisible(page, LOGIN_MODE.DESKTOP); + * ``` + */ +export async function ensureUserStateVisible(page: Page, loginMode: string): Promise { + const isUserStateWidgetVisible = await page + .getByTestId('state-select') + .isVisible() + .catch(() => false); + if (!isUserStateWidgetVisible) { + await telephonyLogin(page, loginMode); + await expect(page.getByTestId('state-select')).toBeVisible({timeout: LONG_WAIT}); + } +} diff --git a/playwright/station-login-test.spec.ts b/playwright/station-login-test.spec.ts index ba2c40f77..2810ef4cc 100644 --- a/playwright/station-login-test.spec.ts +++ b/playwright/station-login-test.spec.ts @@ -7,7 +7,7 @@ import { setupMultiLoginPage, loginViaAccessToken, } from './Utils/initUtils'; -import {stationLogout, telephonyLogin} from './Utils/stationLoginUtils'; +import {stationLogout, telephonyLogin, verifyLoginMode, ensureUserStateVisible} from './Utils/stationLoginUtils'; import { getCurrentState, changeUserState, @@ -16,131 +16,13 @@ import { validateConsoleStateChange, checkCallbackSequence, } from './Utils/userStateUtils'; +import {parseTimeString, waitForWebSocketDisconnection, waitForWebSocketReconnection} from './Utils/helperUtils'; import {USER_STATES, THEME_COLORS, LOGIN_MODE, LONG_WAIT} from './constants'; let page: Page; let context: BrowserContext; let consoleMessages: string[] = []; -/** - * 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) - * @returns Total number of seconds - * @example - * ```typescript - * parseTimeString("01:30"); // Returns 90 (1 minute 30 seconds) - * parseTimeString("00:45"); // Returns 45 (45 seconds) - * parseTimeString("10:00"); // Returns 600 (10 minutes) - * ``` - */ -function parseTimeString(timeString: string): number { - const parts = timeString.split(':'); - const minutes = parseInt(parts[0], 10) || 0; - const seconds = parseInt(parts[1], 10) || 0; - return minutes * 60 + seconds; -} - -/** - * Waits for WebSocket disconnection by monitoring console messages for specific disconnection indicators - * @param consoleMessages - Array of console messages to monitor - * @param timeoutMs - Maximum time to wait for disconnection in milliseconds (default: 15000) - * @returns Promise - True if disconnection is detected, false if timeout is reached - * @description Monitors for network disconnection messages or WebSocket offline status changes - * @example - * ```typescript - * consoleMessages.length = 0; // Clear existing messages - * await page.context().setOffline(true); - * const isDisconnected = await waitForWebSocketDisconnection(consoleMessages); - * expect(isDisconnected).toBe(true); - * ``` - */ -async function waitForWebSocketDisconnection(consoleMessages: string[], timeoutMs: number = 15000): Promise { - const startTime = Date.now(); - while (Date.now() - startTime < timeoutMs) { - const webSocketDisconnectLog = consoleMessages.find( - (msg) => - msg.includes('Failed to load resource: net::ERR_INTERNET_DISCONNECTED') || - msg.includes('[WebSocketStatus] event=checkOnlineStatus | online status= false') - ); - if (webSocketDisconnectLog) { - return true; - } - await new Promise((resolve) => setTimeout(resolve, 100)); - } - return false; -} - -/** - * Waits for WebSocket reconnection by monitoring console messages for online status changes - * @param consoleMessages - Array of console messages to monitor - * @param timeoutMs - Maximum time to wait for reconnection in milliseconds (default: 15000) - * @returns Promise - True if reconnection is detected, false if timeout is reached - * @description Monitors for WebSocket online status change messages indicating successful reconnection - * @example - * ```typescript - * consoleMessages.length = 0; // Clear existing messages - * await page.context().setOffline(false); - * const isReconnected = await waitForWebSocketReconnection(consoleMessages); - * expect(isReconnected).toBe(true); - * ``` - */ -async function waitForWebSocketReconnection(consoleMessages: string[], timeoutMs: number = 15000): Promise { - const startTime = Date.now(); - while (Date.now() - startTime < timeoutMs) { - const webSocketReconnectLog = consoleMessages.find((msg) => - msg.includes('[WebSocketStatus] event=checkOnlineStatus | online status= true') - ); - if (webSocketReconnectLog) { - return true; - } - await new Promise((resolve) => setTimeout(resolve, 100)); - } - return false; -} - -/** - * Verifies that the login mode selector displays the expected login mode - * @param page - The Playwright page object - * @param expectedMode - The expected login mode text to verify (e.g., 'Dial Number', 'Extension', 'Desktop') - * @description Checks the login option select element's trigger text to ensure it matches the expected mode - * @throws {Error} When the login mode doesn't match the expected value - * @example - * ```typescript - * await verifyLoginMode(page, LOGIN_MODE.DIAL_NUMBER); - * await verifyLoginMode(page, LOGIN_MODE.EXTENSION); - * await verifyLoginMode(page, LOGIN_MODE.DESKTOP); - * ``` - */ -// Helper function to verify login mode -async function verifyLoginMode(page: Page, expectedMode: string): Promise { - await expect(page.getByTestId('login-option-select').locator('#select-base-triggerid')).toContainText(expectedMode); -} - -/** - * Ensures the user state widget is visible by checking its current state and logging in if necessary - * @param page - The Playwright page object - * @param loginMode - The login mode to use if login is required (from LOGIN_MODE constants) - * @description Checks if the state-select widget is visible; if not, performs telephony login and waits for it to appear - * @throws {Error} When telephony login fails or state widget doesn't become visible - * @example - * ```typescript - * await ensureUserStateVisible(page, LOGIN_MODE.DIAL_NUMBER); - * await ensureUserStateVisible(page, LOGIN_MODE.EXTENSION); - * await ensureUserStateVisible(page, LOGIN_MODE.DESKTOP); - * ``` - */ -// Helper function to ensure user state widget is visible and login if needed -async function ensureUserStateVisible(page: Page, loginMode: string): Promise { - const isUserStateWidgetVisible = await page - .getByTestId('state-select') - .isVisible() - .catch(() => false); - if (!isUserStateWidgetVisible) { - await telephonyLogin(page, loginMode); - await expect(page.getByTestId('state-select')).toBeVisible({timeout: LONG_WAIT}); - } -} - test.describe('Station Login Tests - Dial Number Mode', () => { test.beforeAll(async ({browser}) => { context = await browser.newContext(); From 079bebc25dd0f6d5899fc9574b0a3fa54b1a0d44 Mon Sep 17 00:00:00 2001 From: PrayagGP Date: Fri, 11 Jul 2025 19:22:14 +0530 Subject: [PATCH 10/10] fix(cc-widgets): update timeout for e2e_test_cc_widgets --- .github/workflows/pull-request.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 1c04591a7..4b4d8dc26 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -19,10 +19,10 @@ env: PW_PASSWORD: ${{ secrets.PW_PASSWORD }} PW_AGENT1_EXTENSION_NUMBER: ${{ secrets.PW_AGENT1_EXTENSION_NUMBER }} PW_DIAL_NUMBER: ${{ secrets.PW_DIAL_NUMBER }} - PW_AGENT2_USERNAME: ${{ secrets.PW_AGENT2_USERNAME }} + PW_AGENT2_USERNAME: ${{ secrets.PW_AGENT2_USERNAME }} PW_AGENT2_EXTENSION_NUMBER: ${{ secrets.PW_AGENT2_EXTENSION_NUMBER }} - PW_SENDER_EMAIL : ${{ secrets.PW_SENDER_EMAIL }} - PW_EMAIL_ENTRY_POINT : ${{ secrets.PW_EMAIL_ENTRY_POINT }} + PW_SENDER_EMAIL: ${{ secrets.PW_SENDER_EMAIL }} + PW_EMAIL_ENTRY_POINT: ${{ secrets.PW_EMAIL_ENTRY_POINT }} PW_APP_PASSWORD: ${{ secrets.PW_APP_PASSWORD }} jobs: @@ -181,7 +181,7 @@ jobs: run: yarn workspace @webex/widgets run test:e2e e2e_test_cc_widgets: - timeout-minutes: 15 + timeout-minutes: 50 runs-on: ubuntu-latest needs: [install, build] if: contains(toJson(github.event.pull_request.labels), 'run_e2e')