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