playwrightで要素を選択する方法5種類とその挙動の違い

playwrightで要素を選択する方法はいくつかありますが挙動が微妙に異なるので違いを調べました。
動作確認はplaywright 1.36.1で実施しています。

# 要素を選択する方法色々


## 1. `waitForSelector()`

指定したセレクタが表示されるまで待ってから取得する。デフォルトでは最大30000msまで待機し、出現しないとテストは失敗する。複数に一致する場合先頭の1件のみ取得されることに注意。

```
const element = await page.waitForSelector('[data-testid="my-element-selector"]');
```

## 2. `$()`

指定したセレクタに一致する先頭の1件の要素を取得する。要素がない場合はnullが返る。
ReactでAPIからデータを取得→state変更→描画のようなつくりの場合描画前にセレクタを取得してnullになることに注意。

```
const element = await page.$('[data-testid="my-element-selector"]');
```

## 3. `$$()`

指定したセレクタに一致するすべての要素を取得し配列で返す。要素がない場合はから配列が返る。
`$()`同様、ReactでAPIからデータを取得→state変更→描画のようなつくりの場合描画前にセレクタを取得して空配列になることに注意。

```
const elements = await page.$$('[data-testid="my-element-selector"]');
```

## 4. `locator()`
指定したセレクタを検索することができる。要素の取得には以下のように`.first()`や`.all()`を使う。

```
# .first()で先頭要素を取得
const locator = page.locator('[data-testid="my-element-selector"]').first();

# .all()で全要素を取得
const locators = await page.locator('[data-testid="my-element-selector"]').all();

# 対象のtextContentを取得する
const textContentawaitFirst = await locator.textContent();
const textContentawaitIdx0 = await locators[0].textContent();

# allTextContents()で全要素のtextContentを取得する
const textContents = await locators.allTextContents();
```

ただしReactのような動的に変化するページで`.all()`を使う場合要素の出現を待たないので注意が必要。
要素の出現を待つ場合は、`waitForSelector()`を挟むとよい。

```
await page.waitForSelector('[data-testid="my-element-selector"]');
const locators = await page.locator('[data-testid="my-element-selector"]').all();
```

### `locator()`の挙動についてもう少し詳しく
React等で、「APIからデータを取得→state変更→描画」といった挙動をするページで以下のようなコードを実行する。

**コード**
```
const locator = page.locator('[data-testid="my-element-selector"]');
console.log('waitForSelector前-----------');
console.log('locator :');
console.log(locator);
console.log('-----------');
console.log('locator.first() :');
console.log(locator.first());
console.log('-----------');
console.log('await locator.all() :');
console.log(await locator.all());
console.log('-----------');
console.log('locator.first().allTextContents() :');
console.log(await locator.first().allTextContents());
console.log('-----------');
console.log('await locator.allTextContents() :');
console.log(await locator.allTextContents());

await page.waitForSelector('[data-testid="my-element-selector"]'); # ここでwaitForSelectorにより要素の出現を待つ

console.log('waitForSelector後-----------');
console.log('locator :');
console.log(locator);
console.log('-----------');
console.log('locator.first() :');
console.log(locator.first());
console.log('-----------');
console.log('await locator.all() :');
console.log(await locator.all());
console.log('-----------');
console.log('locator.first().allTextContents() :');
console.log(await locator.first().allTextContents());
console.log('-----------');
console.log('await locator.allTextContents() :');
console.log(await locator.allTextContents());
```

この出力は以下のようになる。

**console.logの出力**
```
waitForSelector前-----------
locator :
Locator@[data-testid="my-element-selector"]
-----------
locator.first() :
Locator@[data-testid="my-element-selector"] >> nth=0
-----------
await locator.all() :
[]
-----------
locator.first().allTextContents() :
[]
-----------
await locator.allTextContents() :
[]
waitForSelector後-----------
locator :
Locator@[data-testid="my-element-selector"]
-----------
locator.first() :
Locator@[data-testid="my-element-selector"] >> nth=0
-----------
await locator.all() :
[
  Locator@[data-testid="my-element-selector"] >> nth=0,
  Locator@[data-testid="my-element-selector"] >> nth=1,
  Locator@[data-testid="my-element-selector"] >> nth=2,
  Locator@[data-testid="my-element-selector"] >> nth=3,
  Locator@[data-testid="my-element-selector"] >> nth=4,
  Locator@[data-testid="my-element-selector"] >> nth=5,
  Locator@[data-testid="my-element-selector"] >> nth=6,
  Locator@[data-testid="my-element-selector"] >> nth=7,
  Locator@[data-testid="my-element-selector"] >> nth=8,
  Locator@[data-testid="my-element-selector"] >> nth=9,
  Locator@[data-testid="my-element-selector"] >> nth=10,
  Locator@[data-testid="my-element-selector"] >> nth=11
]
-----------
locator.first().allTextContents() :
[ 'Some Text' ]
-----------
await locator.allTextContents() :
[
  'Some Text', 'Some Text',
  'Some Text', 'Some Text',
  'Some Text', 'Some Text',
  'Some Text', 'Some Text',
  'Some Text', 'Some Text',
  'Some Text', 'Some Text'
]
```

このことから`.first()`, `.all()`はdata-testidを持つ要素の出現を待っていないことがわかる。
また、`allTextContents()`も要素の出現を待たないが、以下のように`textContent()`等を使う場合は`textContent()`時点で要素の出現を待つようになる。

```
# Some Textが出力される
console.log('await locator.first().textContent() :');
console.log(await locator.first().textContent());
```

## 5. `page.textContent()`など

`locator()`を使う場合と基本的に同じ挙動だが、`locator()`を使うことが推奨。


---

# まとめ
 playwrightでの要素取得方法とその違いを書きましたが`locator()`を使う方法が基本的に推奨されているようです。ただし、Reactのような動的に変化するページでは`waitForSelector()`と組み合わせて使用する必要があるようです。