From 6647de8ae7c226d50fdf21d79679dbc7652472c2 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Tue, 17 Feb 2026 20:39:56 +0100 Subject: [PATCH] fix: use exact data-tid=toggle-video selector for camera toggle on pre-join screen Co-authored-by: Cursor --- src/bot/orchestrator.ts | 87 +++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 46 deletions(-) diff --git a/src/bot/orchestrator.ts b/src/bot/orchestrator.ts index b6e3711..4cb3813 100644 --- a/src/bot/orchestrator.ts +++ b/src/bot/orchestrator.ts @@ -315,67 +315,62 @@ export class BotOrchestrator { /** * Ensure the camera is turned on in the pre-join screen. * When camera is on, Teams shows the profile/background image. + * + * Teams pre-join uses a fui-Switch input: + * + * - checked present = camera ON (data-cid="toggle-video-true", title="Turn camera off") + * - checked absent = camera OFF (data-cid="toggle-video-false", title="Turn camera on") */ private async _ensureCameraOn(): Promise { try { - // Try multiple selectors for the camera toggle - const cameraSelectors = [ - 'button[data-tid="toggle-video"]', - 'button[aria-label*="camera" i]', - 'button[aria-label*="Camera" i]', - 'button[aria-label*="Video" i]', - '#video-toggle-button', - '[data-tid="prejoin-camera-toggle"]', - ]; + // Primary: the actual switch input (fui-Switch) + let cameraToggle = await this._page!.$('input[data-tid="toggle-video"]'); - let cameraBtn = null; - let matchedSelector = ''; - for (const sel of cameraSelectors) { - cameraBtn = await this._page!.$(sel); - if (cameraBtn) { - matchedSelector = sel; - break; + if (!cameraToggle) { + this._logger.info('Primary camera selector not found, trying fallbacks...'); + const fallbacks = [ + '[data-tid="toggle-video"]', + 'input[role="switch"][title*="camera" i]', + 'input[role="switch"][title*="Camera" i]', + 'input[role="switch"][title*="Video" i]', + ]; + for (const sel of fallbacks) { + cameraToggle = await this._page!.$(sel); + if (cameraToggle) { + this._logger.info(`Camera toggle found via fallback: ${sel}`); + break; + } } } - if (!cameraBtn) { - this._logger.warn('Camera toggle button not found with any selector'); + if (!cameraToggle) { + this._logger.warn('Camera toggle not found on pre-join screen'); return; } - // Log button details for debugging - const btnInfo = await cameraBtn.evaluate((el) => { - return { - ariaLabel: el.getAttribute('aria-label') || '', - ariaPressed: el.getAttribute('aria-pressed'), - ariaChecked: el.getAttribute('aria-checked'), - dataTid: el.getAttribute('data-tid') || '', - className: el.className?.substring(0, 80) || '', - title: el.getAttribute('title') || '', - innerText: el.innerText?.trim()?.substring(0, 30) || '', - }; - }); - this._logger.info(`Camera button found: ${matchedSelector} | aria-label="${btnInfo.ariaLabel}" | aria-pressed=${btnInfo.ariaPressed} | title="${btnInfo.title}"`); + // Read current state + const state = await cameraToggle.evaluate((el: HTMLInputElement) => ({ + checked: el.checked, + dataCid: el.getAttribute('data-cid') || '', + title: el.getAttribute('title') || '', + })); - // Determine if camera is off — click to turn it ON - // In Teams pre-join: aria-pressed="false" means camera is OFF - const shouldClick = btnInfo.ariaPressed === 'false' || btnInfo.ariaChecked === 'false'; + this._logger.info(`Camera state: checked=${state.checked}, data-cid="${state.dataCid}", title="${state.title}"`); - if (shouldClick) { - await cameraBtn.click(); - this._logger.info('Camera toggled ON (was off)'); + if (!state.checked) { + // Camera is OFF — click to turn ON + await cameraToggle.click(); + this._logger.info('Camera toggled ON'); await this._page!.waitForTimeout(2000); - // Verify the toggle worked - const afterState = await cameraBtn.evaluate((el) => el.getAttribute('aria-pressed')); - this._logger.info(`Camera state after toggle: aria-pressed=${afterState}`); - } else if (btnInfo.ariaPressed === null && btnInfo.ariaChecked === null) { - // No aria state — click anyway to turn camera on (safe default) - await cameraBtn.click(); - this._logger.info('Camera clicked (no aria state detected, assuming toggle)'); - await this._page!.waitForTimeout(2000); + // Verify + const afterState = await cameraToggle.evaluate((el: HTMLInputElement) => ({ + checked: el.checked, + dataCid: el.getAttribute('data-cid') || '', + })); + this._logger.info(`Camera after toggle: checked=${afterState.checked}, data-cid="${afterState.dataCid}"`); } else { - this._logger.info('Camera already ON (aria-pressed=true)'); + this._logger.info('Camera already ON'); } } catch (err) { this._logger.warn(`Could not toggle camera: ${err}`);