feat: step-by-step screenshots + Join a meeting flow after auth

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ValueOn AG 2026-02-17 11:23:17 +01:00
parent 08194fe241
commit c892c93215

View file

@ -12,6 +12,11 @@ export interface AuthTestVariant {
description: string; description: string;
} }
export interface StepScreenshot {
label: string;
data: string;
}
export interface AuthTestResult { export interface AuthTestResult {
variantId: string; variantId: string;
variantName: string; variantName: string;
@ -24,6 +29,7 @@ export interface AuthTestResult {
authAttempted: boolean; authAttempted: boolean;
authSuccess: boolean | null; authSuccess: boolean | null;
screenshot?: string; screenshot?: string;
screenshots?: StepScreenshot[];
durationMs: number; durationMs: number;
error?: string; error?: string;
detectedSignals: string[]; detectedSignals: string[];
@ -181,15 +187,26 @@ async function _runVariant(
} }
}); });
// All variants follow the same flow: // Flow with screenshots at every step:
// 1. Navigate to teams.microsoft.com (triggers Teams-specific login redirect) // 1. Navigate to teams.microsoft.com → wait for login redirect → screenshot
// 2. Login (email → password → stay signed in) // 2. Login (email → password → stay signed in) → screenshot after auth
// 3. Wait 20 seconds after login (NO meeting navigation) // 3. Wait for Teams app to load → screenshot
// 4. Screenshot where we landed // 4. Click "Join a meeting" → screenshot
// 5. Navigate to meeting URL → screenshot (final)
let authAttempted = false; let authAttempted = false;
let authSuccess: boolean | null = null; let authSuccess: boolean | null = null;
const screenshots: StepScreenshot[] = [];
async function _screenshotStep(label: string): Promise<void> {
_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`); _log('info', `Step 1: Navigate to teams.microsoft.com`);
await page.goto('https://teams.microsoft.com', { await page.goto('https://teams.microsoft.com', {
waitUntil: 'domcontentloaded', waitUntil: 'domcontentloaded',
@ -204,30 +221,152 @@ async function _runVariant(
} catch { } catch {
_log('warn', `No login redirect after 30s, current URL: ${page.url().substring(0, 150)}`); _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) { if (botAccountEmail && botAccountPassword) {
authAttempted = true; authAttempted = true;
_log('info', `Authenticating as ${botAccountEmail} (skipNavigation=true to preserve OAuth context)`); _log('info', `Authenticating as ${botAccountEmail} (skipNavigation=true to preserve OAuth context)`);
const authProcedure = new AuthProcedure(page, logger); 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); authSuccess = await authProcedure.authenticateWithMicrosoft(botAccountEmail, botAccountPassword, true);
_log(authSuccess ? 'info' : 'warn', `Auth result: ${authSuccess ? 'success' : 'failed'}`); _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 // --- Step 3: Teams App geladen ---
_log('info', 'Waiting 20s after login (no meeting navigation)...'); _log('info', 'Waiting 10s for Teams app to fully load...');
await page.waitForTimeout(20000); await page.waitForTimeout(10000);
_log('info', `Final URL after 20s: ${page.url().substring(0, 150)}`); 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); const detection = await _detectPageType(page);
// Take screenshot // Use last screenshot as the main screenshot for backward compatibility
const screenshot = await _takeScreenshot(page); const screenshot = screenshots.length > 0 ? screenshots[screenshots.length - 1].data : undefined;
return { return {
variantId: variant.id, variantId: variant.id,
@ -241,6 +380,7 @@ async function _runVariant(
authAttempted, authAttempted,
authSuccess, authSuccess,
screenshot, screenshot,
screenshots,
durationMs: Date.now() - startTime, durationMs: Date.now() - startTime,
detectedSignals: detection.signals, detectedSignals: detection.signals,
logs: variantLogs, logs: variantLogs,