← Back to microsoft/playwright
microsoft / playwright · Issue No. 41873
playwright 1.61.1
Chrome 150 now exposes the real browser-UI state of tabs through CDP. I would like Playwright to expose a read-only way to answer questions such as:
Page or pages belong to that tab?This is especially useful when Playwright is connected to a user-owned browser, or when a human and automation coexist in the same headed browser. It is also useful for MCP/browser-agent integrations, where assuming that the newest page is the foreground page is unreliable.
This request is specifically about reading the real Chrome tab-strip state. It is separate from:
page.bringToFront() / Target.activateTarget;Emulation.setFocusEmulationEnabled; andHistorically, CDP only exposed page/renderer targets. It did not expose the browser UI metadata needed to know which tab a human was actually viewing.
In particular:
Target.getTargets is not tab-strip order or focus history.document.hasFocus(), document.visibilityState, focus/blur listeners, and injected scripts are renderer-level signals rather than authoritative tab-strip state.page.bringToFront() mutates state, may steal user focus, and cannot be used as a read operation.chrome.tabs, but requiring an extension is not a general Playwright solution.Playwright also intentionally makes Chromium pages behave as focused by sending:
Emulation.setFocusEmulationEnabled({ enabled: true })
That behavior was introduced to make focus/blur events and automation deterministic and to avoid the timer, RAF, rendering, and screenshot problems associated with background pages. As a result, page-JavaScript checks cannot reliably identify the real foreground tab in a Playwright-controlled browser.
Several previous issues therefore correctly concluded that Playwright could not answer this question at the time.
Chrome 150, now in the Stable channel, adds an embedderData object to Target.TargetInfo for targets of type "tab".
A client can now request tab targets directly:
const { targetInfos } = await cdp.send("Target.getTargets", {
filter: [
{ type: "tab", exclude: false },
{ exclude: true },
],
});
Each Chrome tab target can include:
type ChromeTabEmbedderData = {
tabStripIndex: number;
tabActive: boolean;
tabPinned: boolean;
tabGroupId?: string;
};
The existing browserContextId identifies the browser context, and Browser.getWindowForTarget can return the containing Chrome window ID.
Playwright current Chromium protocol types already include the new TargetInfo.embedderData field as a generic object, so the protocol definition has reached the repository. The remaining work is to consume the tab targets, associate them with Playwright pages, and expose an appropriate public API.
The Chromium implementation and background are documented here:
A few protocol details:
"tab" targets, not "page" targets, a tab can contain multiple page targets because of Chromium's multi-page architecture, so implementations should not assume a one-tab-to-one-page relationship.Target.autoAttachRelated can be used to discover the page targets related to a tab target.Target.targetInfoChanged events when only this tab metadata changes.tabActive means selected in its containing Chrome window. It does not by itself say that the native Chrome window is the OS foreground window.I do not have a strong preference about the exact public shape. A few possibilities:
await page.isActive(): Promise<boolean | null>
null could mean unavailable on this browser/version or that the page cannot be mapped to a tab.
type TabInfo = {
active: boolean;
index: number;
pinned: boolean;
groupId: string | null;
windowId?: string;
};
await page.tabInfo(): Promise<TabInfo | null>
Making this asynchronous communicates that it is a current snapshot rather than a live handle.
const tabs = await context.tabs();
for (const tab of tabs) {
console.log(
tab.active,
tab.index,
tab.pinned,
tab.groupId,
tab.windowId,
tab.pages(),
);
}
This may fit the underlying model best because a Chrome tab is a browser-UI container and can own multiple page targets.
If a cross-browser Playwright API is premature, this could initially be exposed as a Chromium-specific/experimental API while preserving a shape that Firefox and WebKit could implement later if equivalent browser-level data becomes available.
For Chrome versions before 150 and for embedders that do not provide the metadata, the API could return null, omit tab metadata, or throw a clearly documented unsupported-operation error.
Reading tabActive does not require Playwright to remove its existing focus emulation.
A low-risk first implementation could:
Emulation.setFocusEmulationEnabled({enabled: true}) by default;tabInfo(), isActive(), or context.tabs();document.hasFocus(), document.visibilityState, timer throttling, or existing test behavior; andpage.bringToFront() as an explicit mutating operation.Separately, Playwright could eventually offer an opt-in such as a context option or page method to disable focus emulation for tests that specifically need real blur/visibility/background behavior. That would solve a different part of the older requests and would need careful handling because it reintroduces browser throttling and rendering differences.
In other words, exposing real tab metadata can be implemented without changing Playwright's deterministic focus defaults. Real focus/background emulation can remain a separate design decision.
page.isActive()visibilitychangedocument.visibilityStatedocument.hasFocus() behavior under forced focus emulationpage.bringToFront()createPagesInBackground option, closed unmergedThe new Chrome metadata directly addresses the read/query side of #31890, #8090, and part of #3570. It does not by itself solve background-state emulation or OS-level focus stealing, but it lets Playwright stop relying on guesses or page-JavaScript heuristics when it needs to know the real foreground tab.
Playwright is increasingly used beyond QA in hybrid environments where automation, browser agents, MCP clients, and humans share a headed browser. In those environments, reliable foreground-tab tracking is essential to avoid working on the wrong page. This is especially important for https://github.com/microsoft/Webwright and https://github.com/microsoft/playwright-mcp where agent focus and browser tab focus often need to be kept in sync so the user can follow what an agent is doing.
Relay reads this issue against the repository's contribution signals: the files it is likely to touch, how the maintainers triage work this size, and what the first contribution would exercise.
The full analysis for this issue is still being assembled. Until then, the description above and the thread on GitHub are the most reliable context.