Skip to content

Commit 4724bbb

Browse files
fix: avoid no page selected errors (#537)
Uses object identity to track the selected page. This helps to be in sync with the browser state better. Closes #492 --------- Co-authored-by: Nikolay Vitkov <[email protected]>
1 parent b110e86 commit 4724bbb

File tree

6 files changed

+32
-29
lines changed

6 files changed

+32
-29
lines changed

src/McpContext.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export class McpContext implements Context {
8888
// The most recent page state.
8989
#pages: Page[] = [];
9090
#pageToDevToolsPage = new Map<Page, Page>();
91-
#selectedPageIdx = 0;
91+
#selectedPage?: Page;
9292
// The most recent snapshot.
9393
#textSnapshot: TextSnapshot | null = null;
9494
#networkCollector: NetworkCollector;
@@ -146,7 +146,6 @@ export class McpContext implements Context {
146146

147147
async #init() {
148148
await this.createPagesSnapshot();
149-
this.setSelectedPageIdx(0);
150149
await this.#networkCollector.init();
151150
await this.#consoleCollector.init();
152151
}
@@ -221,8 +220,8 @@ export class McpContext implements Context {
221220

222221
async newPage(): Promise<Page> {
223222
const page = await this.browser.newPage();
224-
const pages = await this.createPagesSnapshot();
225-
this.setSelectedPageIdx(pages.indexOf(page));
223+
await this.createPagesSnapshot();
224+
this.selectPage(page);
226225
this.#networkCollector.addPage(page);
227226
this.#consoleCollector.addPage(page);
228227
return page;
@@ -232,7 +231,6 @@ export class McpContext implements Context {
232231
throw new Error(CLOSE_PAGE_ERROR);
233232
}
234233
const page = this.getPageByIdx(pageIdx);
235-
this.setSelectedPageIdx(0);
236234
await page.close({runBeforeUnload: false});
237235
}
238236

@@ -283,7 +281,7 @@ export class McpContext implements Context {
283281
}
284282

285283
getSelectedPage(): Page {
286-
const page = this.#pages[this.#selectedPageIdx];
284+
const page = this.#selectedPage;
287285
if (!page) {
288286
throw new Error('No page selected');
289287
}
@@ -304,19 +302,20 @@ export class McpContext implements Context {
304302
return page;
305303
}
306304

307-
getSelectedPageIdx(): number {
308-
return this.#selectedPageIdx;
309-
}
310-
311305
#dialogHandler = (dialog: Dialog): void => {
312306
this.#dialog = dialog;
313307
};
314308

315-
setSelectedPageIdx(idx: number): void {
316-
const oldPage = this.getSelectedPage();
317-
oldPage.off('dialog', this.#dialogHandler);
318-
this.#selectedPageIdx = idx;
319-
const newPage = this.getSelectedPage();
309+
isPageSelected(page: Page): boolean {
310+
return this.#selectedPage === page;
311+
}
312+
313+
selectPage(newPage: Page): void {
314+
const oldPage = this.#selectedPage;
315+
if (oldPage) {
316+
oldPage.off('dialog', this.#dialogHandler);
317+
}
318+
this.#selectedPage = newPage;
320319
newPage.on('dialog', this.#dialogHandler);
321320
this.#updateSelectedPageTimeouts();
322321
}
@@ -387,6 +386,10 @@ export class McpContext implements Context {
387386
);
388387
});
389388

389+
if (!this.#selectedPage || this.#pages.indexOf(this.#selectedPage) === -1) {
390+
this.selectPage(this.#pages[0]);
391+
}
392+
390393
await this.detectOpenDevToolsWindows();
391394

392395
return this.#pages;

src/McpResponse.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
362362
let idx = 0;
363363
for (const page of context.getPages()) {
364364
parts.push(
365-
`${idx}: ${page.url()}${idx === context.getSelectedPageIdx() ? ' [selected]' : ''}`,
365+
`${idx}: ${page.url()}${context.isPageSelected(page) ? ' [selected]' : ''}`,
366366
);
367367
idx++;
368368
}

src/tools/ToolDefinition.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,10 @@ export type Context = Readonly<{
9090
getDialog(): Dialog | undefined;
9191
clearDialog(): void;
9292
getPageByIdx(idx: number): Page;
93+
isPageSelected(page: Page): boolean;
9394
newPage(): Promise<Page>;
9495
closePage(pageIdx: number): Promise<void>;
95-
setSelectedPageIdx(idx: number): void;
96+
selectPage(page: Page): void;
9697
getElementByUid(uid: string): Promise<ElementHandle<Element>>;
9798
getAXNodeByUid(uid: string): TextSnapshotNode | undefined;
9899
setNetworkConditions(conditions: string | null): void;

src/tools/pages.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const selectPage = defineTool({
4040
handler: async (request, response, context) => {
4141
const page = context.getPageByIdx(request.params.pageIdx);
4242
await page.bringToFront();
43-
context.setSelectedPageIdx(request.params.pageIdx);
43+
context.selectPage(page);
4444
response.setIncludePages(true);
4545
},
4646
});

tests/tools/emulation.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ describe('emulation', () => {
7676

7777
it('report correctly for the currently selected page', async () => {
7878
await withBrowser(async (response, context) => {
79-
await context.newPage();
8079
await emulate.handler(
8180
{
8281
params: {
@@ -89,7 +88,8 @@ describe('emulation', () => {
8988

9089
assert.strictEqual(context.getNetworkConditions(), 'Slow 3G');
9190

92-
context.setSelectedPageIdx(0);
91+
const page = await context.newPage();
92+
context.selectPage(page);
9393

9494
assert.strictEqual(context.getNetworkConditions(), null);
9595
});
@@ -132,7 +132,6 @@ describe('emulation', () => {
132132

133133
it('report correctly for the currently selected page', async () => {
134134
await withBrowser(async (response, context) => {
135-
await context.newPage();
136135
await emulate.handler(
137136
{
138137
params: {
@@ -145,7 +144,8 @@ describe('emulation', () => {
145144

146145
assert.strictEqual(context.getCpuThrottlingRate(), 4);
147146

148-
context.setSelectedPageIdx(0);
147+
const page = await context.newPage();
148+
context.selectPage(page);
149149

150150
assert.strictEqual(context.getCpuThrottlingRate(), 1);
151151
});

tests/tools/pages.test.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ describe('pages', () => {
3131
describe('new_page', () => {
3232
it('create a page', async () => {
3333
await withBrowser(async (response, context) => {
34-
assert.strictEqual(context.getSelectedPageIdx(), 0);
34+
assert.strictEqual(context.getPageByIdx(0), context.getSelectedPage());
3535
await newPage.handler(
3636
{params: {url: 'about:blank'}},
3737
response,
3838
context,
3939
);
40-
assert.strictEqual(context.getSelectedPageIdx(), 1);
40+
assert.strictEqual(context.getPageByIdx(1), context.getSelectedPage());
4141
assert.ok(response.includePages);
4242
});
4343
});
@@ -46,7 +46,7 @@ describe('pages', () => {
4646
it('closes a page', async () => {
4747
await withBrowser(async (response, context) => {
4848
const page = await context.newPage();
49-
assert.strictEqual(context.getSelectedPageIdx(), 1);
49+
assert.strictEqual(context.getPageByIdx(1), context.getSelectedPage());
5050
assert.strictEqual(context.getPageByIdx(1), page);
5151
await closePage.handler({params: {pageIdx: 1}}, response, context);
5252
assert.ok(page.isClosed());
@@ -70,9 +70,9 @@ describe('pages', () => {
7070
it('selects a page', async () => {
7171
await withBrowser(async (response, context) => {
7272
await context.newPage();
73-
assert.strictEqual(context.getSelectedPageIdx(), 1);
73+
assert.strictEqual(context.getPageByIdx(1), context.getSelectedPage());
7474
await selectPage.handler({params: {pageIdx: 0}}, response, context);
75-
assert.strictEqual(context.getSelectedPageIdx(), 0);
75+
assert.strictEqual(context.getPageByIdx(0), context.getSelectedPage());
7676
assert.ok(response.includePages);
7777
});
7878
});
@@ -97,7 +97,7 @@ describe('pages', () => {
9797
it('throws an error if the page was closed not by the MCP server', async () => {
9898
await withBrowser(async (response, context) => {
9999
const page = await context.newPage();
100-
assert.strictEqual(context.getSelectedPageIdx(), 1);
100+
assert.strictEqual(context.getPageByIdx(1), context.getSelectedPage());
101101
assert.strictEqual(context.getPageByIdx(1), page);
102102

103103
await page.close();
@@ -197,7 +197,6 @@ describe('pages', () => {
197197
describe('resize', () => {
198198
it('create a page', async () => {
199199
await withBrowser(async (response, context) => {
200-
assert.strictEqual(context.getSelectedPageIdx(), 0);
201200
const page = context.getSelectedPage();
202201
const resizePromise = page.evaluate(() => {
203202
return new Promise(resolve => {

0 commit comments

Comments
 (0)