Playwright連載4日目です。
APIのスタイル
テスティングフレームワークには大きく2つの流派があります。ひとつはJUnit、あるいはその祖先の SUnitをベースにしたTDDスタイル(テスト駆動開発)のものです。もう1つが、RSpecをベースにしたBDD(振る舞い駆動開発)ものです。
TDDスタイルはテスト関数、もしくはメソッドを実行します。モジュールやテストクラスでグルーピングして、それぞれのメソッドで共通の準備コードを実行したり、というのが可能です。
// TDDスタイルの例(JavaのJUnit) |
BDDスタイルのテストはdescribe
とit
というキーワードの組み合わせでテストを書いていきます。
describe
がグループを表し、describe 名詞
で、特定の要素のテストであるという宣言を行い、その中でit 振る舞い
というのを並べていきます。このit
は英語的にはdescribe
の名詞を指します。特定の要素に対して、その振る舞いを記述するというスタイルです。
表現的には動く仕様書となることを指向していますが、実際のソフトウェアの動きとしてはTDDスタイルとほぼ同じです。
# BDDスタイルの例(RubyのRSpec) |
JavaScript界隈のフレームワークはBDDベースのものが数多く利用されています。Playwrightはこの中間になっており、describe
はあるが、it
はなく、代わりにtest
を使います。
JavaScript/TypeScriptのユニットテストのフレームワークとして高いシェアを誇ってきたのがMetaの作成したJestです。テストの書き方としてはJestを参考にしたのか、ほぼそれに近い形式となっています。Jestはまだ過去のBDDフレームワークとの互換性を考慮してかit
でも書けるようになっていましたが、Playwrightはtest
のみになっています。
test.describe("テスト対象", () => { |
グルーピングを行わないことも可能です。おそらくこのようにボイラープレートが少ない書き方をしたときにも違和感が出ないように(itだけ並ぶとテストに見えない)it
ではなくtest
を選んだのではないかと思います。
test.beforeAll(() => { |
Playwrightが参考にしたと思われるJest、あとはCypressが内部で利用しており、用途が近いMochaと比べると以下の通りです。それぞれ微妙に違っています。
Playwright | Jest | Mocha | |
---|---|---|---|
import | 必要 | 不要 | 不要 |
テスト | test("名前", テスト) |
test("名前", テスト) (it も使える) |
it("名前", テスト) |
グルーピング | test.describe("名前", グループ) |
describe("名前", グループ) |
describe("名前", グループ) |
準備(各テストごと) | test.beforeEach(準備) |
beforeEach(準備) |
beforeEach(準備) |
片付け(各テストごと) | test.afterEach(準備) |
beforeEach(準備) |
beforeEach(準備) |
準備(グループごと) | test.beforeAll(準備) |
beforeAll(準備) |
before(準備) |
片付け(グループごと) | test.afterAll(準備) |
afterAll(準備) |
after(準備) |
Jestはユニットテスト用途でPlaywrightやCypressと同時に使うことが多いので、記述が近いのは便利です。しかし完全に一致しているわけではなく必ずtest.
の前置が必要だったりします。また、並列のテストの書き方が、Jest同等の書き方(test.describe.parallel
)は非推奨になっていたりします。ただし、今時はコード補完ができて当たり前なので、ほぼ補完で済むのでタイプ数は逆に少なくて済みます。
なお、import不要というのはテストコードを書くときは楽ではありますが、エディタ側のコードでそのキーワードを識別できるように設定が必要だったりするため、環境構築の手間暇が増えたりします。特に、JestとMochaはそれぞれで似たようなものが導入されるため、同時に利用するために設定を工夫する必要がありますが、そのような苦労はありません。
テストの便利機能
テストにアノテーションをつけて実行を制御することがあります。
例えば、test()
やtest.describe()
の関数名の直後に.only
を挟み、test.only()
やtest.describe.only()
と付けられます。.only
以外にも、.skip
、.fixme
もあります。
.only
をつけると、それがついたテストのみを実行します。テストUIで起動しているときは、再実行したいテストの▶️ボタンを押せば同じことはできますが、まさにいま機能改修をしていて、該当の機能のテストだけを高速に回して動作確認したい場合などに便利です。
.skip
は逆に特定のテストの実行をスキップする場合に使います。.fixme
も同様ですが、名前の通り不具合で一時的に実行を中止している意図を表現するのに使います。テストUIやCLIの実行結果ではこの2つはどちらもスキップ扱いで機能的な差はありません。
これらはCypressやJestにも類似の機能があるため、Playwrightを使っていない人にも便利でしょう。
Playwrightの固有の機能
ステップ分け
ユニットテストはなるべくテストケースを小さくしよう、というのが良いテスト設計であるとされています。1つのテストケースごとに1事象のみをテストし、1つの失敗で他のテストの失敗につながらないように、というのがよく言われます。
一方で、E2Eテストはユーザーから見た1つの機能などの単位で作っていくことになります。この場合、確定ボタンを押すまでに複数のテキストボックスに値を入れたり、ウィザードのページをいくつも操作したり、一連の長い操作になることもあります。その場合に、テストの中にtest.step()
を複数個書くことで、テストケースの中を構造化できます。テストランナーのUIではPlaywrightのAPI呼び出しが全てリストアップされているので、この機能を使わなかったとしてもトラブル発生時にどこで落ちたのかは分かるには分かるのですが、長くなった場合に見分けがつきやすくなります。
await test.step("名前", async () => { |
テストUI上ではこのような感じでステップが表現されます。
2段以上ネストさせることもできます。
タグ付け
テストの名前に @タグ
というテキストを入れることでタグ付けしてテストのフィルタリングが可能になります。例えば、本番環境で軽い確認用に幾つかのテストをピックアップして使いたい場合はテストの名前の文字列の中に @validation
という文字列を含めておくことで、そのテストのみを選択して実行ができます。
npx playwright test --grep @validation |
--grep-invert
で含んでいないものを実行したり、|
でORを表現したり正規表現でANDを表現することも可能です。
メタデータ
テストに何かしらの追加情報を含められます。type
とdescription
の2つの属性をいくらでもつけられます。
test("", () => { |
ただし、通常の実行やUIを使った実行ではこのメタデータは表示されません。HTMLのレポート出力では表示されます。
タイムアウト時間を伸ばす
デフォルトでは1つのテストケース全体で30秒のタイムアウト時間になっていますが、test.slow()
を呼ぶだけで3倍に伸びます。それでは足りない場合は個別にtest.setTimeout()
で伸ばします。
test('slow test', async ({ page }) => { |
まとめ
Playwrightのテストランナー機能は、既存のJest/Mochaなどに非常に近く、JavaScript系のテスティングフレームワークを使ったことがあればそれほど迷わないでしょう。一方で、後発ならではの工夫がされていたり、E2Eテストならではのslow()
があったり、他のフレームワークをよく使っていたのにも面白い機能がいくつかあります。