Skip to content

[Feature]: Expose Chromium foreground-tab and tab-strip metadata (Chrome 150+) #41873

Description

@pirate

Current package version

playwright 1.61.1

🚀 Feature Request

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:

  • Which tab is active in each Chrome window?
  • Where is a tab in the tab strip?
  • Is it pinned or grouped?
  • Which Chrome window contains it?
  • Which Playwright 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:

  1. activating a tab with page.bringToFront() / Target.activateTarget;
  2. raising or focusing the native Chrome application/window at the OS level;
  3. emulating DOM focus with Emulation.setFocusEmulationEnabled; and
  4. disabling renderer throttling for background or occluded pages.

Why this was not possible before

Historically, 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:

  • The order returned by Target.getTargets is not tab-strip order or focus history.
  • The most recently created page is not necessarily the active tab.
  • document.hasFocus(), document.visibilityState, focus/blur listeners, and injected scripts are renderer-level signals rather than authoritative tab-strip state.
  • Calling page.bringToFront() mutates state, may steal user focus, and cannot be used as a read operation.
  • A browser extension can query 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.

What changed in Chrome 150

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:

  • This metadata belongs to "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.
  • The result is currently pull-based. Chrome does not emit new 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.

Possible Playwright API shapes

I do not have a strong preference about the exact public shape. A few possibilities:

1. Minimal page query

await page.isActive(): Promise<boolean | null>

null could mean unavailable on this browser/version or that the page cannot be mapped to a tab.

2. Page tab snapshot

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.

3. Browser-context tab inventory

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.

4. Chromium-specific API first

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.

Interaction with Playwright focus emulation

Reading tabActive does not require Playwright to remove its existing focus emulation.

A low-risk first implementation could:

  • preserve Emulation.setFocusEmulationEnabled({enabled: true}) by default;
  • expose the real tab-strip state independently through tabInfo(), isActive(), or context.tabs();
  • avoid changing document.hasFocus(), document.visibilityState, timer throttling, or existing test behavior; and
  • keep page.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.

Related issues and PRs

Reading the active/user-selected tab

Background, visibility, and focus behavior

Activation and native-window focus

The 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.

Motivation

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.

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions