そもそもSeleniumでのAPIテストが話題になる理由

理由は3つありますが、妥当と言えるものは1つだけです。

  • すでにテストスイートが存在します。 CIにSeleniumのジョブがあり、実行方法も周知されているため、そこにAPIのチェックを追加するのが最も抵抗の少ない道になります。無理もないことですが、これは技術的な理由ではなく組織的な理由です。

  • フローが両方にまたがります。 フォームを送信し、APIが作成したレコードが正しいかを確認する、といったケースです。これは正当な理由であり、設計して対応する価値のあるケースです。

  • セットアップとティアダウンです。 画面経由では遅いため、API経由でフィクスチャを作成するケースです。合理的ではありますが、これはテストそのものではなく下回りの準備作業にあたります。

それに伴うコスト

フィードバックが遅くなる本来はリクエスト1回で済むチェックを、ブラウザの起動時間が支配します。20分かかるテストスイートは実行される頻度が下がり、それこそが本当の損失です。
原因の切り分けが難しい失敗両者を組み合わせたテストが失敗したときは、画面とAPIのどちらが壊れたのかを自分で突き止める必要があります。スイートを分けておけば、その答えは何もしなくても得られます。
UIに引きずられたカバレッジ結果として、画面がたまたま呼び出すエンドポイントだけをカバーすることになり、それはAPI全体のごく一部にすぎません。エラーコードや境界値のパラメータは一度も実行されません。

うまくいく役割分担

Seleniumは、ブラウザでしか答えられないこと、つまりユーザーから見えるフローが機能しているかどうかの確認に使います。APIはAPI自身のレイヤーでカバーし、そこではチェック1件がページ読み込みではなくリクエスト1回になります。SeleniumのスイートからAPIを呼ぶのは、速度が目的でアサーションを伴わないセットアップとティアダウンに限定します。

本当に両者にまたがる唯一のケース、つまりフォームを送信してバックエンドが保存した内容を検証するケースは、成り行きではなく意図して書く価値があります。テストの本数はわずかですが、そのコストに見合うだけの価値があります。

独立したAPIレイヤーが扱うべきこと

どのツールを使う場合でも、APIテストスイートが最初の1年を生き延びられるかどうかは4つの要素で決まります。期限切れになるセッション、実行時にしか存在しない値、互いに依存し合う呼び出し、そして誰も片づけないレコードです。TestSpriteではこれらをAuto-Authentication、Dynamic Variables、Dependency Chains、Auto-Cleanupと呼んでいます。詳しい仕組みは APIテストのドキュメント で解説しています。

この4点を正しく押さえられるかどうかが、長く使われ続けるスイートと2四半期で削除されるスイートを分けます。そしてこれは、ブラウザのスイートに後付けするよりも、APIのレイヤーで実現するほうがはるかに容易です。

セットアップのケースを正しく行う

ブラウザテスト用のフィクスチャをAPIで作成することは、この2つが本当に組み合わさるべき唯一の場面であり、成り行きではなく意図的に行う価値があります。

これを健全に保つルールは、セットアップの呼び出しでは決してアサーションを行わない、というものです。その役割は、システムを既知の状態にできるだけ速く持っていくことにあります。セットアップが失敗した場合、テストはfailではなくerrorとして終了すべきです。フィクスチャの失敗はプロダクトの不具合ではなくインフラの問題だからです。この2つを混同すると、赤の半分がノイズであるようなスイートができあがります。

2つ目のルールは、セットアップが作成したものはティアダウンが削除する、というものです。その際は、名前で照合するのではなく、そのテストに紐づけて削除します。ブラウザのスイートは誰もが想像する以上の速さでデータを溜め込みます。毎週少しずつ遅くなっていく一覧画面は、たいてい書かれないままのティアダウンが原因です。

両方を実行する

どちらのスイートも、もう一方について知る必要はありません。Seleniumは今までどおりCIのジョブで実行し、APIのカバレッジはその横で実行します。

パイプラインが別のチームの管轄である場合は、 GitHub App が最も抵抗の少ない方法です。これはwebhookであり、リポジトリには何も変更を加えず、ビルドが新しいバージョンの公開を通知した時点で実行されます。代わりにチェックをリポジトリ上で見えるようにしたい場合は、 GitHub Actions のステップでそれが可能です。詳しくは次を参照してください: CLIリポジトリ。

TestSpriteがAPIレイヤーでカバーする範囲

TestSpriteは独立したAPIレイヤーを担うため、ブラウザのスイートに後付けする必要はありません。API Discoveryは稼働中のサービスが公開している内容を列挙し、Plan Generationは機能、スキーマ、認可、エラー処理、境界値の各カテゴリにわたるテストケースを提案します。それらは自然言語で調整できます。

先の4つの状態にまつわる課題は、自前のインフラではなく製品機能として提供されます。Auto-Authenticationはセッションを維持し、Dynamic Variablesは呼び出し間で値を引き継ぎ、Dependency Chainsは実行順序を導出し、Auto-Cleanupは実行によって作成されたものを削除します。これらこそがAPIテストスイートを維持し続けることを難しくしている要因であり、ブラウザのランナーの中で解決するよりも、APIのレイヤーで解決するほうがはるかに容易です。

Seleniumのスイートはそのままの場所に残し、ブラウザでしか答えられない領域をカバーし続けます。両者は同じパイプラインから実行されます。

SeleniumはHTTPリクエストを直接送信できますか?

言語バインディングを使えば可能です。結局はコードを書いているだけだからです。問題は、そもそもブラウザセッションを介在させるべきかどうかであり、純粋なAPIのチェックであれば、その答えはたいていノーです。

Seleniumのスイートは移行すべきでしょうか?

全面的に移行する必要はありません。人々に信頼されているSeleniumのスイートは資産です。何かを移すのではなく、APIのカバレッジをAPI自身のレイヤーで追加してください。

両方にまたがるフローはどのようにテストすればよいですか?

意図的に1つのテストとして書き、本数は少なく保ち、失敗したときにどちら側が壊れたのか分かる程度に具体的なアサーションを書いてください。

この用途にはPlaywrightのほうが適していますか?

Playwrightには標準機能としてリクエスト用のAPIがあるため、仕組みとしては扱いやすくなります。ただし設計上の論点は変わりません。ブラウザのチェックとAPIのチェックは異なる問いに答えるものであり、分けておくほうが適切です。

両方にまたがる認証はどうすればよいですか?

共有するのはセッションではなく認証情報の取得元です。各レイヤーがそれぞれ自分のセッションを取得するようにすれば、一方の失敗がもう一方に波及しません。

要点

ブラウザに関する問いにはブラウザを使いましょう。

SeleniumでのAPIテストとは、たいていの場合、1つのツールに両方をやらせることではなく、2種類のチェックをうまく連携させることを意味します。Seleniumはユーザーから見えるフローのために残し、APIはAPI自身のレイヤーでカバーし、本当に両者にまたがるテストだけを意図的に書いてください。