Files
puppeteer/test/src/console.test.ts
T

330 lines
11 KiB
TypeScript

/**
* @license
* Copyright 2026 Google Inc.
* SPDX-License-Identifier: Apache-2.0
*/
import expect from 'expect';
import type {ConsoleMessage} from 'puppeteer-core/internal/common/ConsoleMessage.js';
import {getTestState, setupTestBrowserHooks} from './mocha-utils.js';
import {html, waitEvent} from './utils.js';
describe('console', function () {
setupTestBrowserHooks();
it('should work', async () => {
const {page} = await getTestState();
const [message] = await Promise.all([
waitEvent<ConsoleMessage>(page, 'console'),
page.evaluate(() => {
return console.log('hello', 5, {foo: 'bar'});
}),
]);
expect(message.text()).atLeastOneToContain([
'hello 5 [object Object]',
'hello 5 JSHandle@object', // WebDriver BiDi
]);
expect(message.type()).toEqual('log');
expect(message.args()).toHaveLength(3);
expect(await message.args()[0]!.jsonValue()).toEqual('hello');
expect(await message.args()[1]!.jsonValue()).toEqual(5);
expect(await message.args()[2]!.jsonValue()).toEqual({foo: 'bar'});
});
it('should work for Error instances', async () => {
const {page} = await getTestState();
const [message] = await Promise.all([
waitEvent<ConsoleMessage>(page, 'console'),
page.evaluate(() => {
return console.log(new Error('test error'));
}),
]);
expect(message.text()).toEqual('Error: test error');
expect(message.type()).toEqual('log');
expect(message.args()).toHaveLength(1);
});
it('should return the first line of the error message in text()', async () => {
const {page} = await getTestState();
const [message] = await Promise.all([
waitEvent<ConsoleMessage>(page, 'console'),
page.evaluate(() => {
return console.log(new Error('test error\nsecond line'));
}),
]);
expect(message.text()).toEqual('Error: test error');
expect(message.type()).toEqual('log');
expect(message.args()).toHaveLength(1);
});
it('should work on script call right after navigation', async () => {
const {page} = await getTestState();
const [message] = await Promise.all([
waitEvent<ConsoleMessage>(page, 'console'),
page.goto(
// Firefox prints warn if <!DOCTYPE html> is not present
`data:text/html,<!DOCTYPE html><script>console.log('SOME_LOG_MESSAGE');</script>`,
),
]);
expect(message.text()).toEqual('SOME_LOG_MESSAGE');
});
it('should work for console.trace', async () => {
const {page} = await getTestState();
const [message] = await Promise.all([
waitEvent<ConsoleMessage>(page, 'console'),
page.evaluate(() => {
console.trace('calling console.trace');
}),
]);
expect(message.type()).toBe('trace');
expect(message.text()).toBe('calling console.trace');
});
it('should work for console.dir', async () => {
const {page} = await getTestState();
const [message] = await Promise.all([
waitEvent<ConsoleMessage>(page, 'console'),
page.evaluate(() => {
console.dir('calling console.dir');
}),
]);
expect(message.type()).toBe('dir');
expect(message.text()).toBe('calling console.dir');
});
it('should work for console.warn', async () => {
const {page} = await getTestState();
const [message] = await Promise.all([
waitEvent<ConsoleMessage>(page, 'console'),
page.evaluate(() => {
console.warn('calling console.warn');
}),
]);
expect(message.type()).toBe('warn');
expect(message.text()).toBe('calling console.warn');
});
it('should work for console.error', async () => {
const {page} = await getTestState();
const [message] = await Promise.all([
waitEvent<ConsoleMessage>(page, 'console'),
page.evaluate(() => {
console.error('calling console.error');
}),
]);
expect(message.type()).toBe('error');
expect(message.text()).toBe('calling console.error');
});
it('should work for console.log with promise', async () => {
const {page} = await getTestState();
const [message] = await Promise.all([
waitEvent<ConsoleMessage>(page, 'console'),
page.evaluate(() => {
console.log(Promise.resolve('should not wait until resolved!'));
}),
]);
expect(message.type()).toBe('log');
expect(message.text()).atLeastOneToContain([
'[promise Promise]',
'JSHandle@promise', // WebDriver BiDi expectation.
]);
});
it('should work for different console API calls with timing functions', async () => {
const {page} = await getTestState();
const messages: any[] = [];
page.on('console', msg => {
return messages.push(msg);
});
// All console events will be reported before `page.evaluate` is finished.
await page.evaluate(() => {
// A pair of time/timeEnd generates only one Console API call.
console.time('calling console.time');
console.timeEnd('calling console.time');
});
expect(
messages.map(msg => {
return msg.type();
}),
).toEqual(['timeEnd']);
expect(messages[0]!.text()).toContain('calling console.time');
});
it('should work for different console API calls with group functions', async () => {
const {page} = await getTestState();
const messages: ConsoleMessage[] = [];
page.on('console', msg => {
return messages.push(msg);
});
// All console events will be reported before `page.evaluate` is finished.
await page.evaluate(() => {
console.group('calling console.group');
console.groupEnd();
});
expect(
messages.map(msg => {
return msg.type();
}),
).toEqual(['startGroup', 'endGroup']);
// We should be able to check both messages, but Chrome report text
expect(messages[0]!.text()).toContain('calling console.group');
});
it('should not fail for window object', async () => {
const {page} = await getTestState();
const [message] = await Promise.all([
waitEvent<ConsoleMessage>(page, 'console'),
page.evaluate(() => {
return console.error(window);
}),
]);
expect(message.text()).atLeastOneToContain([
'[object Window]',
'JSHandle@window', // WebDriver BiDi
]);
});
it('should return remote objects', async () => {
const {page} = await getTestState();
const logPromise = waitEvent<ConsoleMessage>(page, 'console');
await page.evaluate(() => {
(globalThis as any).test = 1;
console.log(1, 2, 3, globalThis);
});
const log = await logPromise;
expect(log.text()).atLeastOneToContain([
'1 2 3 [object Window]',
'1 2 3 JSHandle@window', // WebDriver BiDi
]);
expect(log.args()).toHaveLength(4);
using property = await log.args()[3]!.getProperty('test');
expect(await property.jsonValue()).toBe(1);
});
it('should not dispose handles when page has listeners', async () => {
const {page} = await getTestState();
const [message] = await Promise.all([
waitEvent<ConsoleMessage>(page, 'console'),
page.evaluate(() => {
return console.log({foo: 'bar'});
}),
]);
using handle = message.args()[0]!;
expect(handle.disposed).toBe(false);
expect(await handle.jsonValue()).toEqual({foo: 'bar'});
await handle.dispose();
expect(handle.disposed).toBe(true);
});
it('should trigger correct Log', async () => {
const {page, server, isChrome} = await getTestState();
await page.goto(`http://domain1.test:${server.PORT}/empty.html`);
const [message] = await Promise.all([
waitEvent(page, 'console'),
page.evaluate(async url => {
return await fetch(url).catch(() => {});
}, `http://domain2.test:${server.PORT}/empty.html`),
]);
expect(message.text()).toContain('Access-Control-Allow-Origin');
if (isChrome) {
expect(message.type()).toEqual('error');
} else {
expect(message.type()).toEqual('warn');
}
});
it('should have location when fetch fails', async () => {
const {page, server} = await getTestState();
// The point of this test is to make sure that we report console messages from
// Log domain: https://vanilla.aslushnikov.com/?Log.entryAdded
await page.goto(server.EMPTY_PAGE);
const [message] = await Promise.all([
waitEvent(page, 'console'),
page.setContent(
html`<script>
fetch('http://wat');
</script>`,
),
]);
expect(message.text()).toContain(`ERR_NAME_NOT_RESOLVED`);
expect(message.type()).toEqual('error');
expect(message.location()).toEqual({
url: 'http://wat/',
lineNumber: undefined,
});
});
it('should have location and stack trace for console API calls', async () => {
const {page, server} = await getTestState();
await page.goto(server.EMPTY_PAGE);
const [message] = await Promise.all([
waitEvent(page, 'console'),
page.goto(server.PREFIX + '/consoletrace.html'),
]);
expect(message.text()).toBe('yellow');
expect(message.type()).toBe('trace');
expect(message.location()).toEqual({
url: server.PREFIX + '/consoletrace.html',
lineNumber: 8,
columnNumber: 16,
});
expect(message.stackTrace()).toEqual([
{
url: server.PREFIX + '/consoletrace.html',
lineNumber: 8,
columnNumber: 16,
},
{
url: server.PREFIX + '/consoletrace.html',
lineNumber: 11,
columnNumber: 8,
},
{
url: server.PREFIX + '/consoletrace.html',
lineNumber: 13,
columnNumber: 6,
},
]);
});
// @see https://github.com/puppeteer/puppeteer/issues/3865
it('should not throw when there are console messages in detached iframes', async () => {
const {page, server} = await getTestState();
await page.goto(server.EMPTY_PAGE);
await page.evaluate(async () => {
// 1. Create a popup that Puppeteer is not connected to.
const win = window.open(
window.location.href,
'Title',
'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=780,height=200,top=0,left=0',
)!;
await new Promise(x => {
return (win.onload = x);
});
// 2. In this popup, create an iframe that console.logs a message.
win.document.body.innerHTML = `<iframe src='/consolelog.html'></iframe>`;
const frame = win.document.querySelector('iframe')!;
await new Promise(x => {
return (frame.onload = x);
});
// 3. After that, remove the iframe.
frame.remove();
});
// 4. The target will always be the last one.
const popupTarget = page.browserContext().targets().at(-1)!;
// 5. Connect to the popup and make sure it doesn't throw and is not the same page.
expect(await popupTarget.page()).not.toBe(page);
});
});