フューチャー技術ブログ

Playwrightのテストランナーを他のテストライブラリと比較する

Playwright連載4日目です。

APIのスタイル

テスティングフレームワークには大きく2つの流派があります。ひとつはJUnit、あるいはその祖先の SUnitをベースにしたTDDスタイル(テスト駆動開発)のものです。もう1つが、RSpecをベースにしたBDD(振る舞い駆動開発)ものです。

TDDスタイルはテスト関数、もしくはメソッドを実行します。モジュールやテストクラスでグルーピングして、それぞれのメソッドで共通の準備コードを実行したり、というのが可能です。

// TDDスタイルの例(JavaのJUnit)
class PostTest {
@Test
void comment() {
}
}

BDDスタイルのテストはdescribeitというキーワードの組み合わせでテストを書いていきます。

describeがグループを表し、describe 名詞で、特定の要素のテストであるという宣言を行い、その中でit 振る舞いというのを並べていきます。このitは英語的にはdescribeの名詞を指します。特定の要素に対して、その振る舞いを記述するというスタイルです。

表現的には動く仕様書となることを指向していますが、実際のソフトウェアの動きとしてはTDDスタイルとほぼ同じです。

# BDDスタイルの例(RubyのRSpec)
RSpec.describe 'Post' do
it 'cannot have comments' do
end
end

JavaScript界隈のフレームワークはBDDベースのものが数多く利用されています。Playwrightはこの中間になっており、describeはあるが、itはなく、代わりにtestを使います。

JavaScript/TypeScriptのユニットテストのフレームワークとして高いシェアを誇ってきたのがMetaの作成したJestです。テストの書き方としてはJestを参考にしたのか、ほぼそれに近い形式となっています。Jestはまだ過去のBDDフレームワークとの互換性を考慮してかitでも書けるようになっていましたが、Playwrightはtestのみになっています。

test.describe("テスト対象", () => {
test.beforeAll(() => {
準備コード
})
test("振る舞い1", () => {
})

test("振る舞い2", () => {
})
})

グルーピングを行わないことも可能です。おそらくこのようにボイラープレートが少ない書き方をしたときにも違和感が出ないように(itだけ並ぶとテストに見えない)itではなくtestを選んだのではないかと思います。

test.beforeAll(() => {
})

test("振る舞い1", () => {
})

test("振る舞い2", () => {
})

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を表現することも可能です。

メタデータ

テストに何かしらの追加情報を含められます。typedescriptionの2つの属性をいくらでもつけられます。

test("", () => {
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/<some-issue>' });
})

ただし、通常の実行やUIを使った実行ではこのメタデータは表示されません。HTMLのレポート出力では表示されます。

タイムアウト時間を伸ばす

デフォルトでは1つのテストケース全体で30秒のタイムアウト時間になっていますが、test.slow()を呼ぶだけで3倍に伸びます。それでは足りない場合は個別にtest.setTimeout()で伸ばします。

test('slow test', async ({ page }) => {
test.slow();
// ...
});```

```ts
test('very slow test', async ({ page }) => {
test.setTimeout(120000);
// ...
});

まとめ

Playwrightのテストランナー機能は、既存のJest/Mochaなどに非常に近く、JavaScript系のテスティングフレームワークを使ったことがあればそれほど迷わないでしょう。一方で、後発ならではの工夫がされていたり、E2Eテストならではのslow()があったり、他のフレームワークをよく使っていたのにも面白い機能がいくつかあります。