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:
parent
feb49a4594
commit
2bb0dd20cc
1 changed files with 114 additions and 38 deletions
|
|
@ -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}`],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue