Skip to content

Commit f1afe84

Browse files
authored
test: add tests for checking behavior when dialog is open (#1977)
This is the first part of adding tests for checking tool behavior when a dialog is already open (related to #1069) In future CL, I will be focusing on tools which currently get blocked due to open dialogs. As part of those CLs, proactive rejection of tool execution will be implemented.
1 parent bf3cb58 commit f1afe84

5 files changed

Lines changed: 303 additions & 1 deletion

File tree

tests/tools/console.test.js.snapshot

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,47 @@ Learn more:
118118
reqid=<reqid> data={"corsErrorStatus":{"corsError":"PreflightMissingAllowOriginHeader","failedParameter":""},"isWarning":false,"request":{"url":"http://hostname:port/data.json"},"initiatorOrigin":"","clientSecurityState":{"initiatorIsSecureContext":false,"initiatorIPAddressSpace":"Loopback","localNetworkAccessRequestPolicy":"BlockFromInsecureToMorePrivate"}}
119119
`;
120120

121+
exports[`console > get_console_message > when dialog is open 1`] = `
122+
{
123+
"dialog": {
124+
"type": "alert",
125+
"message": "test dialog",
126+
"defaultValue": ""
127+
},
128+
"consoleMessage": {
129+
"id": 1,
130+
"type": "error",
131+
"text": "This is an error",
132+
"argsCount": 1,
133+
"args": [
134+
"This is an error"
135+
],
136+
"stackTrace": "at (VM7:1:9)\\nat (pptr:;CdpFrame.%3Canonymous%3E%20(<file-path>)\\n--- PendingScript ----------------------\\nat (pptr:;CdpFrame.%3Canonymous%3E%20(<file-path>)\\nNote: line and column numbers use 1-based indexing"
137+
},
138+
"pagination": {
139+
"currentPage": 0,
140+
"totalPages": 1,
141+
"hasNextPage": false,
142+
"hasPreviousPage": false,
143+
"startIndex": 0,
144+
"endIndex": 1,
145+
"invalidPage": false
146+
},
147+
"consoleMessages": [
148+
{
149+
"type": "error",
150+
"text": "This is an error",
151+
"argsCount": 1,
152+
"id": 1
153+
}
154+
]
155+
}
156+
`;
157+
158+
exports[`console > list_console_messages > issues > when dialog is open 1`] = `
159+
{"content":[{"type":"text","text":"# Open dialog\\nalert: test dialog.\\nCall handle_dialog to handle it before continuing.\\n## Console messages\\nShowing 1-1 of 1 (Page 1 of 1).\\nmsgid=1 [log] Pre-dialog message (1 args)"}],"structuredContent":{"dialog":{"type":"alert","message":"test dialog","defaultValue":""},"pagination":{"currentPage":0,"totalPages":1,"hasNextPage":false,"hasPreviousPage":false,"startIndex":0,"endIndex":1,"invalidPage":false},"consoleMessages":[{"type":"log","text":"Pre-dialog message","argsCount":1,"id":1}]}}
160+
`;
161+
121162
exports[`console > list_console_messages > lists error objects 1`] = `
122163
## Console messages
123164
Showing 1-1 of 1 (Page 1 of 1).

tests/tools/console.test.ts

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import assert from 'node:assert';
88
import {before, describe, it} from 'node:test';
99

10+
import type {Dialog} from 'puppeteer-core';
11+
1012
import type {ParsedArguments} from '../../src/bin/chrome-devtools-mcp-cli-options.js';
1113
import {loadIssueDescriptions} from '../../src/issue-descriptions.js';
1214
import {McpResponse} from '../../src/McpResponse.js';
@@ -17,7 +19,11 @@ import {
1719
listConsoleMessages,
1820
} from '../../src/tools/console.js';
1921
import {serverHooks} from '../server.js';
20-
import {getTextContent, withMcpContext} from '../utils.js';
22+
import {
23+
getTextContent,
24+
withMcpContext,
25+
stabilizeStructuredContent,
26+
} from '../utils.js';
2127

2228
describe('console', () => {
2329
before(async () => {
@@ -162,6 +168,37 @@ describe('console', () => {
162168
}
163169
});
164170
});
171+
172+
it('when dialog is open', async t => {
173+
await withMcpContext(async (response, context) => {
174+
const page = context.getSelectedPptrPage();
175+
await page.setContent(
176+
'<script>console.log("Pre-dialog message")</script>',
177+
);
178+
179+
const dialogPromise = new Promise<Dialog>(resolve => {
180+
page.on('dialog', dialog => resolve(dialog));
181+
});
182+
183+
page.evaluate(() => {
184+
alert('test dialog');
185+
});
186+
const dialog = await dialogPromise;
187+
188+
await listConsoleMessages().handler(
189+
{params: {}, page: context.getSelectedMcpPage()},
190+
response,
191+
context,
192+
);
193+
194+
const result = await response.handle(
195+
'list_console_messages',
196+
context,
197+
);
198+
t.assert.snapshot?.(JSON.stringify(result));
199+
await dialog.dismiss();
200+
});
201+
});
165202
});
166203
});
167204

@@ -474,5 +511,44 @@ describe('console', () => {
474511
t.assert.snapshot?.(rawText);
475512
});
476513
});
514+
515+
it('when dialog is open', async t => {
516+
await withMcpContext(async (response, context) => {
517+
const page = context.getSelectedPptrPage();
518+
await page.setContent(
519+
'<script>console.error("This is an error")</script>',
520+
);
521+
522+
await listConsoleMessages().handler(
523+
{params: {}, page: context.getSelectedMcpPage()},
524+
response,
525+
context,
526+
);
527+
528+
const dialogPromise = new Promise<Dialog>(resolve => {
529+
page.on('dialog', dialog => resolve(dialog));
530+
});
531+
page.evaluate(() => {
532+
alert('test dialog');
533+
});
534+
const dialog = await dialogPromise;
535+
536+
await getConsoleMessage.handler(
537+
{params: {msgid: 1}, page: context.getSelectedMcpPage()},
538+
response,
539+
context,
540+
);
541+
542+
const result = await response.handle('get_console_message', context);
543+
t.assert.snapshot?.(
544+
JSON.stringify(
545+
stabilizeStructuredContent(result.structuredContent),
546+
null,
547+
2,
548+
),
549+
);
550+
await dialog.dismiss();
551+
});
552+
});
477553
});
478554
});

tests/tools/pages.test.js.snapshot

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
exports[`pages > close_page > when dialog is open 1`] = `
2+
{"content":[{"type":"text","text":"## Pages\\n1: about:blank [selected]"}],"structuredContent":{"pages":[{"id":1,"url":"about:blank","selected":true}]}}
3+
`;
4+
15
exports[`pages > list_pages > list pages for extension pages with --category-extensions 1`] = `
26
## Pages
37
1: about:blank [selected]
@@ -25,3 +29,23 @@ exports[`pages > list_pages > list pages for side panels with --category-extensi
2529
## Extension Service Workers
2630
sw-1: chrome-extension://<extension-id>/sw.js
2731
`;
32+
33+
exports[`pages > list_pages > when dialog is open 1`] = `
34+
{"content":[{"type":"text","text":"# Open dialog\\nalert: test dialog.\\nCall handle_dialog to handle it before continuing.\\n## Pages\\n1: about:blank [selected]"}],"structuredContent":{"dialog":{"type":"alert","message":"test dialog","defaultValue":""},"pages":[{"id":1,"url":"about:blank","selected":true}]}}
35+
`;
36+
37+
exports[`pages > navigate_page > when dialog is open 1`] = `
38+
{"content":[{"type":"text","text":"Successfully navigated to data:text/html,<div>Navigated</div>.\\n# Open dialog\\nalert: test dialog.\\nCall handle_dialog to handle it before continuing.\\n## Pages\\n1: data:text/html,<div>Navigated</div> [selected]"}],"structuredContent":{"message":"Successfully navigated to data:text/html,<div>Navigated</div>.","dialog":{"type":"alert","message":"test dialog","defaultValue":""},"pages":[{"id":1,"url":"data:text/html,<div>Navigated</div>","selected":true}]}}
39+
`;
40+
41+
exports[`pages > new_page with isolatedContext > when dialog is open 1`] = `
42+
{"content":[{"type":"text","text":"# Open dialog\\nalert: test dialog.\\nCall handle_dialog to handle it before continuing.\\n## Pages\\n1: about:blank\\n2: about:blank [selected]"}],"structuredContent":{"dialog":{"type":"alert","message":"test dialog","defaultValue":""},"pages":[{"id":1,"url":"about:blank","selected":false},{"id":2,"url":"about:blank","selected":true}]}}
43+
`;
44+
45+
exports[`pages > resize > when dialog is open 1`] = `
46+
{"content":[{"type":"text","text":"# Open dialog\\nalert: test dialog.\\nCall handle_dialog to handle it before continuing.\\n## Pages\\n1: about:blank [selected]"}],"structuredContent":{"dialog":{"type":"alert","message":"test dialog","defaultValue":""},"pages":[{"id":1,"url":"about:blank","selected":true}]}}
47+
`;
48+
49+
exports[`pages > select_page > when dialog is open 1`] = `
50+
{"content":[{"type":"text","text":"# Open dialog\\nalert: test dialog.\\nCall handle_dialog to handle it before continuing.\\n## Pages\\n1: about:blank [selected]"}],"structuredContent":{"dialog":{"type":"alert","message":"test dialog","defaultValue":""},"pages":[{"id":1,"url":"about:blank","selected":true}]}}
51+
`;

tests/tools/pages.test.ts

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,29 @@ describe('pages', () => {
205205
} as ParsedArguments,
206206
);
207207
});
208+
209+
it('when dialog is open', async t => {
210+
await withMcpContext(async (response, context) => {
211+
const page = context.getSelectedPptrPage();
212+
213+
const dialogPromise = new Promise<Dialog>(resolve => {
214+
page.on('dialog', dialog => {
215+
resolve(dialog);
216+
});
217+
});
218+
219+
page.evaluate(() => {
220+
alert('test dialog');
221+
});
222+
const dialog = await dialogPromise;
223+
224+
await listPages().handler({params: {}}, response, context);
225+
226+
const result = await response.handle('list_pages', context);
227+
t.assert.snapshot?.(JSON.stringify(result));
228+
await dialog.dismiss();
229+
});
230+
});
208231
});
209232
describe('new_page', () => {
210233
it('create a page', async () => {
@@ -354,6 +377,33 @@ describe('pages', () => {
354377
assert.ok(page.isClosed());
355378
});
356379
});
380+
381+
it('when dialog is open', async t => {
382+
await withMcpContext(async (response, context) => {
383+
const page = context.getSelectedPptrPage();
384+
385+
const dialogPromise = new Promise<Dialog>(resolve => {
386+
page.on('dialog', dialog => {
387+
resolve(dialog);
388+
});
389+
});
390+
391+
page.evaluate(() => {
392+
alert('test dialog');
393+
});
394+
const dialog = await dialogPromise;
395+
396+
await newPage().handler(
397+
{params: {url: 'about:blank'}},
398+
response,
399+
context,
400+
);
401+
402+
const result = await response.handle('new_page', context);
403+
t.assert.snapshot?.(JSON.stringify(result));
404+
await dialog.dismiss();
405+
});
406+
});
357407
});
358408

359409
it('navigate_page targets the pageId page, not the global selection', async () => {
@@ -426,6 +476,35 @@ describe('pages', () => {
426476
assert.ok(!page.isClosed());
427477
});
428478
});
479+
480+
it('when dialog is open', async t => {
481+
await withMcpContext(async (response, context) => {
482+
const page = await context.newPage();
483+
assert.strictEqual(
484+
context.getPageById(2),
485+
context.getSelectedMcpPage(),
486+
);
487+
assert.strictEqual(context.getPageById(2), page);
488+
489+
const dialogPromise = new Promise<void>(resolve => {
490+
page.pptrPage.on('dialog', () => resolve());
491+
});
492+
493+
page.pptrPage
494+
.evaluate(() => {
495+
alert('test dialog');
496+
})
497+
.catch(() => {
498+
// Ignore TargetCloseError when page is closed with open dialog
499+
});
500+
await dialogPromise;
501+
502+
await closePage.handler({params: {pageId: 2}}, response, context);
503+
504+
const result = await response.handle('close_page', context);
505+
t.assert.snapshot?.(JSON.stringify(result));
506+
});
507+
});
429508
});
430509
describe('select_page', () => {
431510
it('selects a page', async () => {
@@ -514,6 +593,29 @@ describe('pages', () => {
514593
);
515594
});
516595
});
596+
597+
it('when dialog is open', async t => {
598+
await withMcpContext(async (response, context) => {
599+
const page = context.getSelectedPptrPage();
600+
601+
const dialogPromise = new Promise<Dialog>(resolve => {
602+
page.on('dialog', dialog => {
603+
resolve(dialog);
604+
});
605+
});
606+
607+
page.evaluate(() => {
608+
alert('test dialog');
609+
});
610+
const dialog = await dialogPromise;
611+
612+
await selectPage.handler({params: {pageId: 1}}, response, context);
613+
614+
const result = await response.handle('select_page', context);
615+
t.assert.snapshot?.(JSON.stringify(result));
616+
await dialog.dismiss();
617+
});
618+
});
517619
});
518620
describe('navigate_page', () => {
519621
it('navigates to correct page', async () => {
@@ -762,6 +864,32 @@ describe('pages', () => {
762864
assert.ok(response.includePages);
763865
});
764866
});
867+
868+
it('when dialog is open', async t => {
869+
await withMcpContext(async (response, context) => {
870+
const page = context.getSelectedPptrPage();
871+
const dialogPromise = new Promise<void>(resolve => {
872+
page.on('dialog', () => resolve());
873+
});
874+
875+
page.evaluate(() => {
876+
alert('test dialog');
877+
});
878+
await dialogPromise;
879+
880+
await navigatePage().handler(
881+
{
882+
params: {url: 'data:text/html,<div>Navigated</div>'},
883+
page: context.getSelectedMcpPage(),
884+
},
885+
response,
886+
context,
887+
);
888+
889+
const result = await response.handle('navigate_page', context);
890+
t.assert.snapshot?.(JSON.stringify(result));
891+
});
892+
});
765893
});
766894
describe('resize', () => {
767895
it('resize the page', async () => {
@@ -926,6 +1054,35 @@ describe('pages', () => {
9261054
assert.deepStrictEqual(dimensions, [850, 650]);
9271055
});
9281056
});
1057+
1058+
it('when dialog is open', async t => {
1059+
await withMcpContext(async (response, context) => {
1060+
const page = context.getSelectedPptrPage();
1061+
const dialogPromise = new Promise<Dialog>(resolve => {
1062+
page.on('dialog', dialog => {
1063+
resolve(dialog);
1064+
});
1065+
});
1066+
1067+
page.evaluate(() => {
1068+
alert('test dialog');
1069+
});
1070+
const dialog = await dialogPromise;
1071+
1072+
await resizePage.handler(
1073+
{
1074+
params: {width: 1600, height: 1400},
1075+
page: context.getSelectedMcpPage(),
1076+
},
1077+
response,
1078+
context,
1079+
);
1080+
1081+
const result = await response.handle('resize_page', context);
1082+
t.assert.snapshot?.(JSON.stringify(result));
1083+
await dialog.dismiss();
1084+
});
1085+
});
9291086
});
9301087

9311088
describe('dialogs', () => {

tests/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,10 @@ export function stabilizeResponseOutput(text: unknown) {
287287
const acceptLanguageRegEx = /accept-language:.*\n/g;
288288
output = output.replaceAll(acceptLanguageRegEx, 'accept-language:<lang>\n');
289289

290+
// Stabilize URL-encoded file paths
291+
const fileUriRegEx = /file%3A%2F%2F%2F[^)\n]+/g;
292+
output = output.replaceAll(fileUriRegEx, '<file-path>');
293+
290294
return output;
291295
}
292296

0 commit comments

Comments
 (0)