From bb304f3db186e2d122c1cc6b86532edce6b8da56 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Wed, 18 Feb 2026 22:10:27 +0100
Subject: [PATCH] fix: add suppressPrompt/msLaunch params to auth join URL, add
joinOnWeb selector
Co-authored-by: Cursor
---
src/bot/orchestrator.ts | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/src/bot/orchestrator.ts b/src/bot/orchestrator.ts
index 324a7fb..8749100 100644
--- a/src/bot/orchestrator.ts
+++ b/src/bot/orchestrator.ts
@@ -15,7 +15,7 @@ import { AudioCaptureProcedure } from './audioCaptureProcedure';
import { ChatProcedure, ChatMessageEntry } from './chatProcedure';
import { AuthProcedure } from './authProcedure';
import { TeamsActionsService } from './teamsActionsService';
-import { isValidMeetingUrl } from './meetingUrlParser';
+import { isValidMeetingUrl, getMeetingLaunchUrl } from './meetingUrlParser';
// Camera / fake video injection is disabled for now to focus on stability.
// The Y4M fake video file was causing browser crashes when audio started flowing.
@@ -264,9 +264,19 @@ export class BotOrchestrator {
}
await this._takeScreenshot('step3-teams-loaded', true);
- // STEP 4: Navigate to the meeting URL
- this._logger.info(`STEP 4: navigating to meeting URL: ${this._meetingUrl.substring(0, 80)}...`);
- await this._page!.goto(this._meetingUrl, {
+ // STEP 4: Navigate to the meeting URL with launch params
+ // Add suppressPrompt, msLaunch=false, directDl=true to skip the native
+ // "Open in Teams app?" dialog that blocks Playwright (invisible native prompt).
+ // For auth join we strip anon=true since the user is authenticated.
+ let launchUrl = getMeetingLaunchUrl(this._meetingUrl);
+ try {
+ const urlObj = new URL(launchUrl);
+ urlObj.searchParams.delete('anon');
+ launchUrl = urlObj.toString();
+ } catch { /* keep as-is */ }
+
+ this._logger.info(`STEP 4: navigating to launch URL: ${launchUrl.substring(0, 120)}...`);
+ await this._page!.goto(launchUrl, {
waitUntil: 'domcontentloaded',
timeout: 30000,
});
@@ -275,9 +285,12 @@ export class BotOrchestrator {
// STEP 4a: Poll for first actionable button (interstitial OR pre-join)
const interstitialSelectors = [
+ 'button[data-tid="joinOnWeb"]',
'button:has-text("Continue on this browser")',
'button:has-text("In diesem Browser fortfahren")',
'button:has-text("Weiter in diesem Browser")',
+ 'button:has-text("Join on the web instead")',
+ 'button:has-text("Use web app instead")',
'button[data-tid="chat-join-button"]',
'button[data-tid="join-call-button"]',
];
@@ -303,7 +316,7 @@ export class BotOrchestrator {
if (!isPreJoin) {
await firstBtn.click();
- this._logger.info(`STEP 4a: clicked interstitial: "${btnText}"`);
+ this._logger.info(`STEP 4a: clicked interstitial: "${btnText}" (data-tid="${btnTid}")`);
await this._takeScreenshot('step4a-after-interstitial', true);
} else {
this._logger.info(`STEP 4a: pre-join button already visible: "${btnText}"`);