From fc0cccf4a53f7ee79fc2c6dd6d7d26d5849acc22 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 3 Apr 2025 22:39:55 -0700 Subject: [PATCH] chore: reuse the first tab when navigating (#131) --- src/context.ts | 16 ++++++++++------ tests/tabs.spec.ts | 43 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/context.ts b/src/context.ts index 57c4168..6d5cd31 100644 --- a/src/context.ts +++ b/src/context.ts @@ -74,11 +74,9 @@ export class Context { } async ensureTab(): Promise { - if (this._currentTab) - return this._currentTab; - const context = await this._ensureBrowserContext(); - await context.newPage(); + if (!this._currentTab) + await context.newPage(); return this._currentTab!; } @@ -110,9 +108,13 @@ export class Context { } private _onPageClosed(tab: Tab) { - this._tabs = this._tabs.filter(t => t !== tab); + const index = this._tabs.indexOf(tab); + if (index === -1) + return; + this._tabs.splice(index, 1); + if (this._currentTab === tab) - this._currentTab = this._tabs[0]; + this._currentTab = this._tabs[Math.min(index, this._tabs.length - 1)]; const browser = this._browser; if (this._browserContext && !this._tabs.length) { void this._browserContext.close().then(() => browser?.close()).catch(() => {}); @@ -151,6 +153,8 @@ export class Context { const context = await this._createBrowserContext(); this._browser = context.browser; this._browserContext = context.browserContext; + for (const page of this._browserContext.pages()) + this._onPageCreated(page); this._browserContext.on('page', page => this._onPageCreated(page)); } return this._browserContext; diff --git a/tests/tabs.spec.ts b/tests/tabs.spec.ts index 940c0dd..4410048 100644 --- a/tests/tabs.spec.ts +++ b/tests/tabs.spec.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { chromium } from 'playwright'; + import { test, expect } from './fixtures'; import type { Client } from '@modelcontextprotocol/sdk/client/index.js'; @@ -29,6 +31,11 @@ async function createTab(client: Client, title: string, body: string) { test('create new tab', async ({ client }) => { expect(await createTab(client, 'Tab one', 'Body one')).toHaveTextContent(` +Open tabs: +- 1: [] (about:blank) +- 2: (current) [Tab one] (data:text/html,Tab oneBody one) + +Current tab: - Page URL: data:text/html,Tab oneBody one - Page Title: Tab one - Page Snapshot @@ -38,8 +45,9 @@ test('create new tab', async ({ client }) => { expect(await createTab(client, 'Tab two', 'Body two')).toHaveTextContent(` Open tabs: -- 1: [Tab one] (data:text/html,Tab oneBody one) -- 2: (current) [Tab two] (data:text/html,Tab twoBody two) +- 1: [] (about:blank) +- 2: [Tab one] (data:text/html,Tab oneBody one) +- 3: (current) [Tab two] (data:text/html,Tab twoBody two) Current tab: - Page URL: data:text/html,Tab twoBody two @@ -56,12 +64,13 @@ test('select tab', async ({ client }) => { expect(await client.callTool({ name: 'browser_select_tab', arguments: { - index: 1, + index: 2, }, })).toHaveTextContent(` Open tabs: -- 1: (current) [Tab one] (data:text/html,Tab oneBody one) -- 2: [Tab two] (data:text/html,Tab twoBody two) +- 1: [] (about:blank) +- 2: (current) [Tab one] (data:text/html,Tab oneBody one) +- 3: [Tab two] (data:text/html,Tab twoBody two) Current tab: - Page URL: data:text/html,Tab oneBody one @@ -78,9 +87,14 @@ test('close tab', async ({ client }) => { expect(await client.callTool({ name: 'browser_close_tab', arguments: { - index: 2, + index: 3, }, })).toHaveTextContent(` +Open tabs: +- 1: [] (about:blank) +- 2: (current) [Tab one] (data:text/html,Tab oneBody one) + +Current tab: - Page URL: data:text/html,Tab oneBody one - Page Title: Tab one - Page Snapshot @@ -88,3 +102,20 @@ test('close tab', async ({ client }) => { - text: Body one \`\`\``); }); + +test('reuse first tab when navigating', async ({ startClient, cdpEndpoint }) => { + const browser = await chromium.connectOverCDP(cdpEndpoint); + const [context] = browser.contexts(); + const pages = context.pages(); + + const client = await startClient({ args: [`--cdp-endpoint=${cdpEndpoint}`] }); + await client.callTool({ + name: 'browser_navigate', + arguments: { + url: 'data:text/html,TitleBody', + }, + }); + + expect(pages.length).toBe(1); + expect(await pages[0].title()).toBe('Title'); +});