diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index 179a2b3b9aca2..bf9758c6a8f53 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -4477,6 +4477,37 @@ When `true`, includes each element's bounding box as a `box` property with `x`, relative to the viewport, in CSS pixels, as returned by [`Element.getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect). Defaults to `false`. +## async method: Page.tabInfo +* since: v1.63 +- returns: <[null]|[Object]> + - `active` <[boolean]> Whether the tab is selected in its browser window. Note that this does not imply that the + browser window itself is focused at the OS level. + - `index` <[int]> Zero-based position of the tab in the tab strip of its browser window. + - `pinned` <[boolean]> Whether the tab is pinned. + - `groupId` <[null]|[string]> Id of the tab group this tab belongs to, if any. + - `windowId` <[int]> Id of the browser window containing the tab, matching the window id in the Chrome DevTools + Protocol. + +Returns a snapshot of the browser tab state for this page, as reflected in the browser tab strip. Unlike renderer-level +signals such as `document.visibilityState`, this reports the real browser UI state, which is useful when a human and +automation share the same headed browser. + +Returns `null` when the information is not available: in browsers other than Chromium, in Chromium versions +below 150, in the Chromium headless shell which has no tab strip, and for pages that are not part of a tab strip. + +**Usage** + +```js +const tabInfo = await page.tabInfo(); +if (tabInfo?.active) + console.log('The user is currently looking at this page'); +``` + +:::note +This API always returns an up-to-date snapshot and does not emit events when the tab state changes. Call it again to +observe changes. +::: + ## async method: Page.tap * since: v1.8 * discouraged: Use locator-based [`method: Locator.tap`] instead. Read more about [locators](../locators.md). diff --git a/packages/isomorphic/protocolMetainfo.ts b/packages/isomorphic/protocolMetainfo.ts index d8afde4be244d..f0079f1926dad 100644 --- a/packages/isomorphic/protocolMetainfo.ts +++ b/packages/isomorphic/protocolMetainfo.ts @@ -287,6 +287,7 @@ export const methodMetainfo = new Map([ ['Page.startCSSCoverage', { title: 'Start CSS coverage', group: 'configuration', }], ['Page.stopCSSCoverage', { title: 'Stop CSS coverage', group: 'configuration', }], ['Page.bringToFront', { title: 'Bring to front', }], + ['Page.tabInfo', { title: 'Get tab info', }], ['Page.pickLocator', { title: 'Pick locator', group: 'configuration', }], ['Page.cancelPickLocator', { title: 'Cancel pick locator', group: 'configuration', }], ['Page.hideHighlight', { title: 'Hide all element highlights', group: 'configuration', }], diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index f4f5766b5d52e..07ab1b37c19a2 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -5017,6 +5017,54 @@ export interface Page { height: number; }): Promise; + /** + * Returns a snapshot of the browser tab state for this page, as reflected in the browser tab strip. Unlike + * renderer-level signals such as `document.visibilityState`, this reports the real browser UI state, which is useful + * when a human and automation share the same headed browser. + * + * Returns `null` when the information is not available: in browsers other than Chromium, in Chromium versions below + * 150, in the Chromium headless shell which has no tab strip, and for pages that are not part of a tab strip. + * + * **Usage** + * + * ```js + * const tabInfo = await page.tabInfo(); + * if (tabInfo?.active) + * console.log('The user is currently looking at this page'); + * ``` + * + * **NOTE** This API always returns an up-to-date snapshot and does not emit events when the tab state changes. Call + * it again to observe changes. + * + */ + tabInfo(): Promise; + /** * **NOTE** Use locator-based [locator.tap([options])](https://playwright.dev/docs/api/class-locator#locator-tap) instead. Read * more about [locators](https://playwright.dev/docs/locators). diff --git a/packages/playwright-core/src/client/channels.d.ts b/packages/playwright-core/src/client/channels.d.ts index 4e892dd09b163..3cbdd24b0b3ed 100644 --- a/packages/playwright-core/src/client/channels.d.ts +++ b/packages/playwright-core/src/client/channels.d.ts @@ -3872,6 +3872,7 @@ export interface PageChannel extends PageEventTarget, Channel { startCSSCoverage(params: PageStartCSSCoverageParams, options: TimeoutOptions): Promise; stopCSSCoverage(params: PageStopCSSCoverageParams, options: TimeoutOptions): Promise; bringToFront(params: PageBringToFrontParams, options: TimeoutOptions): Promise; + tabInfo(params: PageTabInfoParams, options: TimeoutOptions): Promise; pickLocator(params: PagePickLocatorParams, options: TimeoutOptions): Promise; cancelPickLocator(params: PageCancelPickLocatorParams, options: TimeoutOptions): Promise; hideHighlight(params: PageHideHighlightParams, options: TimeoutOptions): Promise; @@ -4401,6 +4402,17 @@ export type PageStopCSSCoverageResult = { export type PageBringToFrontParams = {}; export type PageBringToFrontOptions = {}; export type PageBringToFrontResult = void; +export type PageTabInfoParams = {}; +export type PageTabInfoOptions = {}; +export type PageTabInfoResult = { + tabInfo?: { + active: boolean, + index: number, + pinned: boolean, + groupId?: string, + windowId: number, + }, +}; export type PagePickLocatorParams = {}; export type PagePickLocatorOptions = {}; export type PagePickLocatorResult = { diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts index 8825ec2a9549b..80b1d9ac7d111 100644 --- a/packages/playwright-core/src/client/page.ts +++ b/packages/playwright-core/src/client/page.ts @@ -688,6 +688,13 @@ export class Page extends ChannelOwner implements api.Page await this._channel.bringToFront({}, kNoTimeout); } + async tabInfo(): Promise<{ active: boolean, index: number, pinned: boolean, groupId: string | null, windowId: number } | null> { + const { tabInfo } = await this._channel.tabInfo({}, kNoTimeout); + if (!tabInfo) + return null; + return { ...tabInfo, groupId: tabInfo.groupId ?? null }; + } + async [Symbol.asyncDispose]() { await this.close(); } diff --git a/packages/playwright-core/src/server/channels.d.ts b/packages/playwright-core/src/server/channels.d.ts index b7fbee94374c4..72244e886c7e3 100644 --- a/packages/playwright-core/src/server/channels.d.ts +++ b/packages/playwright-core/src/server/channels.d.ts @@ -3873,6 +3873,7 @@ export interface PageChannel extends PageEventTarget, Channel { startCSSCoverage(params: PageStartCSSCoverageParams, progress: Progress): Promise; stopCSSCoverage(params: PageStopCSSCoverageParams, progress: Progress): Promise; bringToFront(params: PageBringToFrontParams, progress: Progress): Promise; + tabInfo(params: PageTabInfoParams, progress: Progress): Promise; pickLocator(params: PagePickLocatorParams, progress: Progress): Promise; cancelPickLocator(params: PageCancelPickLocatorParams, progress: Progress): Promise; hideHighlight(params: PageHideHighlightParams, progress: Progress): Promise; @@ -4402,6 +4403,17 @@ export type PageStopCSSCoverageResult = { export type PageBringToFrontParams = {}; export type PageBringToFrontOptions = {}; export type PageBringToFrontResult = void; +export type PageTabInfoParams = {}; +export type PageTabInfoOptions = {}; +export type PageTabInfoResult = { + tabInfo?: { + active: boolean, + index: number, + pinned: boolean, + groupId?: string, + windowId: number, + }, +}; export type PagePickLocatorParams = {}; export type PagePickLocatorOptions = {}; export type PagePickLocatorResult = { diff --git a/packages/playwright-core/src/server/chromium/crBrowser.ts b/packages/playwright-core/src/server/chromium/crBrowser.ts index db00e29a01059..f8e43163c5945 100644 --- a/packages/playwright-core/src/server/chromium/crBrowser.ts +++ b/packages/playwright-core/src/server/chromium/crBrowser.ts @@ -30,7 +30,7 @@ import { CRPage } from './crPage'; import { saveProtocolStream } from './crProtocolHelper'; import { CRServiceWorker } from './crServiceWorker'; -import type { InitScript } from '../page'; +import type { InitScript, TabInfo } from '../page'; import type { ConnectionTransport } from '../transport'; import type * as types from '../types'; import type { HttpCredentials } from '@protocol/structs'; @@ -56,6 +56,8 @@ export class CRBrowser extends Browser { private _tracingRecording = false; private _tracingClient: CRSession | undefined; private _userAgent: string = ''; + private _tabInfoSnapshotPromise: Promise> | undefined; + private _tabInfoBrowserSessionPromise: Promise | undefined; static async connect(parent: SdkObject, transport: ConnectionTransport, options: BrowserOptions, devtools?: CRDevTools): Promise { // Make a copy in case we need to update `headful` property below. @@ -276,6 +278,70 @@ export class CRBrowser extends Browser { await this._session.send('Target.closeTarget', { targetId: crPage._targetId }); } + async _tabInfoForPage(crPage: CRPage): Promise { + // Tab targets expose embedderData since Chromium 150. + if (this._majorVersion && this._majorVersion < 150) + return undefined; + if (!this._tabInfoSnapshotPromise) + this._tabInfoSnapshotPromise = this._queryTabInfos().finally(() => this._tabInfoSnapshotPromise = undefined); + const tabInfos = await this._tabInfoSnapshotPromise; + return tabInfos.get(crPage._targetId); + } + + private async _queryTabInfos(): Promise> { + const tabInfoByPageTargetId = new Map(); + const { targetInfos } = await this._session.send('Target.getTargets', { filter: [{ type: 'tab', exclude: false }, { exclude: true }] }); + const tabTargets = targetInfos.filter(targetInfo => !!targetInfo.embedderData); + if (!tabTargets.length) + return tabInfoByPageTargetId; + const browserSession = await this._tabInfoBrowserSession(); + await Promise.all(tabTargets.map(async targetInfo => { + const [pageTargetIds, { windowId }] = await Promise.all([ + this._collectTabPageTargets(browserSession, targetInfo.targetId), + browserSession.send('Browser.getWindowForTarget', { targetId: targetInfo.targetId }), + ]); + // Protocol declares string values, but Chromium sends booleans and numbers. + const embedderData = targetInfo.embedderData as unknown as { tabActive: boolean, tabStripIndex: number, tabPinned: boolean, tabGroupId?: string }; + const tabInfo = { + active: embedderData.tabActive, + index: embedderData.tabStripIndex, + pinned: embedderData.tabPinned, + groupId: embedderData.tabGroupId, + windowId, + }; + for (const pageTargetId of pageTargetIds) + tabInfoByPageTargetId.set(pageTargetId, tabInfo); + })); + return tabInfoByPageTargetId; + } + + // Separate session, so that tab attach events do not interfere with the root session auto-attach handling. + private async _tabInfoBrowserSession(): Promise { + if (!this._tabInfoBrowserSessionPromise) { + this._tabInfoBrowserSessionPromise = this._session.send('Target.attachToBrowserTarget').then( + ({ sessionId }) => this._session.createChildSession(sessionId)); + this._tabInfoBrowserSessionPromise.catch(() => this._tabInfoBrowserSessionPromise = undefined); + } + return this._tabInfoBrowserSessionPromise; + } + + private async _collectTabPageTargets(browserSession: CRSession, tabTargetId: string): Promise { + const { sessionId } = await browserSession.send('Target.attachToTarget', { targetId: tabTargetId, flatten: true }); + const tabSession = browserSession.createChildSession(sessionId); + const pageTargetIds: string[] = []; + tabSession.on('Target.attachedToTarget', (event: Protocol.Target.attachedToTargetPayload) => { + pageTargetIds.push(event.targetInfo.targetId); + }); + try { + // Existing related page targets are reported before the command acknowledgment. + await tabSession.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: false, flatten: true, filter: [{ type: 'page', exclude: false }, { exclude: true }] }); + } finally { + browserSession.send('Target.detachFromTarget', { sessionId }).catch(() => {}); + tabSession.dispose(); + } + return pageTargetIds; + } + async newBrowserCDPSession(): Promise { return await this._connection.createBrowserSession(); } diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index f53e0ecec637c..183c6ab7f3943 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -41,7 +41,7 @@ import { nullProgress } from '../progress'; import type { CRSession } from './crConnection'; import type { Protocol } from './protocol'; import type { RegisteredListener } from '@utils/eventsHelper'; -import type { InitScript, PageDelegate } from '../page'; +import type { InitScript, PageDelegate, TabInfo } from '../page'; import type { Progress } from '../progress'; import type * as types from '../types'; import type * as channels from '../channels'; @@ -208,6 +208,10 @@ export class CRPage implements PageDelegate { await this._mainFrameSession._client.send('Page.bringToFront'); } + async tabInfo(): Promise { + return await this._browserContext._browser._tabInfoForPage(this); + } + async updateEmulateMedia(): Promise { await this._forAllFrameSessions(frame => frame._updateEmulateMedia()); } diff --git a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts index 69bb01f0a8d28..f3917dbc4cc9b 100644 --- a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts @@ -353,6 +353,10 @@ export class PageDispatcher extends Dispatcher { + return { tabInfo: await this._page.tabInfo(progress) }; + } + async pickLocator(params: channels.PagePickLocatorParams, progress: Progress): Promise { const recorder = await progress.race(Recorder.forContext(this._page.browserContext, { omitCallTracking: true, hideToolbar: true })); const selector = await recorder.pickLocator(progress, this._page); diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index 82befdb710c41..509a4694653f1 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -56,6 +56,8 @@ import type * as channels from './channels'; import type { BindingPayload } from '@injected/bindingsController'; import type { AriaNodeJSON, AriaSnapshotJSON } from '@isomorphic/ariaSnapshot'; +export type TabInfo = NonNullable; + export interface PageDelegate { readonly rawMouse: input.RawMouse; readonly rawKeyboard: input.RawKeyboard; @@ -95,6 +97,7 @@ export interface PageDelegate { pdf?: (options: channels.PagePdfParams) => Promise; coverage?: () => any; noUtilityWorld?: () => boolean; + tabInfo?: () => Promise; // Work around WebKit's raf issues on Windows. rafCountForStablePosition(): number; @@ -671,6 +674,12 @@ export class Page extends SdkObject { await progress.race(this.delegate.bringToFront()); } + async tabInfo(progress: Progress): Promise { + if (!this.delegate.tabInfo) + return undefined; + return await progress.race(this.delegate.tabInfo()); + } + async addInitScript(progress: Progress, source: string) { return await progress.race(this._addInitScript(source)); } diff --git a/packages/playwright-core/src/tools/backend/response.ts b/packages/playwright-core/src/tools/backend/response.ts index eb1ed1f0dabb2..8f8a72dbf4bb9 100644 --- a/packages/playwright-core/src/tools/backend/response.ts +++ b/packages/playwright-core/src/tools/backend/response.ts @@ -357,7 +357,7 @@ export function renderTabMarkdown(tab: TabHeader): string[] { return lines; } -export function renderTabsMarkdown(tabs: TabHeader[]): string[] { +export function renderTabsMarkdown(tabs: (TabHeader & { foreground?: boolean })[]): string[] { if (!tabs.length) return ['No open tabs. Navigate to a URL to create one.']; @@ -365,8 +365,9 @@ export function renderTabsMarkdown(tabs: TabHeader[]): string[] { for (let i = 0; i < tabs.length; i++) { const tab = tabs[i]; const current = tab.current ? ' (current)' : ''; + const foreground = tab.foreground ? ' (visible to user)' : ''; const crashed = tab.crashed ? ' [crashed]' : ''; - lines.push(`- ${i}:${current} [${tab.title}](${tab.url})${crashed}`); + lines.push(`- ${i}:${current}${foreground} [${tab.title}](${tab.url})${crashed}`); } return lines; } diff --git a/packages/playwright-core/src/tools/backend/tabs.ts b/packages/playwright-core/src/tools/backend/tabs.ts index 93ad12a02f508..21594501e0de2 100644 --- a/packages/playwright-core/src/tools/backend/tabs.ts +++ b/packages/playwright-core/src/tools/backend/tabs.ts @@ -59,7 +59,13 @@ const browserTabs = defineTool({ break; } } - const tabHeaders = await Promise.all(context.tabs().map(tab => tab.headerSnapshot())); + const tabHeaders = await Promise.all(context.tabs().map(async tab => { + const [header, tabInfo] = await Promise.all([ + tab.headerSnapshot(), + tab.page.tabInfo().catch(() => null), + ]); + return { ...header, foreground: tabInfo?.active }; + })); const result = renderTabsMarkdown(tabHeaders); response.addTextResult(result.join('\n')); }, diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index f4f5766b5d52e..07ab1b37c19a2 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -5017,6 +5017,54 @@ export interface Page { height: number; }): Promise; + /** + * Returns a snapshot of the browser tab state for this page, as reflected in the browser tab strip. Unlike + * renderer-level signals such as `document.visibilityState`, this reports the real browser UI state, which is useful + * when a human and automation share the same headed browser. + * + * Returns `null` when the information is not available: in browsers other than Chromium, in Chromium versions below + * 150, in the Chromium headless shell which has no tab strip, and for pages that are not part of a tab strip. + * + * **Usage** + * + * ```js + * const tabInfo = await page.tabInfo(); + * if (tabInfo?.active) + * console.log('The user is currently looking at this page'); + * ``` + * + * **NOTE** This API always returns an up-to-date snapshot and does not emit events when the tab state changes. Call + * it again to observe changes. + * + */ + tabInfo(): Promise; + /** * **NOTE** Use locator-based [locator.tap([options])](https://playwright.dev/docs/api/class-locator#locator-tap) instead. Read * more about [locators](https://playwright.dev/docs/locators). diff --git a/packages/protocol/spec/page.yml b/packages/protocol/spec/page.yml index d4d41e5c1fecb..baebc8ebea5b5 100644 --- a/packages/protocol/spec/page.yml +++ b/packages/protocol/spec/page.yml @@ -518,6 +518,18 @@ Page: bringToFront: title: Bring to front + tabInfo: + title: Get tab info + returns: + tabInfo: + type: object? + properties: + active: boolean + index: int + pinned: boolean + groupId: string? + windowId: int + pickLocator: title: Pick locator group: configuration diff --git a/packages/protocol/src/validator.ts b/packages/protocol/src/validator.ts index 97233397aa80d..183fdb0b95109 100644 --- a/packages/protocol/src/validator.ts +++ b/packages/protocol/src/validator.ts @@ -2553,6 +2553,16 @@ scheme.PageStopCSSCoverageResult = tObject({ }); scheme.PageBringToFrontParams = tOptional(tObject({})); scheme.PageBringToFrontResult = tOptional(tObject({})); +scheme.PageTabInfoParams = tOptional(tObject({})); +scheme.PageTabInfoResult = tObject({ + tabInfo: tOptional(tObject({ + active: tBoolean, + index: tInt, + pinned: tBoolean, + groupId: tOptional(tString), + windowId: tInt, + })), +}); scheme.PagePickLocatorParams = tOptional(tObject({})); scheme.PagePickLocatorResult = tObject({ selector: tString, diff --git a/tests/library/chromium/chromium.spec.ts b/tests/library/chromium/chromium.spec.ts index 3eccc7fc2991b..e620b9bef3f82 100644 --- a/tests/library/chromium/chromium.spec.ts +++ b/tests/library/chromium/chromium.spec.ts @@ -811,3 +811,55 @@ test('should fire dialogclosed event when dialog is closed out of band', async ( expect(await closedPromise).toBe(dialog); await evaluatePromise; }); + +playwrightTest('page.tabInfo should report tab strip state', async ({ browserType, browserMajorVersion, channel }) => { + playwrightTest.skip(browserMajorVersion < 150, 'embedderData on tab targets requires Chromium 150+'); + // Headless shell has no tab strip, opt into the full Chromium build. + const browser = await browserType.launch({ channel: channel || 'chromium' }); + try { + const context = await browser.newContext(); + const page1 = await context.newPage(); + const info1 = (await page1.tabInfo())!; + expect(info1).toBeTruthy(); + expect(info1.pinned).toBe(false); + expect(info1.groupId).toBe(null); + + const page2 = await context.newPage(); + const info2 = (await page2.tabInfo())!; + expect(info2).toBeTruthy(); + expect(info2.active).toBe(true); + + await page1.bringToFront(); + await expect.poll(async () => (await page1.tabInfo())!.active).toBe(true); + if (info1.windowId === info2.windowId) { + expect((await page2.tabInfo())!.active).toBe(false); + expect(info1.index).not.toBe(info2.index); + } + } finally { + await browser.close(); + } +}); + +playwrightTest('page.tabInfo should serve concurrent calls from all pages', async ({ browserType, browserMajorVersion, channel }) => { + playwrightTest.skip(browserMajorVersion < 150, 'embedderData on tab targets requires Chromium 150+'); + const browser = await browserType.launch({ channel: channel || 'chromium' }); + try { + const context = await browser.newContext(); + const pages = []; + for (let i = 0; i < 5; i++) + pages.push(await context.newPage()); + + const infos = await Promise.all(pages.map(page => page.tabInfo())); + expect(infos.every(info => !!info)).toBe(true); + expect(infos.filter(info => info!.active).length).toBe(1); + expect(infos.map(info => info!.index)).toEqual([0, 1, 2, 3, 4]); + + await pages[0].bringToFront(); + await expect.poll(async () => { + const updated = await Promise.all(pages.map(page => page.tabInfo())); + return updated.findIndex(info => info!.active); + }).toBe(0); + } finally { + await browser.close(); + } +}); diff --git a/tests/mcp/crash.spec.ts b/tests/mcp/crash.spec.ts index 55eb486bd1d55..e764ed645f690 100644 --- a/tests/mcp/crash.spec.ts +++ b/tests/mcp/crash.spec.ts @@ -72,7 +72,7 @@ test.describe('crash recovery', () => { name: 'browser_tabs', arguments: { action: 'list' }, })).toHaveResponse({ - result: `- 0: (current) [](about:blank)`, + result: `- 0: (current) (visible to user) [](about:blank)`, }); }); @@ -94,7 +94,7 @@ test.describe('crash recovery', () => { name: 'browser_tabs', arguments: { action: 'list' }, })).toHaveResponse({ - result: `- 0: (current) [Title](${server.HELLO_WORLD})\n- 1: [](about:blank) [crashed]`, + result: `- 0: (current) [Title](${server.HELLO_WORLD})\n- 1: (visible to user) [](about:blank) [crashed]`, }); }); }); diff --git a/tests/mcp/tabs.spec.ts b/tests/mcp/tabs.spec.ts index 3743dd1f25fea..46e8f87d2da4e 100644 --- a/tests/mcp/tabs.spec.ts +++ b/tests/mcp/tabs.spec.ts @@ -40,7 +40,7 @@ test('list initial tabs', async ({ client }) => { action: 'list', }, })).toHaveResponse({ - result: `- 0: (current) [](about:blank)`, + result: `- 0: (current) (visible to user) [](about:blank)`, }); }); @@ -53,7 +53,7 @@ test('list first tab', async ({ client }) => { }, })).toHaveResponse({ result: `- 0: [](about:blank) -- 1: (current) [Tab one](data:text/html,Tab oneBody one)`, +- 1: (current) (visible to user) [Tab one](data:text/html,Tab oneBody one)`, }); }); @@ -87,7 +87,7 @@ test('create new tab with url', async ({ client }) => { }, })).toHaveResponse({ result: `- 0: [](about:blank) -- 1: (current) [Tab one](data:text/html,Tab oneBody one)`, +- 1: (current) (visible to user) [Tab one](data:text/html,Tab oneBody one)`, }); }); @@ -103,7 +103,7 @@ test('select tab', async ({ client }) => { }, })).toHaveResponse({ result: `- 0: [](about:blank) -- 1: (current) [Tab one](data:text/html,Tab oneBody one) +- 1: (current) (visible to user) [Tab one](data:text/html,Tab oneBody one) - 2: [Tab two](data:text/html,Tab twoBody two)`, }); @@ -114,7 +114,7 @@ test('select tab', async ({ client }) => { index: 0, }, })).toHaveResponse({ - result: `- 0: (current) [](about:blank) + result: `- 0: (current) (visible to user) [](about:blank) - 1: [Tab one](data:text/html,Tab oneBody one) - 2: [Tab two](data:text/html,Tab twoBody two)`, }); @@ -132,7 +132,7 @@ test('close tab', async ({ client }) => { }, })).toHaveResponse({ result: `- 0: [](about:blank) -- 1: (current) [Tab one](data:text/html,Tab oneBody one)`, +- 1: (current) (visible to user) [Tab one](data:text/html,Tab oneBody one)`, }); });