/** * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { test, expect, connectAndNavigate } from './extension-fixtures'; test.skip(({ protocolVersion }) => protocolVersion === 1, 'Multi-tab not supported in protocol v1'); test(`browser_tabs new creates a new tab`, async ({ startExtensionClient, server }) => { server.setContent('/second.html', 'SecondSecond page', 'text/html'); const { browserContext, client } = await startExtensionClient(); const navigateResponse = await connectAndNavigate(browserContext, client, server.HELLO_WORLD); expect(navigateResponse).toHaveResponse({ snapshot: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`), }); const newTabResponse = await client.callTool({ name: 'browser_tabs', arguments: { action: 'new', url: server.PREFIX + '/second.html' }, }); expect(newTabResponse).toHaveResponse({ snapshot: expect.stringContaining(`- generic [active] [ref=e1]: Second page`), }); const listResponse = await client.callTool({ name: 'browser_tabs', arguments: { action: 'list' }, }); expect(listResponse).toHaveResponse({ result: expect.stringMatching(/- 0: \[Title\]\(.*\/hello-world\)\n- 1: \(current\) \[Second\]\(.*\/second\.html\)/), }); }); test(`browser_tabs select switches the active tab`, async ({ startExtensionClient, server }) => { server.setContent('/first.html', 'FirstFirst page', 'text/html'); server.setContent('/second.html', 'SecondSecond page', 'text/html'); const { browserContext, client } = await startExtensionClient(); await connectAndNavigate(browserContext, client, server.PREFIX + '/first.html'); await client.callTool({ name: 'browser_tabs', arguments: { action: 'new', url: server.PREFIX + '/second.html' }, }); const selectResponse = await client.callTool({ name: 'browser_tabs', arguments: { action: 'select', index: 0 }, }); expect(selectResponse).toHaveResponse({ result: expect.stringMatching(/- 0: \(current\) \[First\]\(.*\/first\.html\)\n- 1: \[Second\]\(.*\/second\.html\)/), }); const snapshotResponse = await client.callTool({ name: 'browser_snapshot', arguments: {}, }); expect(snapshotResponse).toHaveResponse({ inlineSnapshot: expect.stringContaining('First page'), }); }); test(`browser_tabs close removes a tab`, async ({ startExtensionClient, server }) => { server.setContent('/first.html', 'FirstFirst page', 'text/html'); server.setContent('/second.html', 'SecondSecond page', 'text/html'); const { browserContext, client } = await startExtensionClient(); await connectAndNavigate(browserContext, client, server.PREFIX + '/first.html'); await client.callTool({ name: 'browser_tabs', arguments: { action: 'new', url: server.PREFIX + '/second.html' }, }); const closeResponse = await client.callTool({ name: 'browser_tabs', arguments: { action: 'close', index: 0 }, }); expect(closeResponse).toHaveResponse({ result: expect.stringMatching(/^- 0: \(current\) \[Second\]\(.*\/second\.html\)$/m), }); const listResponse = await client.callTool({ name: 'browser_tabs', arguments: { action: 'list' }, }); expect(listResponse).toHaveResponse({ result: expect.not.stringContaining('First'), }); expect(listResponse).toHaveResponse({ result: expect.stringContaining('Second'), }); }); test(`cmd+click opens new tab visible in tab list`, async ({ startExtensionClient, server }) => { server.setContent('/link-page', 'LinkPageclick me', 'text/html'); server.setContent('/target-page', 'TargetPageTarget content', 'text/html'); const { browserContext, client } = await startExtensionClient(); const navigateResponse = await connectAndNavigate(browserContext, client, server.PREFIX + '/link-page'); expect(navigateResponse).toHaveResponse({ snapshot: expect.stringContaining(`click me`), }); await client.callTool({ name: 'browser_click', arguments: { element: 'click me', target: 'e2', modifiers: ['ControlOrMeta'] }, }); await expect.poll(async () => { const listResponse = await client.callTool({ name: 'browser_tabs', arguments: { action: 'list' }, }); return (listResponse as any).content?.[0]?.text ?? ''; }).toContain('TargetPage'); const listResponse = await client.callTool({ name: 'browser_tabs', arguments: { action: 'list' }, }); expect(listResponse).toHaveResponse({ result: expect.stringMatching(/- 0:.*\[LinkPage\].*\n- 1:.*\[TargetPage\]/), }); }); test(`window.open from tracked tab auto-attaches new tab`, async ({ startExtensionClient, server }) => { server.setContent('/opener-page', `Opener`, 'text/html'); server.setContent('/opened-page', 'OpenedOpened content', 'text/html'); const { browserContext, client } = await startExtensionClient(); const navigateResponse = await connectAndNavigate(browserContext, client, server.PREFIX + '/opener-page'); expect(navigateResponse).toHaveResponse({ snapshot: expect.stringContaining('open'), }); await client.callTool({ name: 'browser_click', arguments: { element: 'open', target: 'e2' }, }); await expect.poll(async () => { const listResponse = await client.callTool({ name: 'browser_tabs', arguments: { action: 'list' }, }); return (listResponse as any).content?.[0]?.text ?? ''; }).toContain('Opened'); const listResponse = await client.callTool({ name: 'browser_tabs', arguments: { action: 'list' }, }); expect(listResponse).toHaveResponse({ result: expect.stringMatching(/- 0:.*\[Opener\].*\n- 1:.*\[Opened\]/), }); });