670 lines
26 KiB
TypeScript
670 lines
26 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import assert from 'node:assert';
|
|
import path from 'node:path';
|
|
import {before, describe, it} from 'node:test';
|
|
|
|
import type {Dialog} from 'puppeteer-core';
|
|
|
|
import type {ParsedArguments} from '../../src/bin/chrome-devtools-mcp-cli-options.js';
|
|
import {loadIssueDescriptions} from '../../src/issue-descriptions.js';
|
|
import {McpResponse} from '../../src/McpResponse.js';
|
|
import {TextSnapshot} from '../../src/TextSnapshot.js';
|
|
import type {CdpWebWorker} from '../../src/third_party/index.js';
|
|
import {DevTools} from '../../src/third_party/index.js';
|
|
import {
|
|
getConsoleMessage,
|
|
listConsoleMessages,
|
|
} from '../../src/tools/console.js';
|
|
import {installExtension} from '../../src/tools/extensions.js';
|
|
import {serverHooks} from '../server.js';
|
|
import {
|
|
getTextContent,
|
|
withMcpContext,
|
|
stabilizeStructuredContent,
|
|
extractExtensionId,
|
|
assertNoServiceWorkerReported,
|
|
waitExecutionFor,
|
|
} from '../utils.js';
|
|
|
|
const EXTENSION_LOGGING_PATH = path.join(
|
|
import.meta.dirname,
|
|
'../../../tests/tools/fixtures/extension-logging',
|
|
);
|
|
|
|
describe('console', () => {
|
|
before(async () => {
|
|
await loadIssueDescriptions();
|
|
});
|
|
|
|
it('captures logs and errors from extension service worker', async t => {
|
|
await withMcpContext(
|
|
async (response, context) => {
|
|
await installExtension.handler(
|
|
{params: {path: EXTENSION_LOGGING_PATH}},
|
|
response,
|
|
context,
|
|
);
|
|
|
|
const extensionId = extractExtensionId(response);
|
|
assert.ok(extensionId, 'Extension ID should be returned');
|
|
|
|
const swTarget = await context.browser.waitForTarget(
|
|
t => t.type() === 'service_worker' && t.url().includes(extensionId),
|
|
);
|
|
|
|
const swList = await context.createExtensionServiceWorkersSnapshot();
|
|
const sw = swList.find(s => s.target === swTarget);
|
|
assert(sw, 'Service worker not found in context list');
|
|
|
|
const response2 = new McpResponse({} as ParsedArguments);
|
|
|
|
await context.triggerExtensionAction(extensionId);
|
|
const worker = await swTarget.worker();
|
|
|
|
const timeout = 10000;
|
|
await waitExecutionFor(async () => {
|
|
await worker?.evaluate(() => {
|
|
if (typeof globalThis.setTimeout !== 'function') {
|
|
throw new Error('Not ready');
|
|
}
|
|
});
|
|
}, timeout);
|
|
|
|
const errorPromise = new Promise((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
reject(new Error('Timeout waiting for Service Worker error'));
|
|
}, 5000);
|
|
(worker as unknown as CdpWebWorker).client.on(
|
|
'Runtime.exceptionThrown',
|
|
() => {
|
|
clearTimeout(timeout);
|
|
resolve(undefined);
|
|
},
|
|
);
|
|
});
|
|
|
|
worker?.evaluate(() => {
|
|
globalThis.setTimeout(() => {
|
|
throw new Error('Intentional error from Service Worker');
|
|
}, 100);
|
|
});
|
|
|
|
await errorPromise;
|
|
response2.resetResponseLineForTesting();
|
|
|
|
await listConsoleMessages({
|
|
categoryExtensions: true,
|
|
} as ParsedArguments).handler(
|
|
{
|
|
params: {serviceWorkerId: extensionId},
|
|
page: context.getSelectedMcpPage(),
|
|
},
|
|
response2,
|
|
context,
|
|
);
|
|
|
|
const formattedResponse = await response2.handle('test', context);
|
|
const textContent = getTextContent(formattedResponse.content[0]);
|
|
|
|
const sanitizedText = textContent.replaceAll(
|
|
new RegExp(extensionId, 'g'),
|
|
'<extension-id>',
|
|
);
|
|
|
|
t.assert.snapshot?.(sanitizedText);
|
|
|
|
assert.ok(
|
|
sanitizedText.includes('Service Worker starting...'),
|
|
'Should contain start log',
|
|
);
|
|
assert.ok(
|
|
sanitizedText.includes('This is a warning from Service Worker'),
|
|
'Should contain warning log',
|
|
);
|
|
assert.ok(
|
|
sanitizedText.includes('Intentional error from Service Worker'),
|
|
'Should contain error log',
|
|
);
|
|
|
|
await context.uninstallExtension(extensionId);
|
|
const targets = context.browser.targets();
|
|
assertNoServiceWorkerReported(targets, extensionId);
|
|
},
|
|
{},
|
|
{
|
|
categoryExtensions: true,
|
|
} as ParsedArguments,
|
|
);
|
|
});
|
|
|
|
describe('list_console_messages', () => {
|
|
it('list messages', async () => {
|
|
await withMcpContext(async (response, context) => {
|
|
await listConsoleMessages().handler(
|
|
{params: {}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
assert.ok(response.includeConsoleData);
|
|
});
|
|
});
|
|
|
|
it('lists error messages', async () => {
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedMcpPage();
|
|
await page.pptrPage.setContent(
|
|
'<script>console.error("This is an error")</script>',
|
|
);
|
|
await listConsoleMessages().handler(
|
|
{params: {}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
const formattedResponse = await response.handle('test', context);
|
|
const textContent = getTextContent(formattedResponse.content[0]);
|
|
assert.ok(textContent.includes('msgid=1 [error] This is an error'));
|
|
});
|
|
});
|
|
|
|
it('lists error objects', async t => {
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedMcpPage();
|
|
await page.pptrPage.setContent(
|
|
'<script>console.error(new Error("This is an error"))</script>',
|
|
);
|
|
await listConsoleMessages().handler(
|
|
{params: {}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
const formattedResponse = await response.handle('test', context);
|
|
const textContent = getTextContent(formattedResponse.content[0]);
|
|
t.assert.snapshot(textContent);
|
|
});
|
|
});
|
|
|
|
it('work with primitive unhandled errors', async () => {
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedMcpPage();
|
|
await page.pptrPage.setContent('<script>throw undefined;</script>');
|
|
await listConsoleMessages().handler(
|
|
{params: {}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
const formattedResponse = await response.handle('test', context);
|
|
const textContent = getTextContent(formattedResponse.content[0]);
|
|
assert.ok(textContent.includes('msgid=1 [error] Uncaught (0 args)'));
|
|
});
|
|
});
|
|
|
|
describe('issues', () => {
|
|
it('lists issues', async () => {
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedMcpPage();
|
|
const issuePromise = new Promise<void>(resolve => {
|
|
page.pptrPage.once('issue', () => {
|
|
resolve();
|
|
});
|
|
});
|
|
await page.pptrPage.setContent(
|
|
'<input type="text" name="username" />',
|
|
);
|
|
await issuePromise;
|
|
await listConsoleMessages().handler(
|
|
{params: {}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
const formattedResponse = await response.handle('test', context);
|
|
const textContent = getTextContent(formattedResponse.content[0]);
|
|
assert.ok(
|
|
textContent.includes(
|
|
`msgid=1 [issue] An element doesn't have an autocomplete attribute (count: 1)`,
|
|
),
|
|
);
|
|
});
|
|
});
|
|
|
|
it('lists issues after a page reload', async () => {
|
|
await withMcpContext(async (response, context) => {
|
|
const page = await context.newPage();
|
|
response.setPage(page);
|
|
const issuePromise = new Promise<void>(resolve => {
|
|
page.pptrPage.once('issue', () => {
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
await page.pptrPage.setContent(
|
|
'<input type="text" name="username" />',
|
|
);
|
|
await issuePromise;
|
|
await listConsoleMessages().handler(
|
|
{params: {}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
{
|
|
const formattedResponse = await response.handle('test', context);
|
|
const textContent = getTextContent(formattedResponse.content[0]);
|
|
assert.ok(
|
|
textContent.includes(
|
|
`msgid=1 [issue] An element doesn't have an autocomplete attribute (count: 1)`,
|
|
),
|
|
);
|
|
}
|
|
|
|
const anotherIssuePromise = new Promise<void>(resolve => {
|
|
page.pptrPage.once('issue', () => {
|
|
resolve();
|
|
});
|
|
});
|
|
await page.pptrPage.reload();
|
|
await page.pptrPage.setContent(
|
|
'<input type="text" name="username" />',
|
|
);
|
|
await anotherIssuePromise;
|
|
{
|
|
const formattedResponse = await response.handle('test', context);
|
|
const textContent = getTextContent(formattedResponse.content[0]);
|
|
assert.ok(
|
|
textContent.includes(
|
|
`msgid=2 [issue] An element doesn't have an autocomplete attribute (count: 1)`,
|
|
),
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
it('when dialog is open', async t => {
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedMcpPage().pptrPage;
|
|
await page.setContent(
|
|
'<script>console.log("Pre-dialog message")</script>',
|
|
);
|
|
|
|
const dialogPromise = new Promise<Dialog>(resolve => {
|
|
page.on('dialog', dialog => resolve(dialog));
|
|
});
|
|
|
|
page.evaluate(() => {
|
|
alert('test dialog');
|
|
});
|
|
const dialog = await dialogPromise;
|
|
|
|
await listConsoleMessages().handler(
|
|
{params: {}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
|
|
const result = await response.handle(
|
|
'list_console_messages',
|
|
context,
|
|
);
|
|
t.assert.snapshot(JSON.stringify(result));
|
|
await dialog.dismiss();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('get_console_message', () => {
|
|
const server = serverHooks();
|
|
|
|
it('gets a specific console message', async () => {
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedMcpPage();
|
|
await page.pptrPage.setContent(
|
|
'<script>console.error("This is an error")</script>',
|
|
);
|
|
// The list is needed to populate the console messages in the context.
|
|
await listConsoleMessages().handler(
|
|
{params: {}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
await getConsoleMessage.handler(
|
|
{params: {msgid: 1}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
const formattedResponse = await response.handle('test', context);
|
|
const textContent = getTextContent(formattedResponse.content[0]);
|
|
assert.ok(
|
|
textContent.includes('msgid=1 [error] This is an error'),
|
|
'Should contain console message body',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('issues type', () => {
|
|
it('gets issue details with node id parsing', async t => {
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedMcpPage();
|
|
const issuePromise = new Promise<void>(resolve => {
|
|
page.pptrPage.once('issue', () => {
|
|
resolve();
|
|
});
|
|
});
|
|
await page.pptrPage.setContent(
|
|
'<input type="text" name="username" />',
|
|
);
|
|
page.textSnapshot = await TextSnapshot.create(page);
|
|
await issuePromise;
|
|
await listConsoleMessages().handler(
|
|
{params: {}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
const response2 = new McpResponse({} as ParsedArguments);
|
|
response2.setPage(context.getSelectedMcpPage());
|
|
await getConsoleMessage.handler(
|
|
{params: {msgid: 1}, page: context.getSelectedMcpPage()},
|
|
response2,
|
|
context,
|
|
);
|
|
const formattedResponse = await response2.handle('test', context);
|
|
t.assert.snapshot(getTextContent(formattedResponse.content[0]));
|
|
});
|
|
});
|
|
it('gets issue details with request id parsing', async t => {
|
|
server.addRoute('/data.json', (_req, res) => {
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.statusCode = 200;
|
|
res.end(JSON.stringify({data: 'test data'}));
|
|
});
|
|
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedMcpPage();
|
|
const issuePromise = new Promise<void>(resolve => {
|
|
page.pptrPage.once('issue', () => {
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
const url = server.getRoute('/data.json');
|
|
await page.pptrPage.setContent(`
|
|
<script>
|
|
fetch('${url}', {
|
|
method: 'GET',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Custom-Header': 'MyValue'
|
|
}
|
|
});
|
|
</script>
|
|
`);
|
|
page.textSnapshot = await TextSnapshot.create(page);
|
|
await issuePromise;
|
|
const messages = page.getConsoleData();
|
|
let issueMsg;
|
|
for (const message of messages) {
|
|
if (message instanceof DevTools.AggregatedIssue) {
|
|
issueMsg = message;
|
|
break;
|
|
}
|
|
}
|
|
assert.ok(issueMsg);
|
|
const id = context
|
|
.getSelectedMcpPage()
|
|
.getConsoleMessageStableId(issueMsg);
|
|
assert.ok(id);
|
|
await listConsoleMessages().handler(
|
|
{params: {types: ['issue']}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
const response2 = new McpResponse({} as ParsedArguments);
|
|
response2.setPage(context.getSelectedMcpPage());
|
|
await getConsoleMessage.handler(
|
|
{params: {msgid: id}, page: context.getSelectedMcpPage()},
|
|
response2,
|
|
context,
|
|
);
|
|
const formattedResponse = await response2.handle('test', context);
|
|
const rawText = getTextContent(formattedResponse.content[0]);
|
|
const sanitizedText = rawText
|
|
.replaceAll(/ID: \d+/g, 'ID: <ID>')
|
|
.replaceAll(/reqid=\d+/g, 'reqid=<reqid>')
|
|
.replaceAll(/localhost:\d+/g, 'hostname:port');
|
|
t.assert.snapshot(sanitizedText);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('applies source maps to stack traces of console messages', async t => {
|
|
server.addRoute('/main.min.js', (_req, res) => {
|
|
res.setHeader('Content-Type', 'text/javascript');
|
|
res.statusCode = 200;
|
|
res.end(`function n(){console.warn("hello world")}function o(){n()}(function n(){o()})();
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJiYXIiLCJjb25zb2xlIiwid2FybiIsImZvbyIsIklpZmUiXSwic291cmNlcyI6WyIuL21haW4uanMiXSwic291cmNlc0NvbnRlbnQiOlsiXG5mdW5jdGlvbiBiYXIoKSB7XG4gIGNvbnNvbGUud2FybignaGVsbG8gd29ybGQnKTtcbn1cblxuZnVuY3Rpb24gZm9vKCkge1xuICBiYXIoKTtcbn1cblxuKGZ1bmN0aW9uIElpZmUoKSB7XG4gIGZvbygpO1xufSkoKTtcblxuIl0sIm1hcHBpbmdzIjoiQUFDQSxTQUFTQSxJQUNQQyxRQUFRQyxLQUFLLGNBQ2YsQ0FFQSxTQUFTQyxJQUNQSCxHQUNGLEVBRUEsU0FBVUksSUFDUkQsR0FDRCxFQUZEIiwiaWdub3JlTGlzdCI6W119
|
|
`);
|
|
});
|
|
server.addHtmlRoute(
|
|
'/index.html',
|
|
`<script src="${server.getRoute('/main.min.js')}"></script>`,
|
|
);
|
|
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedMcpPage();
|
|
await page.pptrPage.goto(server.getRoute('/index.html'));
|
|
|
|
await getConsoleMessage.handler(
|
|
{params: {msgid: 1}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
const formattedResponse = await response.handle('test', context);
|
|
const rawText = getTextContent(formattedResponse.content[0]);
|
|
|
|
t.assert.snapshot(rawText);
|
|
});
|
|
});
|
|
|
|
it('applies source maps to stack traces of uncaught exceptions', async t => {
|
|
server.addRoute('/main.min.js', (_req, res) => {
|
|
res.setHeader('Content-Type', 'text/javascript');
|
|
res.statusCode = 200;
|
|
res.end(`function n(){throw new Error("b00m!")}function o(){n()}(function n(){o()})();
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJiYXIiLCJFcnJvciIsImZvbyIsIklpZmUiXSwic291cmNlcyI6WyIuL21haW4uanMiXSwic291cmNlc0NvbnRlbnQiOlsiXG5mdW5jdGlvbiBiYXIoKSB7XG4gIHRocm93IG5ldyBFcnJvcignYjAwbSEnKTtcbn1cblxuZnVuY3Rpb24gZm9vKCkge1xuICBiYXIoKTtcbn1cblxuKGZ1bmN0aW9uIElpZmUoKSB7XG4gIGZvbygpO1xufSkoKTtcblxuIl0sIm1hcHBpbmdzIjoiQUFDQSxTQUFTQSxJQUNQLE1BQU0sSUFBSUMsTUFBTSxRQUNsQixDQUVBLFNBQVNDLElBQ1BGLEdBQ0YsRUFFQSxTQUFVRyxJQUNSRCxHQUNELEVBRkQiLCJpZ25vcmVMaXN0IjpbXX0=
|
|
`);
|
|
});
|
|
server.addHtmlRoute(
|
|
'/index.html',
|
|
`<script src="${server.getRoute('/main.min.js')}"></script>`,
|
|
);
|
|
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedMcpPage();
|
|
await page.pptrPage.goto(server.getRoute('/index.html'));
|
|
|
|
await getConsoleMessage.handler(
|
|
{params: {msgid: 1}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
const formattedResponse = await response.handle('test', context);
|
|
const rawText = getTextContent(formattedResponse.content[0]);
|
|
|
|
t.assert.snapshot(rawText);
|
|
});
|
|
});
|
|
|
|
it('applies source maps to stack traces of Error object console.log arguments', async t => {
|
|
server.addRoute('/main.min.js', (_req, res) => {
|
|
res.setHeader('Content-Type', 'text/javascript');
|
|
res.statusCode = 200;
|
|
res.end(`function n(){throw new Error("b00m!")}function o(){n()}(function n(){try{o()}catch(n){console.log("An error happened:",n)}})();
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJiYXIiLCJFcnJvciIsImZvbyIsIklpZmUiLCJlIiwiY29uc29sZSIsImxvZyJdLCJzb3VyY2VzIjpbIi4vbWFpbi5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJcbmZ1bmN0aW9uIGJhcigpIHtcbiAgdGhyb3cgbmV3IEVycm9yKCdiMDBtIScpO1xufVxuXG5mdW5jdGlvbiBmb28oKSB7XG4gIGJhcigpO1xufVxuXG4oZnVuY3Rpb24gSWlmZSgpIHtcbiAgdHJ5IHtcbiAgICBmb28oKTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIGNvbnNvbGUubG9nKCdBbiBlcnJvciBoYXBwZW5lZDonLCBlKTtcbiAgfVxufSkoKTtcblxuIl0sIm1hcHBpbmdzIjoiQUFDQSxTQUFTQSxJQUNQLE1BQU0sSUFBSUMsTUFBTSxRQUNsQixDQUVBLFNBQVNDLElBQ1BGLEdBQ0YsRUFFQSxTQUFVRyxJQUNSLElBQ0VELEdBQ0YsQ0FBRSxNQUFPRSxHQUNQQyxRQUFRQyxJQUFJLHFCQUFzQkYsRUFDcEMsQ0FDRCxFQU5EIiwiaWdub3JlTGlzdCI6W119
|
|
`);
|
|
});
|
|
server.addHtmlRoute(
|
|
'/index.html',
|
|
`<script src="${server.getRoute('/main.min.js')}"></script>`,
|
|
);
|
|
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedMcpPage();
|
|
await page.pptrPage.goto(server.getRoute('/index.html'));
|
|
|
|
await getConsoleMessage.handler(
|
|
{params: {msgid: 1}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
const formattedResponse = await response.handle('test', context);
|
|
const rawText = getTextContent(formattedResponse.content[0]);
|
|
|
|
t.assert.snapshot(rawText);
|
|
});
|
|
});
|
|
|
|
it('applies source maps to stack traces of uncaught exceptions with cause', async t => {
|
|
server.addRoute('/main.min.js', (_req, res) => {
|
|
res.setHeader('Content-Type', 'text/javascript');
|
|
res.statusCode = 200;
|
|
res.end(`function r(){throw new Error("b00m!")}function o(){try{r()}catch(r){throw new Error("bar failed",{cause:r})}}(function r(){try{o()}catch(r){throw new Error("foo failed",{cause:r})}})();
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJiYXIiLCJFcnJvciIsImZvbyIsImUiLCJjYXVzZSIsIklpZmUiXSwic291cmNlcyI6WyIuL21haW4uanMiXSwic291cmNlc0NvbnRlbnQiOlsiXG5mdW5jdGlvbiBiYXIoKSB7XG4gIHRocm93IG5ldyBFcnJvcignYjAwbSEnKTtcbn1cblxuZnVuY3Rpb24gZm9vKCkge1xuICB0cnkge1xuICAgIGJhcigpO1xuICB9IGNhdGNoIChlKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdiYXIgZmFpbGVkJywgeyBjYXVzZTogZSB9KTtcbiAgfVxufVxuXG4oZnVuY3Rpb24gSWlmZSgpIHtcbiAgdHJ5IHtcbiAgICBmb28oKTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIHRocm93IG5ldyBFcnJvcignZm9vIGZhaWxlZCcsIHsgY2F1c2U6IGUgfSk7XG4gIH1cbn0pKCk7XG5cbiJdLCJtYXBwaW5ncyI6IkFBQ0EsU0FBU0EsSUFDUCxNQUFNLElBQUlDLE1BQU0sUUFDbEIsQ0FFQSxTQUFTQyxJQUNQLElBQ0VGLEdBQ0YsQ0FBRSxNQUFPRyxHQUNQLE1BQU0sSUFBSUYsTUFBTSxhQUFjLENBQUVHLE1BQU9ELEdBQ3pDLENBQ0YsRUFFQSxTQUFVRSxJQUNSLElBQ0VILEdBQ0YsQ0FBRSxNQUFPQyxHQUNQLE1BQU0sSUFBSUYsTUFBTSxhQUFjLENBQUVHLE1BQU9ELEdBQ3pDLENBQ0QsRUFORCIsImlnbm9yZUxpc3QiOltdfQ==
|
|
`);
|
|
});
|
|
server.addHtmlRoute(
|
|
'/index.html',
|
|
`<script src="${server.getRoute('/main.min.js')}"></script>`,
|
|
);
|
|
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedMcpPage();
|
|
await page.pptrPage.goto(server.getRoute('/index.html'));
|
|
|
|
await getConsoleMessage.handler(
|
|
{params: {msgid: 1}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
const formattedResponse = await response.handle('test', context);
|
|
const rawText = getTextContent(formattedResponse.content[0]);
|
|
|
|
t.assert.snapshot(rawText);
|
|
});
|
|
});
|
|
|
|
it('applies source maps to stack traces of Error object (with cause) console.log arguments', async t => {
|
|
server.addRoute('/main.min.js', (_req, res) => {
|
|
res.setHeader('Content-Type', 'text/javascript');
|
|
res.statusCode = 200;
|
|
res.end(`function o(){throw new Error("b00m!")}function r(){try{o()}catch(o){throw new Error("bar failed",{cause:o})}}(function o(){try{r()}catch(o){console.log("foo failed",o)}})();
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJiYXIiLCJFcnJvciIsImZvbyIsImUiLCJjYXVzZSIsIklpZmUiLCJjb25zb2xlIiwibG9nIl0sInNvdXJjZXMiOlsiLi9tYWluLmpzIl0sInNvdXJjZXNDb250ZW50IjpbIlxuZnVuY3Rpb24gYmFyKCkge1xuICB0aHJvdyBuZXcgRXJyb3IoJ2IwMG0hJyk7XG59XG5cbmZ1bmN0aW9uIGZvbygpIHtcbiAgdHJ5IHtcbiAgICBiYXIoKTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIHRocm93IG5ldyBFcnJvcignYmFyIGZhaWxlZCcsIHsgY2F1c2U6IGUgfSk7XG4gIH1cbn1cblxuKGZ1bmN0aW9uIElpZmUoKSB7XG4gIHRyeSB7XG4gICAgZm9vKCk7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBjb25zb2xlLmxvZygnZm9vIGZhaWxlZCcsIGUpO1xuICB9XG59KSgpO1xuXG4iXSwibWFwcGluZ3MiOiJBQUNBLFNBQVNBLElBQ1AsTUFBTSxJQUFJQyxNQUFNLFFBQ2xCLENBRUEsU0FBU0MsSUFDUCxJQUNFRixHQUNGLENBQUUsTUFBT0csR0FDUCxNQUFNLElBQUlGLE1BQU0sYUFBYyxDQUFFRyxNQUFPRCxHQUN6QyxDQUNGLEVBRUEsU0FBVUUsSUFDUixJQUNFSCxHQUNGLENBQUUsTUFBT0MsR0FDUEcsUUFBUUMsSUFBSSxhQUFjSixFQUM1QixDQUNELEVBTkQiLCJpZ25vcmVMaXN0IjpbXX0=
|
|
`);
|
|
});
|
|
server.addHtmlRoute(
|
|
'/index.html',
|
|
`<script src="${server.getRoute('/main.min.js')}"></script>`,
|
|
);
|
|
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedMcpPage();
|
|
await page.pptrPage.goto(server.getRoute('/index.html'));
|
|
|
|
await getConsoleMessage.handler(
|
|
{params: {msgid: 1}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
const formattedResponse = await response.handle('test', context);
|
|
const rawText = getTextContent(formattedResponse.content[0]);
|
|
|
|
t.assert.snapshot(rawText);
|
|
});
|
|
});
|
|
|
|
it('ignores frames from ignore listed URLs', async t => {
|
|
server.addHtmlRoute(
|
|
'/index.html',
|
|
`<!DOCTYPE html>
|
|
<script>
|
|
function ignoredFn1(cb) {
|
|
ignoredFn2(cb);
|
|
}
|
|
|
|
function ignoredFn2(cb) {
|
|
cb();
|
|
}
|
|
//# sourceURL=./node_modules/foo.js
|
|
</script>
|
|
<script>
|
|
function callback() {
|
|
console.log('hello from callback');
|
|
}
|
|
|
|
(function callIt() {
|
|
ignoredFn1(callback);
|
|
})();
|
|
//# sourceURL='main.js'
|
|
</script>
|
|
`,
|
|
);
|
|
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedMcpPage();
|
|
await page.pptrPage.goto(server.getRoute('/index.html'));
|
|
|
|
await getConsoleMessage.handler(
|
|
{params: {msgid: 1}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
const formattedResponse = await response.handle('test', context);
|
|
const rawText = getTextContent(formattedResponse.content[0]);
|
|
|
|
t.assert.snapshot(rawText);
|
|
});
|
|
});
|
|
|
|
it('when dialog is open', async t => {
|
|
await withMcpContext(async (response, context) => {
|
|
const page = context.getSelectedMcpPage().pptrPage;
|
|
await page.setContent(
|
|
'<script>console.error("This is an error")</script>',
|
|
);
|
|
|
|
await listConsoleMessages().handler(
|
|
{params: {}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
|
|
const dialogPromise = new Promise<Dialog>(resolve => {
|
|
page.on('dialog', dialog => resolve(dialog));
|
|
});
|
|
page.evaluate(() => {
|
|
alert('test dialog');
|
|
});
|
|
const dialog = await dialogPromise;
|
|
|
|
await getConsoleMessage.handler(
|
|
{params: {msgid: 1}, page: context.getSelectedMcpPage()},
|
|
response,
|
|
context,
|
|
);
|
|
|
|
const result = await response.handle('get_console_message', context);
|
|
t.assert.snapshot(
|
|
JSON.stringify(
|
|
stabilizeStructuredContent(result.structuredContent),
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
await dialog.dismiss();
|
|
});
|
|
});
|
|
});
|
|
});
|