feat: replace failed variants 1-5 with direct /v2/ test, add per-variant logs

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ValueOn AG 2026-02-16 23:06:53 +01:00
parent feb49a4594
commit 2bb0dd20cc

View file

@ -27,6 +27,7 @@ export interface AuthTestResult {
durationMs: number; durationMs: number;
error?: string; error?: string;
detectedSignals: string[]; detectedSignals: string[];
logs: string[];
} }
export interface AuthTestResults { export interface AuthTestResults {
@ -42,29 +43,9 @@ export interface AuthTestResults {
const _VARIANTS: AuthTestVariant[] = [ const _VARIANTS: AuthTestVariant[] = [
{ {
id: 'baseline', id: 'headfulDirect',
name: 'Baseline (aktuell)', name: 'Headful + Direct /v2/',
description: 'Vanilla Playwright headless, basic stealth, anon=true', description: 'Playwright headful, navigiert direkt zu /v2/ URL (umgeht launcher.html komplett)',
},
{
id: 'rebrowser',
name: 'rebrowser-playwright',
description: 'rebrowser-playwright headless, CDP-Leak-Fixes, ohne anon',
},
{
id: 'playwrightExtra',
name: 'playwright-extra + stealth',
description: 'playwright-extra mit stealth plugin headless, ohne anon',
},
{
id: 'headful',
name: 'Headful',
description: 'Playwright headful (headless: false), enhanced stealth, ohne anon',
},
{
id: 'headfulAuth',
name: 'Headful + Auth',
description: 'Playwright headful mit vollstaendigem Auth-Flow (System-Bot Credentials)',
}, },
]; ];
@ -90,7 +71,7 @@ const _BROWSER_ARGS = [
// ============================================================================ // ============================================================================
/** /**
* Run all 5 auth detection test variants against a Teams meeting URL. * Run all 6 auth detection test variants against a Teams meeting URL.
* Does NOT join the meeting only checks which page Teams serves. * Does NOT join the meeting only checks which page Teams serves.
*/ */
export async function runAuthTests( export async function runAuthTests(
@ -144,6 +125,15 @@ async function _runVariant(
const startTime = Date.now(); const startTime = Date.now();
let browser: Browser | null = null; let browser: Browser | null = null;
let context: BrowserContext | null = null; let context: BrowserContext | null = null;
const variantLogs: string[] = [];
const _log = (level: 'info' | 'warn' | 'error', msg: string) => {
const prefix = `[${level.toUpperCase()}]`;
variantLogs.push(`${prefix} ${msg}`);
if (level === 'error') logger.error(`[AuthTest:${variant.id}] ${msg}`);
else if (level === 'warn') logger.warn(`[AuthTest:${variant.id}] ${msg}`);
else logger.info(`[AuthTest:${variant.id}] ${msg}`);
};
try { try {
// Launch browser based on variant // Launch browser based on variant
@ -152,29 +142,69 @@ async function _runVariant(
context = launchResult.context; context = launchResult.context;
const page = launchResult.page; const page = launchResult.page;
// Resolve meeting URL (with or without anon=true) // Collect browser console messages
page.on('console', (msg) => {
const type = msg.type();
if (type === 'error' || type === 'warning') {
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)}`);
}
});
if (variant.id === 'headfulDirect') {
// Variant 6: Navigate directly to /v2/ URL, bypassing launcher.html entirely
const directUrl = _buildDirectV2Url(meetingUrl);
_log('info', `Direct /v2/ navigation: ${directUrl.substring(0, 120)}`);
await page.goto(directUrl, {
waitUntil: 'domcontentloaded',
timeout: 30000,
});
// Wait and log where Teams actually redirects to
await page.waitForTimeout(8000);
const currentUrl = page.url();
_log('info', `After redirect: ${currentUrl.substring(0, 120)}`);
if (currentUrl.includes('light-meetings')) {
_log('warn', 'Teams redirected /v2/ to light-meetings');
} else if (currentUrl.includes('/v2/')) {
_log('info', '/v2/ page loaded successfully!');
}
} else {
// Standard flow: resolve URL, navigate, click launcher
const useAnon = variant.id === 'baseline'; const useAnon = variant.id === 'baseline';
const launchUrl = await _resolveMeetingUrl(meetingUrl, useAnon); const launchUrl = await _resolveMeetingUrl(meetingUrl, useAnon);
logger.info(`[AuthTest:${variant.id}] Navigating to: ${launchUrl.substring(0, 100)}`); _log('info', `Navigating to: ${launchUrl.substring(0, 100)}`);
// Navigate to meeting
await page.goto(launchUrl, { await page.goto(launchUrl, {
waitUntil: 'domcontentloaded', waitUntil: 'domcontentloaded',
timeout: 30000, timeout: 30000,
}); });
// Handle launcher dialog ("Continue on this browser")
await _handleLauncher(page); await _handleLauncher(page);
// Wait for the pre-join page to settle
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
}
// If this is the auth variant, attempt login // If this is the auth variant, attempt login
let authAttempted = false; let authAttempted = false;
let authSuccess: boolean | null = null; let authSuccess: boolean | null = null;
if (variant.id === 'headfulAuth' && botAccountEmail && botAccountPassword) { if (variant.id === 'headfulAuth' && botAccountEmail && botAccountPassword) {
authAttempted = true; authAttempted = true;
_log('info', `Attempting auth as ${botAccountEmail}`);
authSuccess = await _attemptAuth(page, botAccountEmail, botAccountPassword); authSuccess = await _attemptAuth(page, botAccountEmail, botAccountPassword);
_log(authSuccess ? 'info' : 'warn', `Auth result: ${authSuccess ? 'success' : 'failed'}`);
} }
// Detect page type and gather signals // Detect page type and gather signals
@ -197,8 +227,11 @@ async function _runVariant(
screenshot, screenshot,
durationMs: Date.now() - startTime, durationMs: Date.now() - startTime,
detectedSignals: detection.signals, detectedSignals: detection.signals,
logs: variantLogs,
}; };
} catch (err) { } catch (err) {
_log('error', `Variant failed: ${String(err).substring(0, 300)}`);
// Try to take an error screenshot // Try to take an error screenshot
let screenshot: string | undefined; let screenshot: string | undefined;
try { try {
@ -227,9 +260,9 @@ async function _runVariant(
durationMs: Date.now() - startTime, durationMs: Date.now() - startTime,
error: String(err), error: String(err),
detectedSignals: [], detectedSignals: [],
logs: variantLogs,
}; };
} finally { } finally {
// Clean up browser
try { try {
if (context) await context.close(); if (context) await context.close();
if (browser) await browser.close(); if (browser) await browser.close();
@ -268,6 +301,10 @@ async function _launchBrowserForVariant(variant: AuthTestVariant): Promise<Launc
_requireDisplay(); _requireDisplay();
return _launchVanilla(false, 'enhanced'); return _launchVanilla(false, 'enhanced');
case 'headfulDirect':
_requireDisplay();
return _launchVanilla(false, 'enhanced');
default: default:
return _launchVanilla(true, 'basic'); return _launchVanilla(true, 'basic');
} }
@ -555,6 +592,43 @@ async function _resolveMeetingUrl(meetingUrl: string, withAnon: boolean): Promis
} }
} }
/**
* Build a direct /v2/ URL that bypasses the launcher.html page entirely.
* Constructs the Teams pre-join URL with the /v2/ path, hoping Teams
* doesn't redirect to light-meetings when the initial URL is already /v2/.
*/
function _buildDirectV2Url(meetingUrl: string): string {
const trimmed = meetingUrl.trim();
// Extract the meeting context from the short URL or full URL
// Short URL format: https://teams.microsoft.com/meet/XXXXXXX?p=YYYY
// Full URL: https://teams.microsoft.com/l/meetup-join/...
// /v2/ URL: https://teams.microsoft.com/v2/#/l/meetup-join/...
try {
const parsed = new URL(trimmed);
// If it's already a full meetup-join URL, inject /v2/
if (parsed.pathname.includes('/l/meetup-join/')) {
const meetupPath = parsed.pathname.substring(parsed.pathname.indexOf('/l/meetup-join/'));
return `https://teams.microsoft.com/v2/#${meetupPath}${parsed.search}`;
}
// For short URLs (/meet/...), we need to build a /v2/ URL
// Teams /v2/ format with meeting context
if (parsed.pathname.includes('/meet/')) {
const meetId = parsed.pathname.split('/meet/')[1];
const params = parsed.search;
return `https://teams.microsoft.com/v2/#/meet/${meetId}${params}`;
}
} catch {
// URL parsing failed
}
// Fallback: just prepend /v2/# to the path
return `https://teams.microsoft.com/v2/#/l/meetup-join/${encodeURIComponent(trimmed)}`;
}
// ============================================================================ // ============================================================================
// LAUNCHER HANDLING // LAUNCHER HANDLING
// ============================================================================ // ============================================================================
@ -797,6 +871,7 @@ function _createSkippedResult(variant: AuthTestVariant, reason: string): AuthTes
durationMs: 0, durationMs: 0,
error: `Uebersprungen: ${reason}`, error: `Uebersprungen: ${reason}`,
detectedSignals: [], detectedSignals: [],
logs: [`[INFO] Skipped: ${reason}`],
}; };
} }
@ -815,6 +890,7 @@ function _createErrorResult(variant: AuthTestVariant, error: string): AuthTestRe
durationMs: 0, durationMs: 0,
error, error,
detectedSignals: [], detectedSignals: [],
logs: [`[ERROR] ${error}`],
}; };
} }