Open43

Playwright Tips

tatatsurutatatsuru

並列処理テスト実行

test.describe.parallel("Visual regression testing of page", () => {
  何かテストを記載
})

parallelで中に書いてあるテストを順番にではなく、並列で処理できる。

tatatsurutatatsuru

スナップショットを撮り、UIの変化がないかテスト

// pageのスナップショットを撮るテスト関数
const takeSnapShot = async (page: Page, name: string) => {
  expect(await page.screenshot({ fullPage: true })).toMatchSnapshot([
    name,
    `${name}.png`,
  ]);
};

使い方

const baseUrl = "https://hogehoge.com"

const pagePathForSnapShotTest = [
  // ここに各ページの情報を追加
  {
    name: "home",
    path: `${baseUrl}`,
  },
  {
    name: "about",
    path: `${baseUrl}about/`,
  },
];

test.describe.parallel("Checking drawing", () => {
  pagePathForSnapShotTest.map((item) => {
    test(`snapshot test ${item.name}`, async ({ page }) => {
       await takeSnapShot(page, `${item.name}`);
    })
  });
});
tatatsurutatatsuru

画像の描画を待ってから処理を行いたい時

const waitForImageLoad = async (page: Page) => {
  const imgElements = await page.$$("img");
  const imageLoadPromises = imgElements.map(async (imgElement) => {
    if (imgElement) {
      await imgElement.evaluate((node) => {
        return new Promise<void>((resolve) => {
          if (node.complete) {
            resolve();
          } else {
            node.addEventListener("load", () => {
              resolve();
            });
          }
        });
      });
    }
  });

  await Promise.all(imageLoadPromises);
};

2023.12.5追記
https://github.com/microsoft/playwright/issues/6046
https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/complete

const waitForImageLoad = async (page: Page) => {
  for (const img of await page.getByRole("img").all()) {
    await expect(img).toHaveJSProperty("complete", true);
    await expect(img).not.toHaveJSProperty("naturalWidth", 0);
  }
};
tatatsurutatatsuru

evaluateとは

Playwright スクリプト内でブラウザのページ上で JavaScript コードを実行するためのメソッド

tatatsurutatatsuru

厳格性

例) locatorで要素を指定する

await page.locator(".class名 a").click();

このような時に下記エラーが出る場合が...。

Error: locator.click: Error: strict mode violation: locator('.mf_finder_drilldown_reset a') resolved to 2 elements:

これはplaywrightなどのテストでどれを最終的にクリックしたらいいの?と聞いてきてる。
ですので、テストコードを書くときは厳格に要素を指定してあげてみたらGood!!

tatatsurutatatsuru

exact

nameを完全に大文字と小文字を区別し、文字列全体をマッチさせるもの。
デフォルトはfalse。
name が正規表現の場合は無視される。
完全一致でも空白は切り捨てられることに注意。

await page.getByRole("link", { name: "top", exact: true }).click();
tatatsurutatatsuru

何を持ってしてkey down系の操作を正とするか

await page.getByPlaceholder("検索ボックス").press("ArrowDown");

などのkey系の操作はどのようにできてるできてないを判断するの...??

tatatsurutatatsuru

playwright test-report

http://localhost:9323

初期だと上記URLで開く。(テストに失敗すると勝手に開く)

portがたまに使えない時が訪れるので

lsof -i

で動いてるportを確認してみて

kill -9 プロセスID

でkillすれば解決

※間違って違うプロセスを消さないように!!!

tatatsurutatatsuru

Best Practices

https://playwright.dev/docs/best-practices

  • ロケータを使う
    エンドツーエンドのテストを書くには、まずウェブページ上の要素を見つける必要があります。これを行うには、Playwrightに組み込まれているロケータを使用する。ロケーターには自動待機とリトライ機能が付いている。自動待機とは、Playwrightがクリックを実行する前に、要素が表示されていて有効になっていることを確認するなど、要素に対してさまざまな実行可能性チェックを行うことを意味します。テストに弾力性を持たせるために、ユーザー向けの属性と明示的な契約を優先することをお勧めします。

  • ロケータの生成
    Playwrightには、テストを生成してロケータを選択するテストジェネレータがあります。ページを見て、ロール、テキスト、テスト ID ロケータを優先して、最適なロケータを決定します。ロケータにマッチする要素が複数見つかった場合は、ロケータを改良して弾力性を持たせ、ターゲット要素を一意に識別できるようにします。

  • ウェブ・ファーストのアサーションを使う
    アサーションは、期待する結果と実際の結果が一致するかどうかを検証するための方法です。ウェブファーストアサーションを使用することで、Playwright は期待する条件が満たされるまで待機する。例えば、アラートメッセージをテストする場合、テストはメッセージを表示させるボタンをクリックし、アラートメッセージがあるかどうかをチェックする。アラートメッセージが表示されるまでに 0.5 秒かかる場合、toBeVisible() などのアサーションは待機し、必要に応じて再試行します。

  • デバッグの設定
    ローカルデバッグのためには、VS Code エクステンションをインストールして、VSCode でテストをライブデバッグすることをお勧めします。デバッグモードでテストを実行するには、実行したいテストの横の行を右クリックしてブラウザウィンドウを開き、ブレークポイントが設定された場所で一時停止します。

  • Playwrightの依存関係を最新に保つ
    Playwrightのバージョンを最新に保つことで、最新のブラウザバージョンでアプリをテストし、最新のブラウザバージョンが一般公開される前に不具合を検出することができます。

tatatsurutatatsuru

アサーション

あるコードが実行される時に満たされるべき条件を記述して実行時にチェックする仕組みをアサーションという

アサーションの例

// div.classがあるか調べる
// 要素のセレクター
const elementSelector = "div.class";

// 要素が存在するか確認
const isElementExists = await page
  .waitForSelector(elementSelector, { timeout: 5000 })
  .then(() => {
    console.log("element is found");
    return true;
  })
  .catch(() => {
    console.log("element is not found");
    return false;
  })

// ここでtrue出ないとテストがストップする
// アサーション
expect(isElementExists).toBeTruthy();

アサーション
https://playwright.dev/docs/test-assertions#auto-retrying-assertions

tatatsurutatatsuru

console errorの確認

const pageErrors: any[] = [];
page.on("pageerror", (exception) => pageErrors.push(exception));
expect(pageErrors).toMatchObject([]);
tatatsurutatatsuru

selectorに苦戦した話

<select
  :name="name"
  v-model="categoryCurrent"
  @focus="onFocusSelect"
  @blur="onBlurSelect"
  @change="onChangeSelect"
>
  <option
    v-if="!disableCategoryAll"
    class="class_opt_0"
    value=""
  >
    {{ categoryAllText }}
  </option>
  <option
    v-for="(val, i) in categoryValues"
    :key="i"
    :class="'class_opt_' + (i + 1)"
    :value="val"
  >
    {{ val }}
  </option>
</select>

↓ 出力

<select name="name" class="class">
  <option value="" class="class_opt_0">
    test0
  </option>
  <option class="class_opt_1" value="test1">
    test1
  </option>
  <option class="class_opt_2" value="test2">
    test2
  </option>
  <option class="class_opt_3" value="test3">
    test3
  </option>
</select>

こんな感じで実装されているslectorのテストを行いたいと思って

await page.getByLabel('test1 test2 test3').selectOption('test2');

と書いたら
表示はちゃんと切り替わっているのに、検索結果を絞り込めていなかった。。。

何かのバグかな?と思って
https://github.com/microsoft/playwright/issues/15626
をみてみた。

↓ 翻訳

へ〜って感じだったが、今回解決したいこととは関係なかったw

再度Vueで書いたコードを見てみると@focusが入っており、そのフォーカスした値がslectしている値と同じなら検索が走るという分岐が書いてあったので、一度フォーカスさせてからselectOptionをしてみた。

const selector = page.getByLabel(
  "test1 test2 test3"
);

await selector.focus();
await selector.selectOption("test2");

見事OK。スナップショットで確認してみたら、通常の挙動通りに動いている様子を確認することができた。

tatatsurutatatsuru

git actionsでテストを実行

https://zenn.dev/leaner_dev/articles/88272c891d4fb5

https://zenn.dev/keita_hino/scraps/c45380f8e3804d

playright.config.ts
+ retries: process.env.CI ? 2 : 0,
- retries: process.env.CI ? 1 : 0,

https://zenn.dev/keita_hino/articles/481c77e21f7282

2024.04.18
workerは1が推奨されているみたい。flakyなテストをなくすという意味でも。
https://playwright.dev/docs/next/ci#workers

tatatsurutatatsuru

CI上でのfont問題

git actionでテストを実行すると、スナップショット比較の際にフォントのずれが生じてしまう。
Mac OSとgithub action(Linux環境)との差異だということでした。。。

https://qiita.com/taisuke-j/items/feb14d5dedce6ba986c5

Dockerを使用して、今回の問題を解決しようと思う。
https://zenn.dev/kurokimaru/articles/0b15fb3caab925
https://zenn.dev/marokanatani/articles/playwright_visual_test_on_github_actions
https://zenn.dev/keita_hino/articles/d38956a2f1880e
https://zenn.dev/keita_hino/scraps/c45380f8e3804d
https://zenn.dev/keita_hino/articles/481c77e21f7282

tatatsurutatatsuru

git actionのなかでpnpmを使用するとき

test (chromium)
The following actions uses node12 which is deprecated and will be forced to run on node16: pnpm/action-setup@v2.2.2. For more info: https://github.blog/changelog/2023-06-13-github-actions-all-actions-will-run-on-node16-instead-of-node12-by-default/

こんな感じでエラーが出る。

https://github.blog/changelog/2023-06-13-github-actions-all-actions-will-run-on-node16-instead-of-node12-by-default/

これを見るとversionによるwarningが出ていたということがわかった。

playwright.yml
- - uses: pnpm/action-setup@v2.2.2
-   with:
-    version: 6.0
+ - uses: pnpm/action-setup@v2
+   with:
+    version: 8

このように書き直したらwarningが消えた

tatatsurutatatsuru

ubuntuのバージョン

FROM mcr.microsoft.com/playwright:v1.39.0-focal

WORKDIR /app

# pnpmをインストール
RUN npm install -g pnpm

CMD [ "bash", "-c", "pnpm install && /bin/bash" ]
FROM mcr.microsoft.com/playwright:v1.39.0-jammy

WORKDIR /app

# pnpmをインストール
RUN npm install -g pnpm

CMD [ "bash", "-c", "pnpm install && /bin/bash" ]

それぞれFROMの部分が違う。
focal -> Ubuntu 20.04 LTS
jammy -> Ubuntu 22.04 LTS

とそれぞれのバージョンのコードネームになっている

https://github.com/actions/runner-images
https://hub.docker.com/_/microsoft-playwright

tatatsurutatatsuru

Artifactエラー

Error: Create Artifact Container failed: Artifact storage quota has been hit. Unable to upload any new artifacts

解決方法
https://docs.github.com/ja/actions/managing-workflow-runs/removing-workflow-artifacts
https://qiita.com/ikuosaito1989/items/3cb4ed96af0cd229c9ec
https://github.com/GeekyEggo/delete-artifact
https://github.com/actions/upload-artifact/issues/161
https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#sample-storage-cost-calculation

storageの更新は遅延があるみたい。
最大1日とかかかるらしい。

https://stackoverflow.com/questions/65576927/github-actions-youve-used-100-of-included-services-for-github-storage-githu

上限を$1に設定すれば解決するbugも昔は起きていたっぽい
⇨これでお金はかからないと記載があったが、どうなんだろう

tatatsurutatatsuru

artifactって?

Chat-GPTくん

GitHub Actions(ギットハブアクション)では、アーティファクトとして知られるデータを扱うことができます。アーティファクトは、ビルドやデプロイの結果として生成されたファイルやデータのことです。GitHub Actionsを使用してアーティファクトを生成すると、それらをアクションの間で共有したり、後で使用したりできます。アーティファクトは、ビルドされたコードやアプリケーションの成果物、テスト結果、ログファイルなど、さまざまなものを含むことができます。これらのアーティファクトは、GitHubのUIを介して閲覧したり、他のアクションで使用したり、必要に応じてダウンロードしたりできます。

tatatsurutatatsuru

OSの変更

https://github.com/microsoft/playwright/issues/15801

Playwrightの各バージョンは、特定のChromium、WebKit、Firefoxのバージョンにバインドされています。この基準を満たすために、CIプロバイダで設定することにより、異なるオペレーションシステム上でテストを実行することができますが、通常、Mac上のChromeとLinux上のChromeの間に大きな違いはありません。

私はgit actionではdockerを採用しているので、その中でmacosやwindows環境を作ればOSなどは変更できるが、こういったテストはOSの変更をするべきなのかが疑問として残った。

tatatsurutatatsuru

webkit上でtimeoutになり、pageなどが閉じてしまう現象

https://github.com/microsoft/playwright/issues/18552
https://playwright.dev/docs/actionability

playwright自体が高速でテストを行うものであり、タイミング次第では閉じてしまう可能性がある

timewaitforなどで待ちすぎた結果、pageが閉じてしまう現象が起きたので調べてみた。
環境やその他テスト内容にもよるが、色々事情があるらしい。

tatatsurutatatsuru

GitHubのstorage

git actionsを利用してたらいきなり下記のようなエラーが出た。

Create Artifact Container failed: Artifact storage quota has been hit. Unable to upload any new artifacts

どうやらGitHubのstorageがartifactでいっぱいになってしまったみたいだった。

GitHubのsetting ⇨ billing and plansを確認すると下記のようになっていた。

今回は上限を$0で済むようにしてたので、課金にはならなかった。(defaultでそうなってる)

実際に今回は手動でworkflowからartifactを削除していったが、全然storageが解放されない、、、。
なぜなんだろうと調べていたところ、実際に問題が起きている人がいたし、GitHubの公式Docsにも時間がかかるとの記載が!
https://stackoverflow.com/questions/65576927/github-actions-youve-used-100-of-included-services-for-github-storage-githu
https://github.com/actions/upload-artifact/issues/161
https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#sample-storage-cost-calculation

消してから2~3日後に解放された!!

tatatsurutatatsuru

page.goto: Page closed
=========================== logs ===========================
navigating to "hogehoge", waiting until "load"
============================================================
await page.goto("hogehoge") 

こうしないと、次の処理が非同期で走ってしまってエラーを起こしてしまう可能性がある。

https://github.com/microsoft/playwright/issues/12182

tatatsurutatatsuru

toBeUndefined

値がundefinedかどうかをみている。

const pagePath = [
  {
    name: "test",
    path: `${baseUrl}test/`,
  },
];

test.describe.parallel("Checking undefined", () => {
  pagePath.map((item) => {
    test(`existence of ${item.name}`, async ({ page, browser }) => {
      // pageへアクセス
      await page.goto(item.path);
      await page.waitForLoadState("load");

      // ここで値がundefindかどうかみてる
      expect('値を入れる').toBeUndefined();
    });
  });
});
tatatsurutatatsuru

flaky

多少はやはり出るもの。
https://kaminashi-developer.hatenablog.jp/entry/2023/12/12/080000

推奨するlocator

  • page.getByRole(): To locate elements by explicit and implicit accessibility attributes.
  • page.getByText(): To locate nodes by text content.
  • page.getByLabel(): To locate a form control by associated label’s text.
  • page.getByPlaceholder(): To locate an input by placeholder.
  • page.getByAltText(): To locate an element, usually an image, by its alt text alternative.
  • page.getByTitle(): To locate an element by its title attribute.
  • page.getByTestId(): To locate an element based on its data-testid attribute.

timeoutで待たないほうがいい

固定の時間で待つのはよろしくない。
下記のように待つのがベスト!(その要素20個が現れたら即次のテストへ。最大10秒待つ)

expect(await page.locator('.product')).toHaveCount(20, {
  timeout: 10000
});

解決方法

tatatsurutatatsuru

worker

2つの論理CPUコアに対して1つのワーカーを持つことを推奨します。そうしないと、過負荷の CPU ではブラウザの動作が遅くなる可能性があります。

今まで2workersでやってたが、下記のように直した。

playwright.config.ts
workers: process.env.CI ? 1 : 1,

速度は

https://github.com/microsoft/playwright/issues/19865

tatatsurutatatsuru

何をテスト自動化するか

https://fintan.jp/page/8457/

このように「機能修正を行う際ふるまいが変わらない修正に対するテスト」は実行対象外とするようにしました。

無闇にテストを増やすのも考えもの

tatatsurutatatsuru

networkidle

テストには使わない方がいいらしい。他のアサーションを使うべき。

'load' - wait for the load event to be fired.
'domcontentloaded' - wait for the DOMContentLoaded event to be fired.
'networkidle' - DISCOURAGED wait until there are no network connections for at least 500 ms. Don't use this method for testing, rely on web assertions to assess readiness instead.

https://playwright.dev/docs/api/class-page