diff --git a/docs/tool-reference.md b/docs/tool-reference.md index dd661efcc..84d4fc9d1 100644 --- a/docs/tool-reference.md +++ b/docs/tool-reference.md @@ -116,7 +116,7 @@ ### `close_page` -**Description:** Closes the page by its index. +**Description:** Closes the page by its index. The last open page cannot be closed. **Parameters:** diff --git a/src/McpContext.ts b/src/McpContext.ts index 1456234ea..035f71845 100644 --- a/src/McpContext.ts +++ b/src/McpContext.ts @@ -131,6 +131,16 @@ export class McpContext implements Context { this.#consoleCollector.addPage(page); return page; } + async closePage(pageIdx: number): Promise { + if (this.#pages.length === 1) { + throw new Error( + 'Unable to close the last page in the browser. It is fine to keep the last page open.', + ); + } + const page = this.getPageByIdx(pageIdx); + this.setSelectedPageIdx(0); + await page.close({runBeforeUnload: false}); + } getNetworkRequestByUrl(url: string): HTTPRequest { const requests = this.getNetworkRequests(); diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index 37b0f50d8..be07fdbb6 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -62,6 +62,7 @@ export type Context = Readonly<{ clearDialog(): void; getPageByIdx(idx: number): Page; newPage(): Promise; + closePage(pageIdx: number): Promise; setSelectedPageIdx(idx: number): void; getElementByUid(uid: string): Promise>; setNetworkConditions(conditions: string | null): void; diff --git a/src/tools/pages.ts b/src/tools/pages.ts index c360ef193..be6ec2360 100644 --- a/src/tools/pages.ts +++ b/src/tools/pages.ts @@ -45,7 +45,7 @@ export const selectPage = defineTool({ export const closePage = defineTool({ name: 'close_page', - description: `Closes the page by its index.`, + description: `Closes the page by its index. The last open page cannot be closed.`, annotations: { category: ToolCategories.NAVIGATION_AUTOMATION, readOnlyHint: false, @@ -58,9 +58,7 @@ export const closePage = defineTool({ ), }, handler: async (request, response, context) => { - const page = context.getPageByIdx(request.params.pageIdx); - context.setSelectedPageIdx(0); - await page.close({runBeforeUnload: false}); + await context.closePage(request.params.pageIdx); response.setIncludePages(true); }, }); diff --git a/tests/tools/pages.test.ts b/tests/tools/pages.test.ts index 66985a507..5cad3b6e0 100644 --- a/tests/tools/pages.test.ts +++ b/tests/tools/pages.test.ts @@ -53,6 +53,21 @@ describe('pages', () => { assert.ok(response.includePages); }); }); + it('cannot close the last page', async () => { + await withBrowser(async (response, context) => { + const page = context.getSelectedPage(); + try { + await closePage.handler({params: {pageIdx: 0}}, response, context); + assert.fail('not reached'); + } catch (err) { + assert.strictEqual( + err.message, + 'Unable to close the last page in the browser. It is fine to keep the last page open.', + ); + } + assert.ok(!page.isClosed()); + }); + }); }); describe('browser_select_page', () => { it('selects a page', async () => {