[Feature]: Option to make all locators ignore hidden elements by default
メンテナーはふだん 1 日以内に返信
関連するプルリクエストがすでにマージされています。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 18/100
- issue の種類
- 機能追加
- 明瞭さ
- 明確に書かれている
- 活発さ
- 停滞
- 技術スタック
- typescript
- 領域
- testing
調査の方向性
Start with the merged pull request #43859 linked from this issue, since it may already cover part of the request. Look at the existing strictSelectors and testIdAttribute context options, and at how locator.visible() and getByRole's includeHidden filter hidden elements. Done means a context option that applies the visibility filter to getByTestId, getByText and locator() by default, with a per-locator includeHidden: true opt-out, and visibility assertions behaving as the issue describes.
索引モデルが issue の本文から書いたものです。
説明
🚀 Feature Request
Add a context/config option that makes every locator skip hidden elements by default, as if .visible() (added in v1.63) were applied to them. The option name is only a suggestion.
// playwright.config.ts
export default defineConfig({
use: {
locatorIncludeHidden: false, // default: true = current behaviour, nothing breaks
},
});
Per-locator opt-out, reusing the name getByRole already has:
page.getByTestId('save', { includeHidden: true });
page.locator('.toast', { includeHidden: true });
Follow-up to #35294, which was closed in the P3 cleanup (#43813) with ~32 👍. Related: #31840, #36045.
Example
Playwright v1.64.1:
test('hidden duplicate from a cached route', async ({ page }) => {
await page.setContent(`
<div hidden><button data-testid="save">Save</button></div> <!-- previous route, kept mounted -->
<div><button data-testid="save">Save</button></div> <!-- active route -->
`);
await page.getByRole('button', { name: 'Save' }).click(); // ✅ getByRole already skips hidden elements
await page.getByTestId('save').visible().click(); // ✅ works only if you put explicit .visible()
await page.getByTestId('save').click(); // ❌ strict mode violation
});
Actual output of the example above on v1.64.1 (Chromium):
Error: locator.click: Error: strict mode violation: getByTestId('save') resolved to 2 elements:
1) <button data-testid="save">Save</button> aka getByTestId('save').first()
2) <button data-testid="save">Save</button> aka getByRole('button', { name: 'Save' })
Call log:
- waiting for getByTestId('save')
9 | await page.getByRole('button', { name: 'Save' }).click(); // ✅ getByRole already skips hidden elements
10 | await page.getByTestId('save').visible().click(); // ✅ works only if you put explicit .visible()
> 11 | await page.getByTestId('save').click(); // ❌ strict mode violation
| ^
Lines 9 and 10 pass and line 11 fails. Playwright itself suggests getByRole() for element 2: it identifies the visible button uniquely, because getByRole already ignores hidden elements. That is exactly the behavior this issue asks to make available to all locators.
Motivation
UI that stays mounted while hidden is now the default in mainstream stacks:
- React 19.2
<Activity mode="hidden">keeps previously rendered UI in the DOM. - Next.js
cacheComponentskeeps visited routes mounted and hidden. - Streaming SSR and hydration briefly leave hidden duplicates in the DOM, which causes flaky strict-mode violations (see comments in #35294).
- react-navigation / react-native-web keep stacked screens mounted.
In these apps, almost no test ever needs to target a hidden element. Matching hidden elements is the exception, but today it is the default.
Why locator.visible() (v1.63) is not enough
.visible() made opting in shorter, but it is still opt-in per locator:
- It has to go on nearly every locator, including those in page objects and shared fixtures. If you forget it once, the test does not fail clearly. It fails intermittently, only when a cached route or hydration duplicate happens to be in the DOM.
- Locator methods behave inconsistently.
getByRoleexcludes hidden elements by default, butgetByText,getByTestId,getByLabel,getByPlaceholderandlocator()do not. - Codegen and the recorder do not add
.visible(), so every recorded locator has to be edited by hand (or AI 🙄).
"Config hides behaviour from the reader" (concern raised in #19235)
Config already changes how locators resolve: testIdAttribute and the strictSelectors context option both do. This option works the same way, is off by default, and lives in one visible place in the config. A team that enables it has decided its app keeps hidden UI mounted, which is a property of the whole project, not of a single locator.
Proposed semantics
To keep the scope small, reuse what already exists:
- What counts as hidden: the same definition
locator.visible()uses. - Where visibility is checked: only on the final matched element, exactly as if
.visible()were appended. - What it affects: element resolution, so actions, strict mode,
count(),all(),toHaveCount(), etc. - Visibility assertions keep their meaning:
toBeVisible()passes when a visible element matches.toBeHidden()/waitFor({ state: 'hidden' })pass when no visible element matches. That is already their contract, since a hidden element and a missing element both satisfy them.
- Only attachment checks need the opt-out:
toBeAttached()/waitFor({ state: 'attached' })on a hidden element need{ includeHidden: true }. - Implementation: a context option next to
strictSelectors, as @dgozman suggested in #35294.
Alternatives considered
.visible()/.filter({ visible: true })on every locator: the current workaround. See the problems above.- Using only
getByRole: not always possible, for example withdata-testidconventions, non-semantic markup, or migrations from Testing Library or Cypress.
- 主要言語
- TypeScript
- スター
- 99.1k
- フォーク
- 6.8k
- 平均マージ
- 17時間 6分
- マージ済み PR(30日)
- 306
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
microsoft/playwright のほかの issue
-
難易度 3/5 1〜2日 初心者へのやさしさ 76/100
microsoft/playwright#43987 · コメント 3 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 3/5 1〜2日 初心者へのやさしさ 72/100
microsoft/playwright#43986 ·
メンテナーはふだん 1 日以内に返信
-
[Bug]: Embedded number/range inputs contribute their `value` attribute, not their current value, to accessible names対応中かも @lazerg が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 35/100
microsoft/playwright#43984 ·
メンテナーはふだん 1 日以内に返信
-
難易度 3/5 1〜2日 初心者へのやさしさ 64/100
microsoft/playwright#43983 ·
メンテナーはふだん 1 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
microsoft/playwright#43981 ·
メンテナーはふだん 1 日以内に返信
microsoft/playwright の issue をすべて見る
似ている issue
-
area:cards enhancement ready-for-agent
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
fil-donadoni/tolaria#8883 ·
メンテナーはふだん 1 日以内に返信
-
xsd:integer cast from a string rejects negative numbers, a leading + and surrounding whitespaceオープンbug 🐛
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
comunica/comunica#1960 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
Waiting for: Product Owner
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
getsentry/sentry-javascript#26203 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
bytedance/flowgram.ai#1235 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
devswha/herdr-web-ui#3325 ·