Playwrightで取得済みLocatorから1要素だけ絞り込む方法(filter({has})ではなく.and())

概要

今回はPlaywrightで一度取得した複数要素のLocatorから、特定の属性を持つ1要素だけを絞り込んで操作する方法について書いていきます。

ラジオボタンやチェックボックスのようにname属性が同じでvalue違いの要素を扱いたいなんてこと結構あると思います!

そういったときpage.locator('input[name="status"]')で要素を取得し、要素が「value="published"のものをcheckする」といった書き方をすると思います。

しかしこの方法が地味に難しく、自分は最初.filter({ has: ... })で絞り込もうとして全然動かず、地味に時間を溶かしたことがあります。

色々試した結果うまくいった方法は.and()を使うことでした。

この記事では、.and()の使い方と、なぜ.filter({ has })では動かないのかを紹介していこうと思います。

サクッと読める短い記事なので、Playwrightでradio/checkboxを扱っている方は是非見ていってください!

それではやっていきましょう(^^!

目次

やりたかったこと

やりたいことを具体化するとこういう構成です。

  • ステータスのラジオグループをPage Object Model のクラスのコンストラクタで一度だけ取得しておく
  • クラス内のメソッドで引数として value を受け取り、その value に一致するラジオボタンだけを check したい

HTMLはこういう構成を想定しています。

status-radio.html
1
2
3
<input type="radio" name="status" value="draft" />
<input type="radio" name="status" value="published" />
<input type="radio" name="status" value="archived" />

記事中はシンプルさを優先して const で書きますが、クラスのプロパティとして保持しているケースと本質は同じです。

test.spec.js
1
2
3
4
5
// 実際はクラスのコンストラクタで1回だけ取得して this.statusRadioGroup に保持
const statusRadioGroup = page.locator('input[name="status"]');

// メソッド呼び出し時に引数で受け取った value(draft / published / archived のいずれか)で
// 絞り込んで check したい

動かなかったコード(filter({ has }) を使う方法)

最初に書いたのがこれでした。

test.spec.js
1
2
3
4
5
6
7
const statusRadioGroup = page.locator('input[name="status"]');
const value = "published"; // メソッド引数で受け取ったvalueを想定

// ❌ これは動かない
await statusRadioGroup
.filter({ has: page.locator(`[value="${value}"]`) })
.check();

実行するとError: locator.check: Target ... resolved to 0 elementsのようなエラーで落ちます。

セレクタ的には合っているように見えるのに、なぜ要素ゼロになるのか。
これにどれだけ苦しまれたことか…

動いたコード(.and() を使う方法)

正解はこっちでした。

test.spec.js
1
2
3
4
5
6
7
const statusRadioGroup = page.locator('input[name="status"]');
const value = "published"; // メソッド引数で受け取ったvalueを想定

// ✅ これは動く
await statusRadioGroup
.and(page.locator(`[value="${value}"]`))
.check();

.and()は2つの Locator のAND(積集合)を返すメソッドなので、input[name="status"]にも[value="published"]にもマッチする input が、そのまま1つだけヒットします。

なぜ .filter({ has: ... }) だと動かないのか

ここが理解のコアです。

.filter({ has: someLocator })CSSの:has()擬似クラス相当で「指定した Locator を子孫に持つ要素」を絞り込むオプションです。

つまり今回のコードは、Playwright 的にはこう解釈されます。

  • 元の Locator: input[name="status"] にマッチする input 全部
  • フィルタ条件: その input の子孫[value="published"] を持つもの

ところが<input>空要素(void element)で子要素を持てないので、「子孫に何かを含む input」は存在しません。
結果、絞り込み後の要素数はゼロになり、.check()が「0 elements」エラーで落ちる、というわけです。

要はtable要素などの中の要素を取得するときに使用するのが正しいということですね(^^

一方.and()同じ要素に対する2つの Locator の ANDを取るので、input 自身が両方の条件を満たせばヒットします。

使い分けまとめ

軽く整理しておくと、こんな感じで使い分けると間違えにくいです。

  • .and(other)
    • 同じ要素に対する複数条件の AND
    • 今回の radio / checkbox / button[disabled] みたいな属性絞り込み
  • .filter({ has: locator })
    • 子孫に特定要素を含む親を絞る
    • リストアイテムや li / tr / card 系で「中に〇〇を持つ行」を取りたいとき
  • .filter({ hasText: "..." })
    • 含まれるテキストで絞る
  • page.locator('input[name="status"][value="published"]')
    • 1発で書く。要件がない場合、これが一番シンプル

ちなみに今回のケースなら、実は最後の「CSSで属性を連結する書き方」が一番シンプルです。
ただ、value だけが変数で、グループ Locator は使い回したいみたいに役割を分けたいときに .and() が効いてきます。

まとめ

いかがでしたか?
今回はPlaywrightの Locator 絞り込みで地味にハマった.filter({ has }).and()の違いを整理してみました!

今回紹介した点をまとめると以下の通り(^^

  • .filter({ has: ... })は「子孫マッチ」なので、子を持てない<input>系では絞り込み結果がゼロになる
  • ・同一要素に対する複数条件の AND が欲しいときは.and()が正解
  • ・単純に済むならinput[name="status"][value="..."]のようにCSSで属性を連結してもOK
  • ・役割(グループ Locator+動的な value)を分けて使い回したいなら.and()が一番素直

Playwrightは Locator 周りのAPIが豊富な分、似た見た目で挙動がガラッと変わるメソッドが多いので、ハマったときは「これは要素自身を絞るのか、子孫を見ているのか」を意識すると一発で抜けやすいです。

自分も仕事でも個人開発でも Playwright を回している民なので、こういう細かいハマりどころは記事として残しておきたいですね!

ちなみに自分が Playwright で E2E を組んでいる「こぴぺったり」は、コピー&ペーストを簡単に行えるツールになっております。
コピーしたいデータを登録し、登録したデータを押すと文字列がコピーされる仕組みです!
メールアドレスやメールの定型文、PGのよくある書き方集など使い道は様々、ディレクトリ階層がアプリ内にあり、分けやすさも兼ね備えております!

そしてなんと、「完全無料」「登録不要」「コピペデータのデータ送信なし」というセキュリティ的にも安心の一品です。
copype.shinpinoshi.comこぴぺったり
ここから使用できるので、是非使用してみてください!

以上となります!
要件があると厄介になるのはPGあるある。個人開発なら楽なのに…
それではお疲れさまでした!