diff --git a/playwriter/src/mcp.test.ts b/playwriter/src/mcp.test.ts index 039de4c..4740a64 100644 --- a/playwriter/src/mcp.test.ts +++ b/playwriter/src/mcp.test.ts @@ -678,12 +678,7 @@ describe('MCP Server Tests', () => { if (!browserContext) throw new Error('Browser not initialized') const serviceWorker = await getExtensionServiceWorker(browserContext) - // 1. Ensure clean state (disconnected) - await serviceWorker.evaluate(async () => { - await globalThis.disconnectEverything() - }) - - // 2. Open page and navigate + // 1. Open a new page (extension not yet enabled for it) const page = await browserContext.newPage() const targetUrl = 'https://example.com/late-enable' await page.goto(targetUrl) @@ -692,12 +687,12 @@ describe('MCP Server Tests', () => { // Wait for load await page.waitForLoadState('networkidle') - // 3. Enable extension + // 2. Enable extension for this page await serviceWorker.evaluate(async () => { await globalThis.toggleExtensionForActiveTab() }) - // 4. Verify via CDP + // 3. Verify via CDP that the correct URL is shown const browser = await chromium.connectOverCDP(getCdpUrl()) // Wait for sync await new Promise(r => setTimeout(r, 1000)) @@ -712,6 +707,25 @@ describe('MCP Server Tests', () => { }) it('should capture browser console logs with getLatestLogs', async () => { + // Ensure clean state and clear any existing logs + const resetResult = await client.callTool({ + name: 'execute', + arguments: { + code: js` + // Clear any existing logs from previous tests + clearAllLogs(); + console.log('Cleared all existing logs'); + + // Verify connection is working + const pages = context.pages(); + console.log('Current pages count:', pages.length); + + return { success: true, pagesCount: pages.length }; + `, + }, + }) + console.log('Cleanup result:', resetResult) + // Create a new page for this test await client.callTool({ name: 'execute', @@ -844,6 +858,17 @@ describe('MCP Server Tests', () => { }, 30000) it('should keep logs separate between different pages', async () => { + // Clear any existing logs from previous tests + await client.callTool({ + name: 'execute', + arguments: { + code: js` + clearAllLogs(); + console.log('Cleared all existing logs for second log test'); + `, + }, + }) + // Create two pages await client.callTool({ name: 'execute', diff --git a/playwriter/src/mcp.ts b/playwriter/src/mcp.ts index 1b294da..958b5bf 100644 --- a/playwriter/src/mcp.ts +++ b/playwriter/src/mcp.ts @@ -58,6 +58,7 @@ interface VMContext { count?: number searchFilter?: string | RegExp }) => Promise + clearAllLogs: () => void require: NodeRequire import: (specifier: string) => Promise } @@ -126,7 +127,7 @@ async function ensureConnection(): Promise<{ browser: Browser; page: Page }> { const contexts = browser.contexts() const context = contexts.length > 0 ? contexts[0] : await browser.newContext() - // Set up console listener for all pages + // Set up console listener for all future pages context.on('page', (page) => { setupPageConsoleListener(page) }) @@ -134,8 +135,8 @@ async function ensureConnection(): Promise<{ browser: Browser; page: Page }> { const pages = context.pages() const page = pages.length > 0 ? pages[0] : await context.newPage() - // Set up console listener for existing pages - pages.forEach(p => setupPageConsoleListener(p)) + // Set up console listener for all existing pages (including the one we might have just created) + context.pages().forEach(p => setupPageConsoleListener(p)) state.browser = browser state.page = page @@ -146,55 +147,68 @@ async function ensureConnection(): Promise<{ browser: Browser; page: Page }> { } async function getPageTargetId(page: Page): Promise { + if (!page) { + throw new Error('Page is null or undefined') + } + + // Always use internal _guid for consistency and speed + const guid = (page as any)._guid + if (guid) { + return guid + } + try { - // Get CDP session and fetch target info + // Fallback to CDP if _guid is not available const client = await page.context().newCDPSession(page) const { targetInfo } = await client.send('Target.getTargetInfo') await client.detach() return targetInfo.targetId } catch (e) { - // Fallback to using internal _guid if CDP fails - const guid = (page as any)._guid - if (guid) { - return guid - } - throw new Error('Could not get page identifier') + throw new Error(`Could not get page identifier: ${e}`) } } function setupPageConsoleListener(page: Page) { - // Get targetId once when setting up the listener - getPageTargetId(page).then(targetId => { - // Clear logs on navigation/reload - page.on('framenavigated', (frame) => { - // Only clear if it's the main frame navigating (page reload/navigation) - if (frame === page.mainFrame()) { - browserLogs.set(targetId, []) - } - }) + // Get targetId synchronously using _guid + const targetId = (page as any)._guid as string | undefined + + if (!targetId) { + // If no _guid, silently fail - this shouldn't happen in normal operation + return + } + + // Initialize logs array for this page + if (!browserLogs.has(targetId)) { + browserLogs.set(targetId, []) + } + + // Clear logs on navigation/reload + page.on('framenavigated', (frame) => { + // Only clear if it's the main frame navigating (page reload/navigation) + if (frame === page.mainFrame()) { + browserLogs.set(targetId, []) + } + }) + + // Delete logs when page is closed + page.on('close', () => { + browserLogs.delete(targetId) + }) + + page.on('console', (msg) => { + const logEntry = `[${msg.type()}] ${msg.text()}` - // Delete logs when page is closed - page.on('close', () => { - browserLogs.delete(targetId) - }) + // Get or create logs array for this page targetId + if (!browserLogs.has(targetId)) { + browserLogs.set(targetId, []) + } + const pageLogs = browserLogs.get(targetId)! - page.on('console', (msg) => { - const logEntry = `[${msg.type()}] ${msg.text()}` - - // Get or create logs array for this page targetId - if (!browserLogs.has(targetId)) { - browserLogs.set(targetId, []) - } - const pageLogs = browserLogs.get(targetId)! - - pageLogs.push(logEntry) - if (pageLogs.length > MAX_LOGS_PER_PAGE) { - pageLogs.shift() - } - }) - }).catch(err => { - // Silently fail - page might be closed or CDP might not be available + pageLogs.push(logEntry) + if (pageLogs.length > MAX_LOGS_PER_PAGE) { + pageLogs.shift() + } }) } @@ -233,6 +247,9 @@ async function resetConnection(): Promise<{ browser: Browser; page: Page; contex state.page = null state.context = null state.isConnected = false + + // Clear all browser logs on reset + browserLogs.clear() await ensureRelayServer() @@ -242,7 +259,7 @@ async function resetConnection(): Promise<{ browser: Browser; page: Page; contex const contexts = browser.contexts() const context = contexts.length > 0 ? contexts[0] : await browser.newContext() - // Set up console listener for all pages + // Set up console listener for all future pages context.on('page', (page) => { setupPageConsoleListener(page) }) @@ -250,8 +267,8 @@ async function resetConnection(): Promise<{ browser: Browser; page: Page; contex const pages = context.pages() const page = pages.length > 0 ? pages[0] : await context.newPage() - // Set up console listener for existing pages - pages.forEach(p => setupPageConsoleListener(p)) + // Set up console listener for all existing pages (including the one we might have just created) + context.pages().forEach(p => setupPageConsoleListener(p)) state.browser = browser state.page = page @@ -421,6 +438,10 @@ server.tool( // Return all logs or limited count return count !== undefined ? allLogs.slice(-count) : allLogs } + + const clearAllLogs = () => { + browserLogs.clear() + } let vmContextObj: VMContextWithGlobals = { page, @@ -430,6 +451,7 @@ server.tool( accessibilitySnapshot, getLocatorStringForElement, getLatestLogs, + clearAllLogs, resetPlaywright: async () => { const { page: newPage, context: newContext } = await resetConnection() @@ -443,6 +465,7 @@ server.tool( accessibilitySnapshot, getLocatorStringForElement, getLatestLogs, + clearAllLogs, resetPlaywright: vmContextObj.resetPlaywright, require, import: vmContextObj.import,