From c892c93215af4c5ad5e8f13cd27d85765411ba60 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Tue, 17 Feb 2026 11:23:17 +0100
Subject: [PATCH] feat: step-by-step screenshots + Join a meeting flow after
auth
Co-authored-by: Cursor
---
src/bot/authTestProcedure.ts | 172 +++++++++++++++++++++++++++++++----
1 file changed, 156 insertions(+), 16 deletions(-)
diff --git a/src/bot/authTestProcedure.ts b/src/bot/authTestProcedure.ts
index 1c58227..a766161 100644
--- a/src/bot/authTestProcedure.ts
+++ b/src/bot/authTestProcedure.ts
@@ -12,6 +12,11 @@ export interface AuthTestVariant {
description: string;
}
+export interface StepScreenshot {
+ label: string;
+ data: string;
+}
+
export interface AuthTestResult {
variantId: string;
variantName: string;
@@ -24,6 +29,7 @@ export interface AuthTestResult {
authAttempted: boolean;
authSuccess: boolean | null;
screenshot?: string;
+ screenshots?: StepScreenshot[];
durationMs: number;
error?: string;
detectedSignals: string[];
@@ -181,15 +187,26 @@ async function _runVariant(
}
});
- // All variants follow the same flow:
- // 1. Navigate to teams.microsoft.com (triggers Teams-specific login redirect)
- // 2. Login (email → password → stay signed in)
- // 3. Wait 20 seconds after login (NO meeting navigation)
- // 4. Screenshot where we landed
+ // Flow with screenshots at every step:
+ // 1. Navigate to teams.microsoft.com → wait for login redirect → screenshot
+ // 2. Login (email → password → stay signed in) → screenshot after auth
+ // 3. Wait for Teams app to load → screenshot
+ // 4. Click "Join a meeting" → screenshot
+ // 5. Navigate to meeting URL → screenshot (final)
let authAttempted = false;
let authSuccess: boolean | null = null;
+ const screenshots: StepScreenshot[] = [];
+ async function _screenshotStep(label: string): Promise {
+ _log('info', `Screenshot: "${label}" — URL: ${page.url().substring(0, 120)}`);
+ const data = await _takeScreenshot(page);
+ if (data) {
+ screenshots.push({ label, data });
+ }
+ }
+
+ // --- Step 1: Navigate to teams.microsoft.com ---
_log('info', `Step 1: Navigate to teams.microsoft.com`);
await page.goto('https://teams.microsoft.com', {
waitUntil: 'domcontentloaded',
@@ -204,30 +221,152 @@ async function _runVariant(
} catch {
_log('warn', `No login redirect after 30s, current URL: ${page.url().substring(0, 150)}`);
}
+ await _screenshotStep('1 - Login-Seite');
- // Step 2: Login
+ // --- Step 2: Login ---
if (botAccountEmail && botAccountPassword) {
authAttempted = true;
_log('info', `Authenticating as ${botAccountEmail} (skipNavigation=true to preserve OAuth context)`);
const authProcedure = new AuthProcedure(page, logger);
- // Always skipNavigation=true: we're already on login.microsoftonline.com with the correct
- // OAuth parameters (client_id, redirect_uri for Teams). Navigating to plain
- // login.microsoftonline.com would lose this context.
authSuccess = await authProcedure.authenticateWithMicrosoft(botAccountEmail, botAccountPassword, true);
_log(authSuccess ? 'info' : 'warn', `Auth result: ${authSuccess ? 'success' : 'failed'}`);
+
+ if (authSuccess) {
+ // Wait for redirect chain back to Teams
+ _log('info', 'Waiting for redirect back to Teams...');
+ try {
+ await page.waitForURL('**/teams.microsoft.com/**', { timeout: 30000 });
+ } catch {
+ _log('warn', `No Teams redirect after 30s, current URL: ${page.url().substring(0, 150)}`);
+ }
+ await page.waitForTimeout(5000);
+ }
+ await _screenshotStep('2 - Nach Login');
+ } else {
+ _log('info', 'No credentials provided — skipping auth');
}
- // Step 3: Wait 20 seconds — NO further navigation
- _log('info', 'Waiting 20s after login (no meeting navigation)...');
- await page.waitForTimeout(20000);
- _log('info', `Final URL after 20s: ${page.url().substring(0, 150)}`);
+ // --- Step 3: Teams App geladen ---
+ _log('info', 'Waiting 10s for Teams app to fully load...');
+ await page.waitForTimeout(10000);
+ await _screenshotStep('3 - Teams App');
- // Detect page type and gather signals
+ // --- Step 4: Click "Join a meeting" ---
+ _log('info', 'Step 4: Looking for "Join a meeting" button...');
+ const joinMeetingSelectors = [
+ 'button:has-text("Join a meeting")',
+ 'button:has-text("An Besprechung teilnehmen")',
+ 'a:has-text("Join a meeting")',
+ 'a:has-text("An Besprechung teilnehmen")',
+ '[data-tid="join-meeting-button"]',
+ '[data-tid="join-a-meeting-button"]',
+ 'button[title="Join a meeting"]',
+ 'button[aria-label="Join a meeting"]',
+ ];
+
+ let joinMeetingClicked = false;
+ for (const selector of joinMeetingSelectors) {
+ try {
+ const btn = await page.waitForSelector(selector, { timeout: 5000, state: 'visible' });
+ if (btn) {
+ await btn.click();
+ _log('info', `Clicked "Join a meeting": ${selector}`);
+ joinMeetingClicked = true;
+ await page.waitForTimeout(3000);
+ break;
+ }
+ } catch {
+ // Try next selector
+ }
+ }
+
+ if (!joinMeetingClicked) {
+ _log('warn', 'Could not find "Join a meeting" button — logging all visible buttons');
+ try {
+ const buttons = await page.evaluate(() => {
+ const btns = document.querySelectorAll('button, a[role="button"], [role="menuitem"]');
+ return Array.from(btns).slice(0, 30).map(b => {
+ const text = (b.textContent || '').trim().substring(0, 80);
+ const tid = b.getAttribute('data-tid') || '';
+ const title = b.getAttribute('title') || '';
+ return `[${b.tagName} tid="${tid}" title="${title}"] ${text}`;
+ });
+ });
+ buttons.forEach(b => _log('info', ` Button: ${b}`));
+ } catch (e) {
+ _log('warn', `Could not enumerate buttons: ${e}`);
+ }
+ }
+ await _screenshotStep('4 - Join a meeting');
+
+ // --- Step 5: Enter meeting URL ---
+ if (joinMeetingClicked) {
+ _log('info', `Step 5: Entering meeting URL: ${meetingUrl.substring(0, 80)}`);
+ const meetingInputSelectors = [
+ 'input[placeholder*="meeting"]',
+ 'input[placeholder*="Besprechung"]',
+ 'input[placeholder*="code"]',
+ 'input[placeholder*="Code"]',
+ 'input[placeholder*="link"]',
+ 'input[placeholder*="Link"]',
+ 'input[data-tid="join-meeting-input"]',
+ 'input[type="text"]',
+ 'input[type="url"]',
+ ];
+
+ let meetingInputFound = false;
+ for (const selector of meetingInputSelectors) {
+ try {
+ const input = await page.waitForSelector(selector, { timeout: 5000, state: 'visible' });
+ if (input) {
+ await input.fill(meetingUrl);
+ _log('info', `Entered meeting URL in: ${selector}`);
+ meetingInputFound = true;
+ await page.waitForTimeout(2000);
+ break;
+ }
+ } catch {
+ // Try next
+ }
+ }
+
+ if (!meetingInputFound) {
+ _log('warn', 'Could not find meeting URL input field');
+ }
+
+ // Try clicking "Join" button after entering URL
+ const joinNowSelectors = [
+ 'button:has-text("Join")',
+ 'button:has-text("Teilnehmen")',
+ 'button:has-text("Join now")',
+ 'button:has-text("Jetzt teilnehmen")',
+ '#prejoin-join-button',
+ 'button[data-tid="prejoin-join-button"]',
+ ];
+
+ for (const selector of joinNowSelectors) {
+ try {
+ const btn = await page.$(selector);
+ if (btn && await btn.isVisible()) {
+ _log('info', `Found Join button: ${selector} (NOT clicking — test only)`);
+ break;
+ }
+ } catch {
+ // Try next
+ }
+ }
+
+ await page.waitForTimeout(5000);
+ await _screenshotStep('5 - Meeting URL eingegeben');
+ }
+
+ // --- Final: detect page type ---
+ _log('info', `Final URL: ${page.url().substring(0, 150)}`);
const detection = await _detectPageType(page);
- // Take screenshot
- const screenshot = await _takeScreenshot(page);
+ // Use last screenshot as the main screenshot for backward compatibility
+ const screenshot = screenshots.length > 0 ? screenshots[screenshots.length - 1].data : undefined;
return {
variantId: variant.id,
@@ -241,6 +380,7 @@ async function _runVariant(
authAttempted,
authSuccess,
screenshot,
+ screenshots,
durationMs: Date.now() - startTime,
detectedSignals: detection.signals,
logs: variantLogs,