
こんにちは!みなさん、テストしてますか?
第1回では、まずはとにかくやってみようということで、VSCode拡張を使いながら簡単なE2Eテスト自動化にトライしてみました。初歩の初歩とはいえ、自動化がどんなものなのか、ざっくりイメージはついたのではないでしょうか。
記事一覧:モダンなE2Eテストの考え方をマスターしよう
第2回では、前・中・後編に分けて、E2Eテストを取り巻くパラダイム(考え方)や、使われるツールがどのように変わってきたのかについて書いていきます。この前編では、E2Eテストの重要な要素技術である「要素探索」の変遷について深堀っていきたいと思います。
なお、筆者自身は2017年ごろから自動テストに関わってきた身ですので、実は歴史を語るにはちょっと経験不足かもしれません。コメントがありましたらSNSなどでご連絡頂けますと助かります!
そもそもE2Eテストとは
E2Eテストとは「完全に統合済みのシステムを、ユーザーインターフェース(特にGUI)から操作する」テストのことを指します。
完全に統合済みというのは、システムを構成する様々なコンポーネントが全てビルドされた状態のことを指します。この特徴から、システムテストと呼ばれることもあります。
ユーザーインターフェースからというのは、ユーザーが実際にシステムを利用する際のインターフェースを用いるということを指します。この観点から、受け入れテストと呼ばれることもあります。
E2Eテストの目的
E2Eテストの主な目的は、実際に提供されるシステム上で(あるいは、非常に近い構成の上で)ユーザーがビジネスケースを達成できるかどうかを検証することです。
例えば、ECサイトであれば、以下のようなビジネスケースが考えられるでしょう。
- ログイン〜購入まで
- 商品を返品できる
- 発送前の商品はキャンセルできる
これらのビジネスケースを、実際のシステムに近い構成でテストするのがE2Eテストです。これにより、単体テスト・結合テストのような小さい単位のテストでは見つけづらいコンポーネント間の齟齬や、各機能レベルでは問題なく動作するがビジネスケースは達成できない、といった問題を解決できます。
例えば、上記のECサイトの例なら、ログイン、カートに追加、購入といった機能レベルでは問題なく実装されているものの、住所の新規登録が出来ないので購入に進めない、といった問題を見つけることができます(さすがに、素朴すぎるケースではありますが、一例ということで)。
また、最近はWeb・モバイル問わず、クライアントとサーバーが相互に通信し合う構成のアプリが多いです。Webフロントエンドとバックエンドをそれぞれ個別に作って、実際の構成では様々なエラーが出ることもあるでしょう。こうした問題を見つけることもE2Eテストには期待されています。
要素探索技術の変遷
さて、E2Eテストとはどういうものか?についておさらいできたところで、ここからはE2Eテストの重要な技術である 要素探索 技術の変遷について見ていきましょう。
第1回のテストコードの中でも、要素探索は繰り返し出てきます。例えば、サンプルToDoアプリのテキストボックスをクリックするためのコードは、こんな感じで書かれていました。

await page.getByRole('textbox', { name: 'What needs to be done?' }).click();このうち、 getByRole('textbox', { name: 'What needs to be done?' }) という部分が要素探索に当たります。
実は、この getByRole を用いた探索方法はここ数年で出てきた比較的新しい手法です。そのため、ちょっと古めの本などを読むと全然違うことが書いてあったりします。 getByRole はなぜ利用を推奨されているのか、他の方法とどう違うのかを理解するために、どんな変遷を辿ってきたのかについてこの記事でおさらいしておきましょう。
内部構造を用いた要素探索
例えば、テスト対象がWebアプリケーションのログインフォームだとします。
<form method="post" action="/login">
<label for="email"> メールアドレス </label>
<input id="email" />
<label for="password"> パスワード </label>
<input id="password" />
<span id="button" class="login-btn">送信</span>
</form>メールアドレス入力欄、パスワード入力欄、ログインボタンがあります。
これらのUI要素に、文字入力やクリックなどのアクションを行うためには、まずこれらの要素をHTML内(正確にはDOMツリー内)から探さなければいけません。ロケーターとは、この探索を行うために使う一意のキーのことを指します。
先ほどのHTMLに対してテストコードを書く場合、CSSセレクター や XPath などのものを使ってそれぞれの要素を取得することができます。例えば、 CSSセレクター を使ってメールアドレスを取得するためのコードは以下のようなものになります。 #email という書き方で、 id が email のもの、という意味になります。
await page.locator("#email")ところで、この id とは何のために使われるものでしょう?ウェブ開発者向けの技術ドキュメントサイトであるMDNによると、idは以下のような目的で使われるものと書いてあります。
ID 属性の目的は、リンク(フラグメント識別子を使用)、スクリプト、またはスタイル設定(CSS を使用)の際に、単一の要素を識別することです。
https://developer.mozilla.org/ja/docs/Web/HTML/Reference/Global_attributes/id#%E8%A7%A3%E8%AA%AC
つまり、 id 属性はページ内で使われるJavaScriptやCSSのために使われるもので、これらの都合で変更される可能性があるわけです。
より具体的に言うと、上記のログインフォームは id の値を元にバックエンドサーバーにデータを送ります。そのため、バックエンドサーバーの仕様が変わった場合、フォームも変わる可能性があります。一例として、バックエンドサーバーが受け取る変数名が email から mailaddress に変わった場合、ログインフォームは以下のように変わります。
<form method="post" action="/login">
- <label for="email"> メールアドレス </label>
- <input id="email" />
+ <label for="mailaddress"> メールアドレス </label>
+ <input id="mailaddress" />
<label for="password"> パスワード </label>
<input id="password" />
<span id="button" class="login-btn">送信</span>
</form>もちろん、これに対応して、テストコードも変えなければいけません。
- const inputEmail = getElementById("email")
+ const inputEmail = getElementById("mailaddress")ですが、E2Eテストの本来の目的と照らし合わせると、このように内部構造の変化の影響をテストコードが受けるのはおかしいはずです。E2Eテストの目的は ユーザーがビジネスケースを達成できるかどうか のはずなのに、目的が変わらないのにテストコードを直さないといけなくなってしまいます。これでは本末転倒です。
テスト用のIDを用いた要素探索
そこで、内部構造の代わりに、テスト用に一意のIDを振ろうという考え方が現れました。例えば、data-testid という属性を定義し、それをテスト用に使うことができます。
<form method="post" action="/login">
<label for="email"> メールアドレス </label>
<input id="email" data-testid="email" />
<label for="password"> パスワード </label>
<input id="password" data-testid="password" />
<span id="button" class="login-btn" data-testid="submit">送信</span>
</form>この場合、テストコードは次のように書けます。
await page.locator('[data=testid="email"]')
// 属性名が data-testid の場合は、
// Playwrightのショートハンドで次のようにも書ける
await page.getByTestId('email')data- から始まる属性は データ属性 と呼ばれており、どんな属性も自由に定義でき、原則としてアプリケーションの振る舞いに影響を与えません。そのため、テスト用のIDを振るという用途にはぴったりです。
便利な半面、結局データ属性を個別に定義しないといけなくなってしまい、手間が増えることに変わりはありません。また、データ属性はユニークである保証が無いため、ページ内に同じデータ属性が複数存在する場合に、テストが不安定になる場合があります。例えば、一つのページに新規登録フォームとログインフォームがある場合、 data-test="email" が2つあると、ログインフォームのテストをしているつもりが実は新規登録フォームに入力していた、なんてことも考えられます。
セマンティクスを用いた要素探索
ところで、id や class のような内部属性にしろ、 data-test のようなテスト用の属性にしろ、突き詰めると ユーザーにとっては何の関係もない値 です。ユーザーは「メールアドレス」と書いてあるフォームに文字を入力するのであって、 id や data-test に email と書いてあるフォームに文字を入力するわけではないからです。そのため、E2Eテストの大原則である「ユーザーがビジネスケースを達成できることの検証」という目的と照らし合わせると、とても不自然なことをしています。
この点をカバーするため、2025年現在では要素の セマンティクス(意味) を用いた要素探索手法が広く用いられています。アクセシビリティ支援技術の中には、例えばスクリーンリーダーのような「ページに表示されている情報を様々な方法でユーザーに伝える」ための技術があります。GUIのような視覚的に理解できるインターフェースを、ページの構造やUIコンポーネントの役割、表示されているテキストなどを用いて様々な形でユーザーに伝えます。
こうした支援技術が用いるデータは、ユーザーの目線からページを見た際の 意味 を機械的に表現したものが多いです。Webページでセマンティクスを表現する方法はいろいろありますが、一例として ボタン を表す方法をいくつか挙げましょう。
<input type="button" name="OK" />
<button>OK</button>
<span role="button" aria-label="OK">OK</button>これらはすべてスクリーンリーダー上では OK というラベルを持った ボタン として振る舞います(※厳密には、3つ目の例はTabキーによるフォーカスが当たらない、Enterキーによる押下ができないなど、いくつか制約があります)。
仮に、これらの要素を 内部属性 を用いて探索する場合、それぞれ別の書き方をする必要があります。
await page.locator('input[type="button"][name="OK"]');
await page.locator('button:has-text("OK")');
await page.locator('span[role="button"][aria-label="OK"]');ですが、 セマンティクス を用いる場合、一つの書き方で三つすべての要素にマッチします。
await page.getByRole('button', { name: 'OK' });セマンティクスを手がかりに要素を探索することで、よりE2Eテストらしい、ユーザー目線のテストを書けるようになります。また、UIのセマンティクスを整備することによって、スクリーンリーダーなどのアクセシビリティ支援技術がUIを理解しやすくなる効果もあります。
まとめ
前編ではE2Eテストそのものの説明と、GUIを扱うという性質上重要になってくる要素探索についての解説を行いました。
次回、中編では、自動テストそのものの実装技術について触れたいと思います。

