From 8ed183f13c60a5b463d1f12b0d84fea97c069fb5 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Tue, 17 Feb 2026 11:49:00 +0100
Subject: [PATCH] refactor: 2 path variants (Sign in / Join a meeting) with
full page load waits
Co-authored-by: Cursor
---
src/bot/authTestProcedure.ts | 373 +++++++++++++++++++----------------
1 file changed, 200 insertions(+), 173 deletions(-)
diff --git a/src/bot/authTestProcedure.ts b/src/bot/authTestProcedure.ts
index a766161..60cbaae 100644
--- a/src/bot/authTestProcedure.ts
+++ b/src/bot/authTestProcedure.ts
@@ -49,19 +49,14 @@ export interface AuthTestResults {
const _VARIANTS: AuthTestVariant[] = [
{
- id: 'chromiumClean',
- name: 'Chromium Headful Clean',
- description: 'Playwright Chromium headful, enhanced stealth + realistic devices, keine fake-flags',
+ id: 'signIn',
+ name: 'Pfad: Sign in',
+ description: 'Nach Login auf Teams-Landingpage "Sign in" klicken — was kommt?',
},
{
- id: 'chromiumNoAutomation',
- name: 'Chromium No-Automation',
- description: 'Chromium headful, zusaetzlich --disable-extensions, --no-first-run',
- },
- {
- id: 'rebrowserHeadful',
- name: 'rebrowser-playwright Headful',
- description: 'rebrowser-playwright headful, CDP-Leak-Fixes + realistic devices',
+ id: 'joinMeeting',
+ name: 'Pfad: Join a meeting',
+ description: 'Nach Login auf Teams-Landingpage "Join a meeting" klicken — was kommt?',
},
];
@@ -69,7 +64,7 @@ const _VARIANTS: AuthTestVariant[] = [
// CONSTANTS
// ============================================================================
-const _VARIANT_TIMEOUT_MS = 60000;
+const _VARIANT_TIMEOUT_MS = 120000;
const _SCREENSHOT_QUALITY = 50;
const _USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0';
@@ -96,8 +91,8 @@ const _BROWSER_ARGS_NO_AUTOMATION = [
// ============================================================================
/**
- * Run auth detection test variants: login at teams.microsoft.com, screenshot where we land.
- * Does NOT navigate to any meeting URL — only tests if Teams login works.
+ * Run auth tests: 2 variants — "Sign in" and "Join a meeting" on the Teams landing page.
+ * Both do full Microsoft login first, then click different buttons to see what page loads.
*/
export async function runAuthTests(
meetingUrl: string,
@@ -109,7 +104,6 @@ export async function runAuthTests(
const results: AuthTestResult[] = [];
for (const variant of _VARIANTS) {
- // All variants require credentials
if (!botAccountEmail || !botAccountPassword) {
results.push(_createSkippedResult(variant, 'Keine Credentials angegeben'));
continue;
@@ -119,7 +113,7 @@ export async function runAuthTests(
try {
const result = await _runVariant(variant, meetingUrl, botAccountEmail, botAccountPassword);
results.push(result);
- logger.info(`[AuthTest] Variant ${variant.name}: pageType=${result.pageType}, url=${result.finalUrl.substring(0, 80)}`);
+ logger.info(`[AuthTest] Variant ${variant.name}: finalUrl=${result.finalUrl.substring(0, 80)}`);
} catch (err) {
logger.error(`[AuthTest] Variant ${variant.name} failed:`, err);
results.push(_createErrorResult(variant, String(err)));
@@ -161,39 +155,30 @@ async function _runVariant(
};
try {
- // Launch browser based on variant
- const launchResult = await _launchBrowserForVariant(variant);
+ // Always use Chromium Headful Clean for both variants
+ const launchResult = await _launchChromiumHeadful(_BROWSER_ARGS_CLEAN);
browser = launchResult.browser;
context = launchResult.context;
const page = launchResult.page;
- // Collect browser console messages
+ // Collect browser console messages (only errors, to reduce noise)
page.on('console', (msg) => {
const type = msg.type();
- if (type === 'error' || type === 'warning') {
+ if (type === 'error') {
variantLogs.push(`[CONSOLE:${type}] ${msg.text().substring(0, 200)}`);
}
});
- // Collect page errors
page.on('pageerror', (err) => {
variantLogs.push(`[PAGE_ERROR] ${String(err).substring(0, 200)}`);
});
- // Track navigation/redirects
page.on('framenavigated', (frame) => {
if (frame === page.mainFrame()) {
variantLogs.push(`[NAV] ${frame.url().substring(0, 150)}`);
}
});
- // 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[] = [];
@@ -206,111 +191,196 @@ async function _runVariant(
}
}
- // --- Step 1: Navigate to teams.microsoft.com ---
- _log('info', `Step 1: Navigate to teams.microsoft.com`);
+ // =====================================================================
+ // 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',
timeout: 30000,
});
- // Wait for Teams to redirect to login.microsoftonline.com (up to 30s)
- _log('info', 'Waiting for login redirect to login.microsoftonline.com...');
+ // Wait for login redirect OR the Teams landing page to appear
+ _log('info', 'Waiting for login.microsoftonline.com redirect...');
try {
await page.waitForURL('**/login.microsoftonline.com/**', { timeout: 30000 });
_log('info', `Redirected to login: ${page.url().substring(0, 150)}`);
} catch {
- _log('warn', `No login redirect after 30s, current URL: ${page.url().substring(0, 150)}`);
+ _log('warn', `No login redirect, current URL: ${page.url().substring(0, 150)}`);
+ }
+
+ // Wait for the login page to FULLY RENDER (email input visible)
+ _log('info', 'Waiting for login page to fully render...');
+ try {
+ await page.waitForSelector(
+ 'input[name="loginfmt"], input[type="email"], button:has-text("Sign in"), button:has-text("Join a meeting")',
+ { timeout: 15000, state: 'visible' },
+ );
+ await page.waitForTimeout(2000);
+ } catch {
+ _log('warn', 'Login page elements not found, taking screenshot anyway');
+ await page.waitForTimeout(5000);
}
await _screenshotStep('1 - Login-Seite');
- // --- Step 2: Login ---
+ // =====================================================================
+ // STEP 2: Microsoft Login
+ // =====================================================================
if (botAccountEmail && botAccountPassword) {
authAttempted = true;
- _log('info', `Authenticating as ${botAccountEmail} (skipNavigation=true to preserve OAuth context)`);
- const authProcedure = new AuthProcedure(page, logger);
- authSuccess = await authProcedure.authenticateWithMicrosoft(botAccountEmail, botAccountPassword, true);
- _log(authSuccess ? 'info' : 'warn', `Auth result: ${authSuccess ? 'success' : 'failed'}`);
+ // Check if we're on login.microsoftonline.com or on the Teams landing page
+ const currentUrl = page.url();
+ const onMsLogin = currentUrl.includes('login.microsoftonline.com') || currentUrl.includes('login.live.com');
- 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);
+ if (onMsLogin) {
+ _log('info', `On MS login page, authenticating as ${botAccountEmail}`);
+ const authProcedure = new AuthProcedure(page, logger);
+ authSuccess = await authProcedure.authenticateWithMicrosoft(botAccountEmail, botAccountPassword, true);
+ _log(authSuccess ? 'info' : 'warn', `Auth result: ${authSuccess ? 'success' : 'failed'}`);
+ } else {
+ _log('info', 'Not on MS login page — Teams may already be loaded. Skipping auth step.');
+ authSuccess = null;
}
- await _screenshotStep('2 - Nach Login');
} else {
_log('info', 'No credentials provided — skipping auth');
}
- // --- Step 3: Teams App geladen ---
- _log('info', 'Waiting 10s for Teams app to fully load...');
- await page.waitForTimeout(10000);
- await _screenshotStep('3 - Teams App');
+ // =====================================================================
+ // STEP 3: Wait for Teams landing page to fully render
+ // =====================================================================
+ _log('info', 'Waiting for Teams landing page to fully render...');
- // --- 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) {
+ // Wait for redirect back to Teams (if we were on login page)
+ if (authSuccess) {
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;
- }
+ await page.waitForURL('**/teams.microsoft.com/**', { timeout: 30000 });
} catch {
- // Try next selector
+ _log('warn', `No Teams redirect, current URL: ${page.url().substring(0, 150)}`);
}
}
- 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}`;
- });
+ // Wait for either "Sign in" or "Join a meeting" button to be visible
+ // This is the Teams landing page ("Everyone together in Teams")
+ _log('info', 'Waiting for "Sign in" or "Join a meeting" button to appear...');
+ try {
+ await page.waitForSelector(
+ 'button:has-text("Sign in"), button:has-text("Join a meeting"), button:has-text("Anmelden"), button:has-text("An Besprechung teilnehmen")',
+ { timeout: 30000, state: 'visible' },
+ );
+ await page.waitForTimeout(3000);
+ _log('info', 'Teams landing page loaded');
+ } catch {
+ _log('warn', 'Landing page buttons not found after 30s');
+ await page.waitForTimeout(5000);
+ }
+ await _screenshotStep('2 - Teams Landingpage');
+
+ // Log all visible buttons for debugging
+ try {
+ const buttons = await page.evaluate(() => {
+ const btns = document.querySelectorAll('button, a[role="button"]');
+ return Array.from(btns).slice(0, 20).map(b => {
+ const text = (b.textContent || '').trim().substring(0, 80);
+ const tid = b.getAttribute('data-tid') || '';
+ return `[${b.tagName} tid="${tid}"] ${text}`;
});
- buttons.forEach(b => _log('info', ` Button: ${b}`));
- } catch (e) {
- _log('warn', `Could not enumerate buttons: ${e}`);
- }
+ });
+ buttons.forEach(b => _log('info', ` Visible button: ${b}`));
+ } catch {
+ // Ignore
}
- await _screenshotStep('4 - Join a meeting');
- // --- Step 5: Enter meeting URL ---
- if (joinMeetingClicked) {
- _log('info', `Step 5: Entering meeting URL: ${meetingUrl.substring(0, 80)}`);
+ // =====================================================================
+ // STEP 4: Click variant-specific button
+ // =====================================================================
+ if (variant.id === 'signIn') {
+ // --- VARIANT A: Click "Sign in" ---
+ _log('info', 'Step 3: Clicking "Sign in" on Teams landing page...');
+ const signInSelectors = [
+ 'button:has-text("Sign in")',
+ 'button:has-text("Anmelden")',
+ 'a:has-text("Sign in")',
+ 'a:has-text("Anmelden")',
+ ];
+
+ let clicked = false;
+ for (const selector of signInSelectors) {
+ try {
+ const btn = await page.waitForSelector(selector, { timeout: 5000, state: 'visible' });
+ if (btn) {
+ await btn.click();
+ _log('info', `Clicked: ${selector}`);
+ clicked = true;
+ break;
+ }
+ } catch {
+ // Try next
+ }
+ }
+
+ if (!clicked) {
+ _log('warn', '"Sign in" button not found on landing page');
+ }
+
+ // Wait for the resulting page to FULLY load
+ _log('info', 'Waiting for resulting page to fully load...');
+ await page.waitForTimeout(5000);
+ try {
+ await page.waitForLoadState('networkidle', { timeout: 20000 });
+ } catch {
+ _log('warn', 'networkidle timeout, continuing');
+ }
+ await page.waitForTimeout(5000);
+ await _screenshotStep('3 - Nach "Sign in"');
+
+ } else if (variant.id === 'joinMeeting') {
+ // --- VARIANT B: Click "Join a meeting" ---
+ _log('info', 'Step 3: Clicking "Join a meeting" on Teams landing page...');
+ const joinSelectors = [
+ 'button:has-text("Join a meeting")',
+ 'button:has-text("An Besprechung teilnehmen")',
+ 'a:has-text("Join a meeting")',
+ 'a:has-text("An Besprechung teilnehmen")',
+ ];
+
+ let clicked = false;
+ for (const selector of joinSelectors) {
+ try {
+ const btn = await page.waitForSelector(selector, { timeout: 5000, state: 'visible' });
+ if (btn) {
+ await btn.click();
+ _log('info', `Clicked: ${selector}`);
+ clicked = true;
+ break;
+ }
+ } catch {
+ // Try next
+ }
+ }
+
+ if (!clicked) {
+ _log('warn', '"Join a meeting" button not found on landing page');
+ }
+
+ // Wait for the resulting page to FULLY load
+ _log('info', 'Waiting for resulting page to fully load...');
+ await page.waitForTimeout(5000);
+ try {
+ await page.waitForLoadState('networkidle', { timeout: 20000 });
+ } catch {
+ _log('warn', 'networkidle timeout, continuing');
+ }
+ await page.waitForTimeout(5000);
+ await _screenshotStep('3 - Nach "Join a meeting"');
+
+ // If there's an input for meeting URL, enter it
+ _log('info', 'Looking for meeting URL input...');
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[placeholder*="meeting" i]',
+ 'input[placeholder*="code" i]',
+ 'input[placeholder*="link" i]',
+ 'input[placeholder*="ID" i]',
'input[type="text"]',
'input[type="url"]',
];
@@ -323,7 +393,7 @@ async function _runVariant(
await input.fill(meetingUrl);
_log('info', `Entered meeting URL in: ${selector}`);
meetingInputFound = true;
- await page.waitForTimeout(2000);
+ await page.waitForTimeout(3000);
break;
}
} catch {
@@ -331,70 +401,49 @@ async function _runVariant(
}
}
- if (!meetingInputFound) {
- _log('warn', 'Could not find meeting URL input field');
+ if (meetingInputFound) {
+ await page.waitForTimeout(5000);
+ await _screenshotStep('4 - Meeting URL eingegeben');
+ } else {
+ _log('warn', 'No meeting URL input field found');
}
-
- // 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 ---
+ // =====================================================================
+ // FINAL: Log result
+ // =====================================================================
_log('info', `Final URL: ${page.url().substring(0, 150)}`);
- const detection = await _detectPageType(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,
variantName: variant.name,
success: true,
- pageType: detection.pageType,
+ pageType: 'unknown',
finalUrl: page.url(),
- hasSignInLink: detection.hasSignInLink,
- hasNameInput: detection.hasNameInput,
- hasJoinButton: detection.hasJoinButton,
+ hasSignInLink: false,
+ hasNameInput: false,
+ hasJoinButton: false,
authAttempted,
authSuccess,
screenshot,
screenshots,
durationMs: Date.now() - startTime,
- detectedSignals: detection.signals,
+ detectedSignals: [],
logs: variantLogs,
};
} catch (err) {
_log('error', `Variant failed: ${String(err).substring(0, 300)}`);
- // Try to take an error screenshot
let screenshot: string | undefined;
+ const screenshots: StepScreenshot[] = [];
try {
if (context) {
const pages = context.pages();
if (pages.length > 0) {
screenshot = await _takeScreenshot(pages[0]);
+ if (screenshot) screenshots.push({ label: 'Error', data: screenshot });
}
}
} catch {
@@ -413,6 +462,7 @@ async function _runVariant(
authAttempted: false,
authSuccess: null,
screenshot,
+ screenshots,
durationMs: Date.now() - startTime,
error: String(err),
detectedSignals: [],
@@ -438,22 +488,9 @@ interface LaunchResult {
page: Page;
}
-async function _launchBrowserForVariant(variant: AuthTestVariant): Promise {
- _requireDisplay(); // All variants are headful
-
- switch (variant.id) {
- case 'chromiumClean':
- return _launchChromiumHeadful(_BROWSER_ARGS_CLEAN);
-
- case 'chromiumNoAutomation':
- return _launchChromiumHeadful(_BROWSER_ARGS_NO_AUTOMATION);
-
- case 'rebrowserHeadful':
- return _launchRebrowserHeadful();
-
- default:
- return _launchChromiumHeadful(_BROWSER_ARGS_CLEAN);
- }
+async function _launchBrowserForVariant(_variant: AuthTestVariant): Promise {
+ _requireDisplay();
+ return _launchChromiumHeadful(_BROWSER_ARGS_CLEAN);
}
/**
@@ -1100,26 +1137,16 @@ function _createErrorResult(variant: AuthTestVariant, error: string): AuthTestRe
* Generate a recommendation based on test results.
*/
function _generateRecommendation(results: AuthTestResult[]): string {
- const v2Variants = results.filter(r => r.pageType === 'v2');
- const authSuccess = results.find(r => r.authAttempted && r.authSuccess === true);
+ const authSuccess = results.filter(r => r.authAttempted && r.authSuccess === true);
+ const failures = results.filter(r => !r.success);
- if (authSuccess) {
- return `Variante "${authSuccess.variantName}" hat Auth erfolgreich durchgefuehrt! ` +
- `Auth-Flow kann mit dieser Konfiguration aktiviert werden.`;
+ if (authSuccess.length > 0) {
+ return `Microsoft-Login erfolgreich. Screenshots pruefen, um den besten Pfad zu waehlen.`;
}
- if (v2Variants.length > 0) {
- const names = v2Variants.map(v => `"${v.variantName}"`).join(', ');
- return `${v2Variants.length} Variante(n) haben /v2/ erhalten: ${names}. ` +
- `Auth ist prinzipiell moeglich — Auth-Flow kann re-aktiviert werden.`;
+ if (failures.length === results.length) {
+ return 'Beide Varianten fehlgeschlagen. Logs und Screenshots pruefen.';
}
- const successVariants = results.filter(r => r.success);
- if (successVariants.every(r => r.pageType === 'lightMeetings')) {
- return 'Alle Varianten wurden auf light-meetings umgeleitet. ' +
- 'Teams blockiert den authentifizierten Join fuer alle getesteten Browser-Konfigurationen. ' +
- 'Weitere Optionen: Graph API / Bot Framework oder andere Stealth-Ansaetze evaluieren.';
- }
-
- return 'Test abgeschlossen. Ergebnisse pruefen und naechste Schritte planen.';
+ return 'Test abgeschlossen. Screenshots pruefen und naechste Schritte planen.';
}