Playwrightでハマったこと・調べたことの備忘録

# はじめに
Playwrightでテストを書くにあたり、ハマったこと・調べたことなどをまとめた備忘録になります。随時更新予定。

# 確認環境
- Version 1.46.1
- Node.js v20.16.0

# 調べたこと

## デバイスについて
- 下記でデバイス一覧を確認できる。
    - [`node_modules/playwright-core/lib/server/deviceDescriptorsSource.json`](https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json)
- `playwright.config.ts`に設定を追加することでモバイル・iPadもスクリーンショット取得可能。

## 特定のprojectのみテストする場合、`--project=xxx`オプションを追加する。
複数のprojectを`playwright.config.ts`に持っている場合に単一のprojectでテスト実行したい場合に`--project=xxx`オプションを使用する。

例）`chromium`のみテストを実行する。

```
playwright test --project=chromium
```

参考：[Projects | Playwright](https://playwright.dev/docs/test-projects#run-projects)


## テストの実行速度が遅い場合、トレースを記録する。
テストの実行速度が遅い場合の調査は一般的にトレースを記録してどの操作が遅いのか調査する。
`--trace on`を指定すると、レポートにトレースが表示される。

```
npx playwright test xxx.spec.ts --trace on
```

参考：[Recording a Trace](https://playwright.dev/docs/trace-viewer-intro#recording-a-trace)
### trace Viewerが動作しない　※未解決
自分の環境ではserviceWorkersが正常に動作せず、TraceViewerが動作しなかった。`https`で接続する必要があるが、現状改善できないため、[UIモード](https://playwright.dev/docs/test-ui-mode)で確認する。

[\[BUG\] trace viewer (html report) not working in jenkins pipeline · Issue #27655 · microsoft/playwright](https://github.com/microsoft/playwright/issues/27655)
## 特定のテストファイルのみの場合はファイル名を指定する。
```
npx playwright test tests/all-browsers/schedule/event-detail.spec.ts
```

参考：[Command line | Playwright](https://playwright.dev/docs/test-cli)
## アサーション
参考：[Writing tests | Playwright](https://playwright.dev/docs/writing-tests)

## 値チェック

```
<div class="test-div">test txt</div>
```

```sample.ts
const targetDiv = await page.locator('.test-div');
await expect(targetDiv).toHaveText('test txt');
```

## 存在チェック
### 要素が存在するかをチェックする
```
await expect(page.locator("selector")).toBeVisible();
```
### 要素が存在しないかをチェックする
```
await expect(page.locator("selector")).not.toBeVisible();
```


## スタイルのチェック

```
await expect(this.page.locator('.test-dev')).toHaveCSS('background-color', 'rgb(250, 250, 250)')
```

[\[Question\] is there a way to get element background colour ? · Issue #4282 · microsoft/playwright](https://github.com/microsoft/playwright/issues/4282#issuecomment-1914107494)
## 属性取得
参考：[LocatorAssertions | Playwright](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-have-attribute)

### Inputのplaceholderの値をチェックする
```
const expectedPlaceholder = "入力してください。";
// input要素のplaceholder属性の値を取得
const actualPlaceholder = await page.locator('.sample-input').getAttribute('placeholder');
// placeholderの値を検証
expect(actualPlaceholder).toBe(expectedPlaceholder);
```

## Jestの`test.each`のようにテストをまとめられないか？

PlaywrightにはJestの`test.each`のようなものは存在しない。
追加希望のissueはあったが、`forEach`ループを使用するという結論でクローズされている。
[\[Feature\] Add support for test.each / describe.each · Issue #7036 · microsoft/playwright](https://github.com/microsoft/playwright/issues/7036)

```
[
  { a: 1, b: 1, expected: 2 },
  { a: 1, b: 2, expected: 3 },
  { a: 2, b: 1, expected: 3 },
].forEach(({ a, b, expected }) => {
  test(`given ${a} and ${b} as arguments, returns ${expected}`, ({ page }) => {
    expect(a + b).toEqual(expected);
  })
});
```

参考：[Playwright](https://playwright.dev/docs/test-parameterize)

`forEach`は非同期操作を待機しないため、非同期操作を行う場合は`for`文を使用する必要がある。
> forEach() は非同期操作を待機しないため、代わりにこれが必要になります。

```ts
let testList = [
  { name: 'inputA', selector: '.input-a' },
  { name: 'inputB', selector: '.input-b' },
];
for (const { name, selector } of testList) {
    await test(`${name}表示テスト$`, async({ page }) => {
    let elem = await page.locator(`${selector}`);
    expect(elem).toBeVisible();
  })
}
```

[foreach in playwright(typescript) · Issue #19943 · microsoft/playwright](https://github.com/microsoft/playwright/issues/19943)
## デバイスによって実行するテストを変える

下記のパターン
### `isMobile`オプションを使用する。
- [TestOptions | Playwright](https://playwright.dev/docs/api/class-testoptions#test-options-is-mobile)
- テスト内の一部をデバイスによって分岐したい場合
- ただし、タブレット端末も`isMobile=true`となるので注意する。
```
test('ログイン', async ({ page, isMobile }) => {

...（省略）

      if (isMobile) {
        //特定のセレクタを持つ要素が描画されるまで待つ
          await page.waitForSelector('#sp-container');
        else {
          await page.waitForSelector('#pc-container');
      }
```
### `playwright.config.ts`の`projects`内で対象デバイスprojectのtestMatchを指定する。
- project.name（`testInfo.project.name`）を参照する。

```
test('ログイン', async ({ page, isMobile }, testInfo) => {

...（省略）


      switch (testInfo.project.name) {
        //特定のセレクタを持つ要素が描画されるまで待つ
        case "Mobile Safari":
          await page.waitForSelector('#sp-container');
          break;
        default:
          await page.waitForSelector('#pc-container');
      }
```

### デバイスによってスキップする
`beforeEach`でデバイスをチェックしてテストをスキップすることが可能。
ただし、出力でskipX件と表示される。skipして良いものか確認必要になりそうなので、ファイルを分けてテストした方が良いかもしれない。。

```
test.describe("簡易入力ダイアログテスト", () => {
    test.beforeEach(({ isMobile }) => {
        test.skip(isMobile, "スキップの説明");
    });

    test("簡易入力ダイアログが表示されるか", async ({ page }) => {

```

## テストのファイル名取得
```sample.spec.ts
test('random test', async ({ page }, testInfo) => {
    await page.goto('https://playwright.dev/');

    console.log(testInfo.title);
    console.log(testInfo.titlePath);
});
```


参考：[How to get the current test name for Playwright in TypeScript? - Stack Overflow](https://stackoverflow.com/a/73775050)


## APIリクエストのレスポンスを受け取るまで待機する

すべての[Request](https://playwright.dev/docs/api/class-request "リクエスト")と[Response](https://playwright.dev/docs/api/class-response "応答")を監視できる。
`page.waitForResponse()`を使用することで待機できる。
APIリクエスト前に下記を記述する。
```
const responsePromise = page.waitForResponse('**/sample_api/fetch_data');
// リクエストURLに該当文字列を含む
const responsePromise = page.waitForResponse((response) => response.url().includes(`sample_api/events/${対象ID}`));
```

ステータスのテスト。
```
expect(apiResponse.status()).toBe(200);
```

`json()`でレスポンスを取得・テストできる。
```
let resBody = await apiResponse.json();
 let createEventId = resBody?.result?.event_id;
```

参考：[Network | Playwright](https://playwright.dev/docs/network#network-events)

## 要素をクリックする

```
await page.locator('sample-btn').click();
```

デフォルトは中央をクリックする。クリック位置を指定したい場合はオプションの`position`を指定する。
```
await page.locator('sample-div').click({
    position: { x: 30, y: 15 },
  });
```
位置以外に左クリック、クリックする回数、押下する修飾キーなどのオプションがある。詳細は公式[Locator](https://playwright.dev/docs/api/class-locator#locator-click)参照。

## 要素をドラッグ＆ドロップする
いくつか方法がある。ただし、`dragTo`と`dragToDrop`はドロップが正常に動作しないパターンが存在するため注意が必要。※詳細はハマった項目に記載。

### dragTo

```
await page.locator(`#draggable`).dragTo(page.locator(`#droppable`));
```
参考：[drag To](https://playwright.dev/docs/api/class-locator#locator-drag-to)
### dragToDrop

```
await page.dragAndDrop('#draggable', '#droppable');
```

参考：[Page | Playwright](https://playwright.dev/docs/api/class-page#page-drag-and-drop)

### mouse
```
await page.locator('#```').hover();
await page.mouse.down();
await page.locator('#droppable').hover();
await page.mouse.up();
```

参考：[Actions | Playwright](https://playwright.dev/docs/input#drag-and-drop)


## 日時

```
    await page.clock.setFixedTime(new Date('2024-10-10T00:00:00'));
```
# ハマったこと

## ドラッグ＆ドロップが上手く動作しない。
`dragTo`や`dragToDrop`を使用した際に、永遠に上下スクロールし続け、ドラッグ＆ドロップが正常に動作しない現象が発生した。
### 原因
- `dragTo`や`dragToDrop`では指定したLocator位置へ一瞬でカーソルが移動する形となる。この挙動で正しい位置を取得できなくなっている可能性がある。
- ドラッグ＆ドロップする要素の親がスクロール可能になっている。ここがスクロールするため、正しい位置が取得できていない可能性がある。

### 対処法
- ドラッグ＆ドロップを行う前に、親要素などスクロールしている要素の位置を調整する。下記の例は一番上にスクロールを行っている。
```sample.ts
await page.$eval(`[id=`${DRAG_DROP_PARENT_ID}`]`, (element) => {
    element.scrollTo(0, 0);
});
```

- `dragTo`や`dragToDrop`は使用せず、`page.mouse`を使用する。
- `page.mouse.move`のoptions`steps`を適切に指定することで解消。
	- ただし、Safariはstepsを指定すると正常に動作しなくなるため未指定にした。
>options Object (optional)
	steps number (optional)#
	Defaults to 1. Sends intermediate mousemove events.
	
参考：[Mouse | Playwright](https://playwright.dev/docs/api/class-mouse)

```sample.ts
export async function drag(page: Page, draggable: Locator, droppable: Locator, isSafari: boolean) {
    const box = (await droppable.boundingBox())!;
    await draggable.hover();
    await page.mouse.down();

    let options = isSafari ? {} : { steps: 5 };
    await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, options);
    await page.mouse.up();
};
```
### 参考
- [\[BUG\] dragTo / mouse actions not working in our application · Issue #20254 · microsoft/playwright](https://github.com/microsoft/playwright/issues/20254)
- [javascript - Playwright page.click scrolls too much - Stack Overflow](https://stackoverflow.com/a/73470527)
- [PlaywrightでdragToメソッドが上手く動作しない時の対処方法 │ Find out by JURABI](https://blog.jurabi.jp/how-to-troubleshoot-when-the-dragto-method-in-playwright-is-not-working-properly/)
# エラー関連
## 数字で始まるidをselectorで指定するとエラーになる。
### 対処法
属性セレクタで指定する。

```
await page.waitForSelector('[id="20240930"]');
```
### 原因
CSS3 IDセレクタ仕様で、#のあとに数字を使用してはいけないため、エラーになる。
> they cannot start with a digit, two hyphens, or a hyphen followed by a digit.
> 数字、2つのハイフン、ハイフンの後に数字で始めることはできない。

引用元：[Syntax and basic data types](https://www.w3.org/TR/CSS2/syndata.html#characters)

```
await page.waitForSelector('#20240930');
```

```bash
page.waitForSelector: SyntaxError: Failed to execute 'querySelectorAll' on 'Document': '#20240930' is not a valid selector
```

参考：[Document.querySelector() で先頭が数字のIDを指定するとエラーが起きる。](https://jun-app.com/articles/queryselector-error-with-numeric)


## テストに問題はないが時々`navigating to "http://sample.com", waiting until "load"`で落ちる

### 原因
- 自分の場合は、テストしているアプリ自体の問題でAPIリクエストに失敗し、対象要素が表示されていなかった。
### 対処法
- 前述の原因だったため未確認だが、単純に時間がかかっていて落ちる場合は下記を試してみる。
	- PlaywrightTestConfigで`timeout`の値を変更する。`workers`の数を指定してみる。
### 参考
- [Navigating to "url", waiting until "load" - Python Playwright Issue - Stack Overflow](https://stackoverflow.com/questions/68266451/navigating-to-url-waiting-until-load-python-playwright-issue)
- [Playwrightの並列数(worker数)を指定 - Minerva](https://minerva.mamansoft.net/Notes/Playwright%E3%81%AE%E4%B8%A6%E5%88%97%E6%95%B0(worker%E6%95%B0)%E3%82%92%E6%8C%87%E5%AE%9A)


## Bootstrapのtooltipがテストできない。
- `.tooltip`などをSelectorに指定して`data-toggle="tooltip"`要素のLocatorを取得しようと試みたが取得できなかった。
- そもそも`data-toggle="tooltip"`要素に`mouse.hover`でホバーしても、ツールチップが表示されない。

mountされたコンポーネントを基にしてテストを行う方法が
[\[BUG\] Playwright component tests fail to trigger Fluent tooltip · Issue #27660 · microsoft/playwright · GitHub](https://github.com/microsoft/playwright/issues/27660)
Angularではライブラリ

# 調査したいこと
- [ ] 要素のtextContentの値が一致するまで待機は可能？
	- `await page.waitForSelector('text="Please provide a new password."');`で待機可能か？ [\[BUG\] page.waitForSelector('text="something"'); flaky · Issue #1239 · microsoft/playwright](https://github.com/microsoft/playwright/issues/1239)
- [ ] テスト数の多いテストファイルでタイムアウトが発生する。テストに含める内容・テストの細分化のベストプラクティス