
去年くらいから仕事でPlaywrightを触る機会があり、一段落したので所感をまとめてみました。
Agentic E2Eが普及の兆しを見せ、これらも陳腐化していくのかもしれません。。
Page Object内ではexpectを使わないようにした
Page Object内でexpectを使うと、Auto-waitingの恩恵が受けられるものの、事実上テストケースをPage Object内に書けてしまい、責務があいまいになりそうに感じたため、Page Object内では使わないようにしました。
一応はこれで責務は分けられたものの、「ここでexpectが使えたらシンプルなコードになるのになー」というケースは若干あり、正直少し微妙だったかもしれません。
LocatorをTypeScriptのConstructor Shorthandで定義するようにし、Pageオブジェクトをprivateにして隠蔽し、locatorで直接指定せずgetBy系で取得するようにした
Page Objectが操作するPageオブジェクトとLocatorは、公式などでは
class LoginPage {
readonly page: Page;
readonly getStartedLink: Locator;
constructor(page: Page) {
this.page = page;
this.getStartedLink = page.locator('a', { hasText: 'Get started' });
}
}
のようにメンバー変数宣言とコンストラクターでの設定で書きますが、publicのためテストケース側から直接pageオブジェクトが操作できると、Page Objectクラスの意義が薄れるように感じたのと、似たようなコードを2度書くのが少し冗長に感じたので、TypeScriptのConstructor Shorthandで定義する形を取りました。
また、locatorによる直接指定は極力避け、基本的にgetBy系で取得する形にしました。
class LoginPage {
constructor(
private readonly page: Page = this.page,
public readonly getStartedLink: Locator = page.getByText('Get started'),
) {}
}
こうすることで、テストケース側からpageオブジェクトを操作したい場合はPage Objectクラスに操作メソッドを宣言する事を強制でき、メンバー変数宣言とコンストラクターの設定を集約できてコード量を少し減らしつつ、CSS/XPathの乱用を防ぐ事ができました。
最初のうちはこれでもよかったのですが、Locatorの数が増えてくると数十の引数を持つコンストラクターができてしまい、人によっては違和感があるようでした。
(Kotlinのdata classなどで結構見慣れていたというのもありますが。。)
また、Locatorの設定は式に限られるため、「別のlocatorから派生して取得」といった事ができず、常にルートから取得する記述する必要があるのに加え、getBy系で取得できない要素についてはヘルパーメソッドに切り出すなどの回避策をせざるを得ませんでした。
Page Objectがいまいち使いこなせなかった
Playwrightでは、いわゆるベストプラクティスとしてPage Objectの使用が推奨されていますが、コードの再利用が期待したほどできず、似たようなコードが散在してしまう事が多かったです。
Locatorを1行で書かないとplaywright-vscodeでハイライトできない
上記の書き方に関連するのですが、PlaywrightのVSCode拡張ではPage Objectで宣言されたLocatorにカーソルを合わせると、デバッグブラウザー上でハイライトする機能があり重宝するのですが、長いlocatorを折り返してしまうとなぜかそれが効かなくなる事象がありました。
また、ヘルパーメソッドなどを経由してもハイライト対象外となってしまうため、書き心地が著しく下がる画面などがあったりしました。
playwright.config.tsのprojectsでなくglobalSetupを使った
Playwrightで実行直前にfixtureなどを登録する場合、projectsを使うケースとglobalSetupを使うケースの2つがありますが、前者を宣言すると上述のPlaywrightのVSCode拡張で「どのプロジェクトを有効にするか」のプルダウンが表示されてしまうため、後者を使用して行いました。
ただ、後者の場合、Playwrightのテストケースが開始される前?の実行になるため、デバッグが難しく原因が分かりにくい罠にはまりました。その場合は DEBUG=pw:api npx playwright test のようなトレースログ出力で原因究明したりして回避しました。
playwright merge-reportsがよかった
Playwrightでマスターデータを登録→マスターデータを使ってバッチを実行→バッチ実行結果を確認、というフローでE2Eテストを行いたい場合、Playwright内でバッチを実行してしまうと全体のタイムアウト制限に引っかかってしまいます。
とはいえ、マスターデータを登録するテストケースと、バッチ実行結果を確認するテストケースを分けてしまうと、後者のPlaywrightレポートしか出力されません。
これを解決するのがmerge-reportsで、その名の通りPlaywrightレポートをマージして1つの実行結果にしてくれます。そのため、バッチなどの長時間の実行をE2Eテスト内に挟むことができるようになりました。
expect.pollやexpect.notの罠にはまった
非同期処理の完了待ちなど、expectのAuto-waitingだけでは判定できない場合にexpect.pollを使うと、判定条件を柔軟に書けて便利なのですが、便利すぎてFlakyなテストケースがあると「expect.pollで書けばいいのでは?」という誘惑?に駆られる事がありました。
これは待機時間が長くなるため避けるべきアンチパターンだと後から気づき、Auto-waitingの形に書き直すようにしました。
また、expect.notも多用すると期待した結果以外のテストがOKになってしまう事があり、これも期待する厳密な結果で判定するようにしました。
Promise.raceで分岐、Promise.allで待機
これはPlaywrightとはあまり関係ないのですが、複数のlocatorの結果を見て期待する結果になっているか判定する場合、expectをPromise.raceやPromise.allで束ねることで行うことがありました。
慣れれば見やすいと感じるものの、Playwright公式では一度Promiseオブジェクトを変数に受けて最後に待機判定する形の方が推奨されているようです。