-
Notifications
You must be signed in to change notification settings - Fork 396
test(calling): add Playwright E2E base setup and SDK initialization tests #4790
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
eigengravy
merged 13 commits into
webex:next
from
eigengravy:calling-sdk-e2e-tests-sdk-init
Apr 8, 2026
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
c03d680
test(calling): add Playwright E2E base setup and SDK initialization t…
eigengravy 28d3cef
test(calling): verify callingClient object exists after SDK initializ…
eigengravy 002d1c2
refactor(calling): restructure Playwright tests into calling subdirec…
eigengravy 99e8011
refactor(calling): address PR review comments
eigengravy e38d9e1
refactor(calling): address additional PR review comments
eigengravy bc5a587
refactor(calling): align test timeouts with SDK constants + 5s buffer
eigengravy cad55b4
chore: update yarn.lock
eigengravy 7352d3a
fix(calling): add test:e2e:calling script and extract domain constant
eigengravy d286a2a
refactor(calling): add multi-browser support and restructure constants
eigengravy e457bdc
feat(calling): add region and country discovery helpers and selectors
eigengravy 7f35a0a
chore(calling): add TODO for verifying active clients based on init c…
eigengravy f3f251e
fix: address all comments
eigengravy c4d39ac
fix: remove unwanted changes
eigengravy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import {defineConfig, devices} from '@playwright/test'; | ||
| import dotenv from 'dotenv'; | ||
| import path from 'path'; | ||
|
|
||
| // .env lives at repo root | ||
| dotenv.config({path: path.resolve(__dirname, '../../.env')}); | ||
|
|
||
| const BASE_URL = process.env.PW_BASE_URL || 'https://localhost:8000'; | ||
|
eigengravy marked this conversation as resolved.
|
||
|
|
||
| // Browser selection via PW_BROWSER env var: 'chrome' (default), 'firefox', 'edge', 'safari' | ||
| const PW_BROWSER = process.env.PW_BROWSER || 'chrome'; | ||
|
|
||
| const chromiumArgs = [ | ||
| '--disable-site-isolation-trials', // Allow cross-origin iframes in the same process | ||
| '--disable-web-security', // Bypass CORS for local dev server | ||
| '--no-sandbox', // Required for CI containers without root | ||
| '--disable-features=WebRtcHideLocalIpsWithMdns', // Expose real local IPs for WebRTC ICE candidates | ||
| '--allow-file-access-from-files', // Allow file:// protocol access | ||
| '--use-fake-ui-for-media-stream', // Auto-grant camera/mic permissions without prompt | ||
| '--use-fake-device-for-media-stream', // Use synthetic audio/video instead of real hardware | ||
| '--disable-extensions', // Prevent extensions from interfering with tests | ||
| '--disable-plugins', // Prevent plugins from interfering with tests | ||
| '--ignore-certificate-errors', // Accept self-signed certs from local dev server | ||
| ...(process.env.CI ? [] : ['--auto-open-devtools-for-tabs']), // Open DevTools only in local runs | ||
| ]; | ||
|
|
||
| const browserOptions: Record<string, object> = { | ||
| chrome: { | ||
| ...devices['Desktop Chrome'], | ||
| channel: 'chrome' as const, | ||
| launchOptions: {args: chromiumArgs}, | ||
| }, | ||
| edge: { | ||
| ...devices['Desktop Edge'], | ||
| channel: 'msedge' as const, | ||
| launchOptions: {args: chromiumArgs}, | ||
| }, | ||
| firefox: { | ||
| ...devices['Desktop Firefox'], | ||
| launchOptions: { | ||
| firefoxUserPrefs: { | ||
| 'media.navigator.streams.fake': true, // Use fake media devices | ||
| 'media.navigator.permission.disabled': true, // Auto-grant media permissions | ||
| }, | ||
| }, | ||
| }, | ||
| safari: { | ||
| ...devices['Desktop Safari'], | ||
| }, | ||
| }; | ||
|
|
||
| export default defineConfig({ | ||
| testDir: './playwright', | ||
| timeout: 120000, | ||
| webServer: { | ||
| command: 'yarn samples:serve', | ||
| cwd: path.resolve(__dirname, '../..'), | ||
| url: BASE_URL, | ||
| ignoreHTTPSErrors: true, | ||
| reuseExistingServer: true, | ||
| stdout: 'ignore', | ||
| stderr: 'pipe', | ||
| }, | ||
| retries: 3, | ||
| fullyParallel: false, | ||
| workers: 6, | ||
| reporter: 'html', | ||
| use: { | ||
| baseURL: BASE_URL, | ||
| ignoreHTTPSErrors: true, | ||
| trace: 'retain-on-failure', | ||
| }, | ||
| projects: [ | ||
| // Production | ||
| { | ||
| name: 'Calling: OAuth Setup - PROD', | ||
| testDir: './playwright/utils', | ||
| testMatch: /oauth\.setup\.ts/, | ||
| }, | ||
| { | ||
| name: 'Calling SDK E2E - PROD', | ||
| dependencies: ['Calling: OAuth Setup - PROD'], | ||
| testDir: './playwright/tests', | ||
| use: browserOptions[PW_BROWSER], | ||
| }, | ||
| // Integration | ||
| { | ||
| name: 'Calling: OAuth Setup - INT', | ||
| testDir: './playwright/utils', | ||
| testMatch: /oauth\.setup\.ts/, | ||
| use: {testEnv: 'int'} as any, | ||
| }, | ||
| { | ||
| name: 'Calling SDK E2E - INT', | ||
| dependencies: ['Calling: OAuth Setup - INT'], | ||
| testDir: './playwright/tests', | ||
| use: {...browserOptions[PW_BROWSER], testEnv: 'int'} as any, | ||
| }, | ||
| ], | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import path from 'path'; | ||
|
|
||
| export type ServiceIndicator = 'calling' | 'contactcenter' | 'guestcalling'; | ||
|
|
||
| // App paths | ||
| export const SAMPLE_APP_PATH = '/samples/calling/'; | ||
| export const CC_SERVICE_DOMAIN = 'rtw.prod-us1.rtmsprod.net'; | ||
|
|
||
| // Discovery | ||
| export const REGION = 'US-EAST'; | ||
| export const COUNTRY = 'US'; | ||
|
|
||
| // OAuth | ||
| export const ENV_PATH = path.resolve(__dirname, '../../../../.env'); | ||
| export const DEVELOPER_PORTAL_GETTING_STARTED_URL = | ||
| 'https://developer.webex.com/docs/getting-started'; | ||
| export const DEVELOPER_PORTAL_INT_GETTING_STARTED_URL = | ||
| 'https://developer-portal-intb.ciscospark.com/docs/getting-started'; | ||
|
|
||
| export {CALLING_SELECTORS} from './selectors'; | ||
| export {AWAIT_TIMEOUT, SDK_INIT_TIMEOUT, REGISTRATION_TIMEOUT, OPERATION_TIMEOUT} from './timeouts'; |
|
Shreyas281299 marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| // Element CALLING_SELECTORS (from the calling sample app) | ||
| export const CALLING_SELECTORS = { | ||
| // Authentication | ||
| ACCESS_TOKEN_INPUT: '#access-token', | ||
| INITIALIZE_CALLING_BTN: '#access-token-save', | ||
| AUTH_STATUS: '#access-token-status', | ||
| SERVICE_INDICATOR: '#ServiceIndicator', | ||
| SERVICE_DOMAIN: '#ServiceDomain', | ||
| REGION_INPUT: '#region', | ||
| COUNTRY_INPUT: '#country', | ||
| FEDRAMP_CHECKBOX: '#fedramp', | ||
| ENABLE_PRODUCTION_BTN: '#enableProduction', | ||
|
|
||
| // Registration | ||
| REGISTER_BTN: '#registration-register', | ||
| UNREGISTER_BTN: '#registration-unregister', | ||
| REGISTRATION_STATUS: '#registration-status', | ||
|
|
||
| // Call Controls | ||
| DESTINATION_INPUT: '#destination', | ||
| MAKE_CALL_BTN: '#create-call-action', | ||
| END_CALL_BTN: '#end-call', | ||
| ANSWER_BTN: '#answer', | ||
| MUTE_BTN: '#mute_button', | ||
| HOLD_BTN: '#hold_button', | ||
| DTMF_INPUT: '#dtmf_digit', | ||
| SEND_DIGIT_BTN: '#send-digit', | ||
|
|
||
| // Transfer | ||
| TRANSFER_TARGET_INPUT: '#transfer_target', | ||
| TRANSFER_OPTIONS: '#transfer-options', | ||
| TRANSFER_BTN: '#transfer', | ||
| END_SECOND_CALL_BTN: '#end-second', | ||
| TRANSFER_STATUS: '#transfer-call', | ||
|
|
||
| // Media | ||
| GET_MEDIA_STREAMS_BTN: '#sd-get-media-streams', | ||
| LOCAL_AUDIO: '#local-audio', | ||
| REMOTE_AUDIO: '#remote-audio', | ||
|
|
||
| // Guest Calling | ||
| GUEST_CONTAINER: '#guest-container', | ||
| JWT_TOKEN_FOR_DEST: '#jwt-token-for-dest', | ||
| GUEST_NAME: '#guest-name', | ||
| GENERATE_GUEST_TOKEN_BTN: '#generate-guest-token', | ||
|
|
||
|
eigengravy marked this conversation as resolved.
|
||
| // Call info | ||
| CALL_OBJECT: '#call-object', | ||
| INCOMING_CALL: '#incoming-call', | ||
| CALL_QUALITY_METRICS: '#call-quality-metrics', | ||
|
|
||
| END_BTN: '#end', | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| // Timeouts — SDK timeout + 5s buffer for network/UI overhead | ||
| export const AWAIT_TIMEOUT = 10000; // General UI interactions | ||
| export const SDK_INIT_TIMEOUT = 65000; // RETRY_TIMER_UPPER_LIMIT (60s) + 5s | ||
| export const REGISTRATION_TIMEOUT = 35000; // BASE_REG_RETRY_TIMER_VAL_IN_SEC (30s) + 5s | ||
| export const OPERATION_TIMEOUT = 15000; // SUPPLEMENTARY_SERVICES_TIMEOUT (10s) + 5s |
111 changes: 111 additions & 0 deletions
111
packages/calling/playwright/tests/01-sdk-initialization.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| import {test, expect} from '@playwright/test'; | ||
| import { | ||
| navigateToCallingApp, | ||
| initializeCallingSDK, | ||
| verifySDKInitialized, | ||
| setServiceIndicator, | ||
| setServiceDomain, | ||
| setEnvironmentToInt, | ||
| setRegion, | ||
| setCountry, | ||
| waitForMobiusDiscoveryRequest, | ||
| verifyMobiusServersDiscovered, | ||
| } from '../utils/setup'; | ||
| import { | ||
| CALLING_SELECTORS, | ||
| SDK_INIT_TIMEOUT, | ||
| AWAIT_TIMEOUT, | ||
| CC_SERVICE_DOMAIN, | ||
| REGION, | ||
| COUNTRY, | ||
| } from '../constants'; | ||
|
|
||
| const getToken = (envVar: string): string => { | ||
| const token = process.env[envVar]; | ||
| if (!token) { | ||
| throw new Error(`${envVar} not set. Run OAuth setup first.`); | ||
| } | ||
|
|
||
| return token; | ||
| }; | ||
|
|
||
| test.describe('SDK Initialization', () => { | ||
| test.describe.configure({mode: 'parallel'}); | ||
|
|
||
| test('Normal Calling - init with calling service indicator', async ({page}, testInfo) => { | ||
| const isInt = (testInfo.project.use as any).testEnv === 'int'; | ||
| const envPrefix = isInt ? '_INT' : ''; | ||
|
|
||
| await navigateToCallingApp(page); | ||
| if (isInt) await setEnvironmentToInt(page); | ||
| await setServiceIndicator(page, 'calling'); | ||
|
|
||
| await initializeCallingSDK(page, getToken(`CALLER${envPrefix}_ACCESS_TOKEN`)); | ||
| await verifySDKInitialized(page); | ||
| }); | ||
|
|
||
| test('Contact Center - init with contactcenter service indicator', async ({page}, testInfo) => { | ||
| const isInt = (testInfo.project.use as any).testEnv === 'int'; | ||
| const envPrefix = isInt ? '_INT' : ''; | ||
|
|
||
| await navigateToCallingApp(page); | ||
| if (isInt) await setEnvironmentToInt(page); | ||
| await setServiceIndicator(page, 'contactcenter'); | ||
| await setServiceDomain(page, CC_SERVICE_DOMAIN); | ||
|
|
||
| await initializeCallingSDK(page, getToken(`CALLER${envPrefix}_ACCESS_TOKEN`)); | ||
| await verifySDKInitialized(page); | ||
| }); | ||
|
|
||
| test('Guest Calling - generate guest token and init', async ({page}, testInfo) => { | ||
| const isInt = (testInfo.project.use as any).testEnv === 'int'; | ||
| test.skip(isInt, 'Guest calling is prod-only'); | ||
|
|
||
| await navigateToCallingApp(page); | ||
| await setServiceIndicator(page, 'guestcalling'); | ||
|
|
||
| // Guest container should become visible after selecting guestcalling | ||
| await expect(page.locator(CALLING_SELECTORS.GUEST_CONTAINER)).toBeVisible({ | ||
| timeout: AWAIT_TIMEOUT, | ||
| }); | ||
|
|
||
| // Click "Generate Guest Token [Prod only]" - fetches JWT from AWS Lambda | ||
| await page.locator(CALLING_SELECTORS.GENERATE_GUEST_TOKEN_BTN).click({timeout: AWAIT_TIMEOUT}); | ||
|
|
||
| // Wait for the token to be populated in the access token field | ||
| await expect(page.locator(CALLING_SELECTORS.ACCESS_TOKEN_INPUT)).not.toHaveValue('', { | ||
| timeout: SDK_INIT_TIMEOUT, | ||
| }); | ||
|
|
||
| // Click "Initialize Calling" to init with the guest token | ||
| await page.locator(CALLING_SELECTORS.INITIALIZE_CALLING_BTN).click({timeout: AWAIT_TIMEOUT}); | ||
| await verifySDKInitialized(page); | ||
| }); | ||
|
|
||
| test('Normal Calling - init with explicit region and country', async ({page}, testInfo) => { | ||
| const isInt = (testInfo.project.use as any).testEnv === 'int'; | ||
| const envPrefix = isInt ? '_INT' : ''; | ||
|
|
||
| await navigateToCallingApp(page); | ||
| if (isInt) await setEnvironmentToInt(page); | ||
| await setServiceIndicator(page, 'calling'); | ||
| await setCountry(page, COUNTRY); | ||
| await setRegion(page, REGION); | ||
|
|
||
| const mobiusDiscoveryRequest = waitForMobiusDiscoveryRequest(page, { | ||
| region: REGION, | ||
| country: COUNTRY, | ||
| }); | ||
|
|
||
| await initializeCallingSDK(page, getToken(`CALLEE${envPrefix}_ACCESS_TOKEN`)); | ||
| await verifySDKInitialized(page); | ||
|
|
||
| await expect(mobiusDiscoveryRequest).resolves.toContain( | ||
| `regionCode=${encodeURIComponent(REGION)}` | ||
| ); | ||
| await expect(mobiusDiscoveryRequest).resolves.toContain( | ||
| `countryCode=${encodeURIComponent(COUNTRY)}` | ||
| ); | ||
| await verifyMobiusServersDiscovered(page); | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.