/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import assert from 'node:assert';
import {describe, it} from 'node:test';
import type {ParsedArguments} from '../../src/bin/chrome-devtools-mcp-cli-options.js';
import {navigatePage} from '../../src/tools/pages.js';
import {serverHooks} from '../server.js';
import {withMcpContext} from '../utils.js';
describe('pages allowList', () => {
const server = serverHooks();
const args = {experimentalNavigationAllowlist: true} as ParsedArguments;
it('navigates through redirects when all URLs are allowed', async () => {
server.addRoute('/a.html', (_req, res) => {
res.writeHead(302, {Location: '/b.html'});
res.end();
});
server.addRoute('/b.html', (_req, res) => {
res.writeHead(302, {Location: '/c.html'});
res.end();
});
server.addHtmlRoute(
'/c.html',
'
Final Destination
',
);
await withMcpContext(async (response, context) => {
const page = context.getSelectedMcpPage();
const baseUrl = server.baseUrl;
const allowList = `${baseUrl}/a.html,${baseUrl}/b.html,${baseUrl}/c.html`;
await navigatePage(args).handler(
{
params: {
url: `${baseUrl}/a.html`,
allowList,
},
page,
},
response,
context,
);
assert.strictEqual(page.pptrPage.url(), `${baseUrl}/c.html`);
const content = await page.pptrPage.evaluate(
() => document.querySelector('h1')?.textContent,
);
assert.strictEqual(content, 'Final Destination');
});
});
it('blocks navigation when a redirect target is not allowed', async () => {
server.addRoute('/a.html', (_req, res) => {
res.writeHead(302, {Location: '/b.html'});
res.end();
});
server.addRoute('/b.html', (_req, res) => {
res.writeHead(302, {Location: '/c.html'});
res.end();
});
server.addHtmlRoute(
'/c.html',
'Final Destination
',
);
await withMcpContext(async (response, context) => {
const page = context.getSelectedMcpPage();
const baseUrl = server.baseUrl;
// b.html is missing from allowList
const allowList = `${baseUrl}/a.html,${baseUrl}/c.html`;
await navigatePage(args).handler(
{
params: {
url: `${baseUrl}/a.html`,
allowList,
timeout: 2000, // Short timeout for failure
},
page,
},
response,
context,
);
// The navigation to b.html should be blocked.
// Puppeteer's goto will likely throw a timeout or net::ERR_ABORTED error.
const url = page.pptrPage.url();
assert.notStrictEqual(url, `${baseUrl}/c.html`);
assert.ok(
response.responseLines.some(line =>
line.includes('Unable to navigate'),
),
);
});
});
});