Files
AI/참고/playwright-main/tests/extension/tab-grouping.spec.ts

348 lines
13 KiB
TypeScript
Raw Normal View History

2026-05-12 19:40:31 +09:00
/**
* 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, extensionId, clickAllowAndSelect, startWithExtensionFlag } from './extension-fixtures';
test('connect page is not in group before selection', async ({ startExtensionClient, server }) => {
const { browserContext, client } = await startExtensionClient();
const connectPagePromise = browserContext.waitForEvent('page', page =>
page.url().startsWith(`chrome-extension://${extensionId}/connect.html`)
);
const navigatePromise = client.callTool({ name: 'browser_navigate', arguments: { url: server.HELLO_WORLD } });
const connectPage = await connectPagePromise;
// Wait for the tab list to appear — this means connectToMCPRelay was processed.
await expect(connectPage.locator('.tab-item').first()).toBeVisible();
const groupId = await connectPage.evaluate(async () => {
const chrome = (window as any).chrome;
const tab = await chrome.tabs.getCurrent();
return tab?.groupId ?? -1;
});
expect(groupId).toBe(-1);
await clickAllowAndSelect(connectPage, 'Welcome');
await navigatePromise;
});
test('connected tab is in green Playwright group, connect page is closed', async ({ browserWithExtension, startClient, server }) => {
const browserContext = await browserWithExtension.launch();
const page = await browserContext.newPage();
await page.goto(server.HELLO_WORLD);
const client = await startWithExtensionFlag(browserWithExtension, startClient);
const connectPagePromise = browserContext.waitForEvent('page', page =>
page.url().startsWith(`chrome-extension://${extensionId}/connect.html`)
);
const navigatePromise = client.callTool({ name: 'browser_navigate', arguments: { url: server.HELLO_WORLD } });
const connectPage = await connectPagePromise;
const connectClosePromise = connectPage.waitForEvent('close');
await clickAllowAndSelect(connectPage, 'Title');
await navigatePromise;
// The connect page tab is closed since the user selected a different tab.
await connectClosePromise;
const [sw] = browserContext.serviceWorkers();
// Connected tab should be in the Playwright group.
await expect.poll(async () => {
return sw.evaluate(async () => {
const chrome = (globalThis as any).chrome;
const [connectedTab] = await chrome.tabs.query({ title: 'Title' });
if (!connectedTab || connectedTab.groupId === -1)
return null;
const g = await chrome.tabGroups.get(connectedTab.groupId);
return { color: g.color, title: g.title };
});
}).toEqual({ color: 'green', title: 'Playwright' });
});
test('tab added to group gets auto-attached', async ({ browserWithExtension, startClient, server, protocolVersion }) => {
test.skip(protocolVersion === 1, 'Multi-tab not supported in protocol v1');
server.setContent('/extra', '<title>Extra</title><body>Extra content</body>', 'text/html');
const browserContext = await browserWithExtension.launch();
const page = await browserContext.newPage();
await page.goto(server.HELLO_WORLD);
const extraPage = await browserContext.newPage();
await extraPage.goto(server.PREFIX + '/extra');
const client = await startWithExtensionFlag(browserWithExtension, startClient);
const connectPagePromise = browserContext.waitForEvent('page', p =>
p.url().startsWith(`chrome-extension://${extensionId}/connect.html`)
);
const navigatePromise = client.callTool({ name: 'browser_navigate', arguments: { url: server.HELLO_WORLD } });
const connectPage = await connectPagePromise;
await clickAllowAndSelect(connectPage, 'Title');
await navigatePromise;
const [sw] = browserContext.serviceWorkers();
// Wait for the connected tab to be added to the group.
await expect.poll(async () => {
return sw.evaluate(async () => {
const chrome = (globalThis as any).chrome;
const [connectedTab] = await chrome.tabs.query({ title: 'Title' });
return connectedTab?.groupId ?? -1;
});
}).toBeGreaterThan(-1);
// Drag the extra tab into the Playwright group — this should auto-attach it.
await sw.evaluate(async (targetUrl: string) => {
const chrome = (globalThis as any).chrome;
const [connectedTab] = await chrome.tabs.query({ title: 'Title' });
const [extra] = await chrome.tabs.query({ url: targetUrl });
await chrome.tabs.group({ groupId: connectedTab.groupId, tabIds: [extra.id] });
}, server.PREFIX + '/extra');
await expect.poll(async () => {
const r = await client.callTool({ name: 'browser_tabs', arguments: { action: 'list' } });
return (r as any).content?.[0]?.text ?? '';
}).toContain('Extra');
});
test('chrome:// tab dragged into group stays until it navigates to a debuggable URL', async ({ browserWithExtension, startClient, server, protocolVersion }) => {
test.skip(protocolVersion === 1, 'Multi-tab not supported in protocol v1');
server.setContent('/second', '<title>Second</title><body>Second</body>', 'text/html');
const browserContext = await browserWithExtension.launch();
const page = await browserContext.newPage();
await page.goto(server.HELLO_WORLD);
const client = await startWithExtensionFlag(browserWithExtension, startClient);
const connectPagePromise = browserContext.waitForEvent('page', p =>
p.url().startsWith(`chrome-extension://${extensionId}/connect.html`)
);
const navigatePromise = client.callTool({ name: 'browser_navigate', arguments: { url: server.HELLO_WORLD } });
const connectPage = await connectPagePromise;
await clickAllowAndSelect(connectPage, 'Title');
await navigatePromise;
const [sw] = browserContext.serviceWorkers();
await expect.poll(async () => {
return sw.evaluate(async () => {
const chrome = (globalThis as any).chrome;
const [connectedTab] = await chrome.tabs.query({ title: 'Title' });
return connectedTab?.groupId ?? -1;
});
}).toBeGreaterThan(-1);
const groupId = await sw.evaluate(async () => {
const chrome = (globalThis as any).chrome;
const [connectedTab] = await chrome.tabs.query({ title: 'Title' });
return connectedTab.groupId as number;
});
// Open a chrome:// tab and drag it into the Playwright group.
const chromeTabId = await sw.evaluate(async () => {
const chrome = (globalThis as any).chrome;
const tab = await chrome.tabs.create({ url: 'chrome://version/', active: false });
return tab.id as number;
});
await expect.poll(async () => {
return sw.evaluate(async (id: number) => {
const chrome = (globalThis as any).chrome;
const tab = await chrome.tabs.get(id);
return tab.url || '';
}, chromeTabId);
}).toContain('chrome://version');
await sw.evaluate(async ({ id, gid }: { id: number, gid: number }) => {
const chrome = (globalThis as any).chrome;
await chrome.tabs.group({ groupId: gid, tabIds: [id] });
}, { id: chromeTabId, gid: groupId });
// The chrome:// tab stays in the group without a debugger badge.
await expect.poll(async () => {
return sw.evaluate(async (id: number) => {
const chrome = (globalThis as any).chrome;
const tab = await chrome.tabs.get(id);
const badge = await chrome.action.getBadgeText({ tabId: id });
return { groupId: tab.groupId, badge };
}, chromeTabId);
}).toEqual({ groupId, badge: '' });
// Navigating to a debuggable URL attaches it and shows the badge.
await sw.evaluate(async ({ id, url }: { id: number, url: string }) => {
const chrome = (globalThis as any).chrome;
await chrome.tabs.update(id, { url });
}, { id: chromeTabId, url: server.PREFIX + '/second' });
await expect.poll(async () => {
return sw.evaluate(async (id: number) => {
const chrome = (globalThis as any).chrome;
const tab = await chrome.tabs.get(id);
const badge = await chrome.action.getBadgeText({ tabId: id });
return { groupId: tab.groupId, badge };
}, chromeTabId);
}).toEqual({ groupId, badge: '✓' });
});
test('tab removed from group gets auto-detached', async ({ browserWithExtension, startClient, server, protocolVersion }) => {
test.skip(protocolVersion === 1, 'Multi-tab not supported in protocol v1');
server.setContent('/second', '<title>Second</title><body>Second</body>', 'text/html');
const browserContext = await browserWithExtension.launch();
const page = await browserContext.newPage();
await page.goto(server.HELLO_WORLD);
const client = await startWithExtensionFlag(browserWithExtension, startClient);
const connectPagePromise = browserContext.waitForEvent('page', p =>
p.url().startsWith(`chrome-extension://${extensionId}/connect.html`)
);
const navigatePromise = client.callTool({ name: 'browser_navigate', arguments: { url: server.HELLO_WORLD } });
const connectPage = await connectPagePromise;
await clickAllowAndSelect(connectPage, 'Title');
await navigatePromise;
// Create a second tab via the client — it will be attached and added to the group.
await client.callTool({ name: 'browser_tabs', arguments: { action: 'new', url: server.PREFIX + '/second' } });
const [sw] = browserContext.serviceWorkers();
// The second tab is attached (has the connected badge).
await expect.poll(async () => {
return sw.evaluate(async (targetUrl: string) => {
const chrome = (globalThis as any).chrome;
const [t] = await chrome.tabs.query({ url: targetUrl });
if (!t?.id)
return '';
return await chrome.action.getBadgeText({ tabId: t.id });
}, server.PREFIX + '/second');
}).toBe('✓');
// Ungroup the second tab — this should auto-detach it.
await sw.evaluate(async (targetUrl: string) => {
const chrome = (globalThis as any).chrome;
const [second] = await chrome.tabs.query({ url: targetUrl });
await chrome.tabs.ungroup([second.id]);
}, server.PREFIX + '/second');
// The badge should be cleared, indicating the tab was detached.
await expect.poll(async () => {
return sw.evaluate(async (targetUrl: string) => {
const chrome = (globalThis as any).chrome;
const [t] = await chrome.tabs.query({ url: targetUrl });
if (!t?.id)
return '';
return await chrome.action.getBadgeText({ tabId: t.id });
}, server.PREFIX + '/second');
}).toBe('');
});
test('connected tab is removed from group on disconnect', async ({ browserWithExtension, startClient, server }) => {
const browserContext = await browserWithExtension.launch();
const page = await browserContext.newPage();
await page.goto(server.HELLO_WORLD);
const client = await startWithExtensionFlag(browserWithExtension, startClient);
const connectPagePromise = browserContext.waitForEvent('page', page =>
page.url().startsWith(`chrome-extension://${extensionId}/connect.html`)
);
const navigatePromise = client.callTool({ name: 'browser_navigate', arguments: { url: server.HELLO_WORLD } });
const connectPage = await connectPagePromise;
await clickAllowAndSelect(connectPage, 'Title');
await navigatePromise;
const [sw] = browserContext.serviceWorkers();
await client.close();
await expect.poll(async () => {
return sw.evaluate(async () => {
const chrome = (globalThis as any).chrome;
const [tab] = await chrome.tabs.query({ title: 'Title' });
return tab?.groupId ?? -1;
});
}).toBe(-1);
});
test('tab is re-added to Playwright group after reconnecting', async ({ browserWithExtension, startClient, server }) => {
const browserContext = await browserWithExtension.launch();
const page = await browserContext.newPage();
await page.goto(server.HELLO_WORLD);
const connect = async () => {
const client = await startWithExtensionFlag(browserWithExtension, startClient);
const connectPagePromise = browserContext.waitForEvent('page', p =>
p.url().startsWith(`chrome-extension://${extensionId}/connect.html`)
);
const navigatePromise = client.callTool({ name: 'browser_navigate', arguments: { url: server.HELLO_WORLD } });
const connectPage = await connectPagePromise;
await clickAllowAndSelect(connectPage, 'Title');
await navigatePromise;
return { client };
};
// First connection.
const first = await connect();
const [sw] = browserContext.serviceWorkers();
await first.client.close();
// Wait for the tab to be ungrouped after disconnect.
await expect.poll(async () => {
return sw.evaluate(async () => {
const chrome = (globalThis as any).chrome;
if (!chrome?.tabs)
return null;
const [tab] = await chrome.tabs.query({ title: 'Title' });
return tab?.groupId ?? -1;
});
}).toBe(-1);
// Second connection.
await connect();
// The tab must end up in a green Playwright group again.
await expect.poll(async () => {
return sw.evaluate(async () => {
const chrome = (globalThis as any).chrome;
if (!chrome?.tabs)
return null;
const [tab] = await chrome.tabs.query({ title: 'Title' });
if (!tab || tab.groupId === -1)
return null;
const g = await chrome.tabGroups.get(tab.groupId);
return { color: g.color, title: g.title };
});
}).toEqual({ color: 'green', title: 'Playwright' });
});