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}`);