カテゴリ目次
- はじめに
- 早見表:この場面ではこのマッチャー
- 自動リトライ系: ロケータのアサーション(LocatorAssertions)
- toBeAttached
- toBeVisible
- toBeHidden
- toBeEnabled
- toBeDisabled
- toBeEditable
- toBeChecked
- toBeFocused
- toBeEmpty
- toBeInViewport
- toContainText
- toHaveText
- toHaveValue
- toHaveValues
- toHaveCount
- toHaveAttribute
- toHaveClass
- toContainClass
- toHaveId
- toHaveCSS
- toHaveJSProperty
- toHaveRole
- toHaveAccessibleName
- toHaveAccessibleDescription
- toHaveScreenshot
- toMatchAriaSnapshot
- 自動リトライ系: ページのアサーション(PageAssertions)
- APIレスポンスのアサーション(APIResponseAssertions)
- 汎用の値アサーション(generic matchers)
- 修飾子・便利機能
- 初心者向けまとめ: 迷ったらこれ
- 関連記事
はじめに
Playwrightでテストを書いていると、expect(...)という書き方に必ず出会います。「今、この状態になっているはずだ」をコードで検証する仕組みで、アサーション(assertion)と呼ばれるものです。日本語では「表明」「検証」などと訳されますが、私は普段からそのまま「アサーション」と呼んでいます。
たとえば次のようなコードです。
await expect(page.getByRole("button", { name: "送信" })).toBeVisible();
「送信という名前のボタンが画面に表示されているはずだ」を検証しているコードです。実際には表示されていなければテストは失敗し、「表示されていませんでした」というエラーメッセージが出ます。
この記事は、Playwrightのexpectで使えるマッチャー(.toBeVisible()のような、検証の種類を表す部分)を一望できる保存版リファレンスです。「この状態を確認したいけど、どのマッチャーを使えばいいんだっけ」となったときに、開いてもらえば見つかるようにまとめました。実在しないマッチャーを載せると余計に混乱を招くだけなので、この記事では実際にPlaywrightに存在するマッチャーだけを扱っています。
expectには2つの系統がある
初心者の方にまず押さえておいてほしいのは、Playwrightのexpectには性質の違う2つの系統がある、という点です。
- 自動リトライする非同期アサーション:
await expect(locator).toBeVisible()のように、Playwrightが用意したロケータやページ、APIレスポンスを検証するものです。条件を満たすまで(デフォルトでは最大5秒間)自動的に何度もチェックを繰り返してくれます。要素がまだ表示されていなくても表示されるまで勝手に待ってくれるので、sleepのような待機処理を自分で書く必要はありません。ここは本当にありがたい仕組みです。 - 汎用の同期アサーション:
expect(値).toBe(...)のように、数値や文字列、配列、オブジェクトといった普通のJavaScript/TypeScriptの値を検証するものです。値はその場ですでに確定しているものとして扱われるため、待つという概念がなく、その場で1回だけ判定されます。
この2つを混同すると、「なぜか思った通りに待ってくれない」「なぜかawaitが要求される・されない」で混乱します。次の1点だけ覚えておいてください。
ロケータ・ページ・APIレスポンスを検証するときはawaitが必須。数値や文字列など普通の値を検証するときはawaitは不要。
// (1) 自動リトライ系: ロケータの検証。awaitが必須
await expect(page.getByText("保存しました")).toBeVisible();
// (2) 汎用系: 数値の検証。awaitは不要
expect(1 + 1).toBe(2);
awaitを付け忘れると、実は何も検証していないのにテストが通ってしまう(意図せずスキップされてしまう)ことがあります。私も昔これで痛い目にあったので、expect(locator)...の形のときはawaitを付け忘れていないか、必ず確認する癖をつけてください。
早見表:この場面ではこのマッチャー
「〜を確認したい」から、使うべきマッチャーを逆引きできる表です。マッチャー名をクリックすると、詳しい説明と使用例のある見出しへジャンプします。迷ったらまずここに戻ってきてください。
| 確認したいこと | 使うマッチャー |
|---|---|
| 要素が画面に表示されているか | toBeVisible |
| 要素が非表示になっているか | toBeHidden |
| 要素がDOM上に存在するか(非表示でもよい) | toBeAttached |
| ボタンなどがクリックできる状態か | toBeEnabled |
| ボタンなどが無効化されているか | toBeDisabled |
| 入力欄が編集可能か | toBeEditable |
| チェックボックス・ラジオボタンがオンか | toBeChecked |
| 要素にフォーカスが当たっているか | toBeFocused |
| 入力欄や要素が空か | toBeEmpty |
| 要素がビューポート(画面の見える範囲)内にあるか | toBeInViewport |
| 一致する要素の数 | toHaveCount |
| テキストを部分的に含んでいるか | toContainText |
| テキストが完全に一致するか | toHaveText |
| 入力欄・テキストエリアの値 | toHaveValue |
| 複数選択セレクトの選択値一覧 | toHaveValues |
属性(hrefやdata-*など)の値 |
toHaveAttribute |
| class属性の値 | toHaveClass |
| 特定のclassが含まれているか | toContainClass |
| id属性の値 | toHaveId |
| CSSの計算スタイル値 | toHaveCSS |
| 要素のJavaScriptプロパティの値 | toHaveJSProperty |
| ARIAロール(役割) | toHaveRole |
| アクセシブルネーム(読み上げられる名前) | toHaveAccessibleName |
| アクセシブルな説明文 | toHaveAccessibleDescription |
| 見た目のスクリーンショット比較 | toHaveScreenshot |
| ページのタイトル | toHaveTitle |
| ページのURL | toHaveURL |
| APIレスポンスが成功(2xx)か | toBeOK |
| JavaScriptの値同士が等しいか | toBe / toEqual |
| 配列に特定の値が含まれるか | toContain |
| オブジェクトが特定のプロパティを持つか | toHaveProperty |
| 関数がエラーを投げるか | toThrow |
自動リトライ系: ロケータのアサーション(LocatorAssertions)
await expect(locator).xxx()の形で使います。ロケータが条件を満たすまで自動的に待ってくれるのが最大の特徴です。代表的なマッチャーを1つずつ見ていきましょう。
toBeAttached
要素がDOM(ページの構造)上に存在することを検証します。画面に見えているかどうか(表示・非表示)は問いません。
await expect(page.locator(".item")).toBeAttached();
よくある使い所: 一覧の項目がまだ非表示のアニメーション中でも、「そもそもDOMには追加されているか」を先に確かめたいとき。
toBeVisible
要素が実際に画面上に見えている状態であることを検証します。もっともよく使うマッチャーの1つです。
await expect(page.getByRole("button", { name: "送信" })).toBeVisible();
toBeHidden
要素が非表示(display: noneなど)であるか、またはDOM上に存在しないことを検証します。
await expect(page.getByText("読み込み中...")).toBeHidden();
よくある使い所: ローディング表示が消えたこと(=処理が完了したこと)を確認したいとき。
toBeEnabled
要素(ボタンや入力欄など)が有効化されていて、クリックや入力ができる状態であることを検証します。
await expect(page.getByRole("button", { name: "送信" })).toBeEnabled();
toBeDisabled
要素が無効化されていて、クリックや入力ができない状態であることを検証します。
await expect(page.getByRole("button", { name: "送信" })).toBeDisabled();
よくある使い所: 必須項目が未入力の間、送信ボタンが押せないようになっていることを確認したいとき。
toBeEditable
入力欄が編集可能(有効化されていて、readonlyでもない)状態であることを検証します。
await expect(page.getByLabel("メールアドレス")).toBeEditable();
toBeChecked
チェックボックスやラジオボタンが選択(チェック)された状態であることを検証します。
await expect(page.getByLabel("利用規約に同意する")).toBeChecked();
// チェックが外れていることを確認したい場合
await expect(page.getByLabel("利用規約に同意する")).not.toBeChecked();
toBeFocused
要素にフォーカス(キーボード入力の対象)が当たっていることを検証します。
await expect(page.getByLabel("検索")).toBeFocused();
よくある使い所: ページを開いた直後に検索欄へ自動的にフォーカスが移ることを確認したいとき。
toBeEmpty
入力欄・テキストエリアにテキストが入っていないこと、または要素に子要素が何もないことを検証します。
await expect(page.getByLabel("備考")).toBeEmpty();
toBeInViewport
要素が現在のビューポート(ブラウザの表示範囲)内に入っていることを検証します。
await expect(page.getByText("ページの一番下です")).toBeInViewport();
よくある使い所: 無限スクロールで、目的の要素までスクロールが完了したことを確認したいとき。
toContainText
要素のテキストが、指定した文字列を部分的に含んでいることを検証します。前後の空白や改行の違いは自動的に無視されます。
await expect(page.locator(".result-message")).toContainText("保存しました");
よくある使い所: 一部が動的に変わるメッセージ(「◯件保存しました」など)を、固定部分だけで検証したいとき。これ、意外と使えます。
toHaveText
要素のテキストが、指定した文字列と完全に一致することを検証します。複数の要素にマッチするロケータの場合は、配列で渡すことで各要素との一致を順番に検証できます。
await expect(page.locator("h1")).toHaveText("ようこそ");
// 複数要素の場合: 配列で順番に検証する
await expect(page.locator(".item .title")).toHaveText([
"練習用アイテムA",
"練習用アイテムB",
]);
toHaveValue
<input>や<textarea>の現在の値を検証します。
await expect(page.getByLabel("ユーザー名")).toHaveValue("taro");
よくある使い所: フォームに文字を入力した直後、実際にその値が反映されているかを確認したいとき。
toHaveValues
複数選択できる<select multiple>で、選択されている値の一覧を検証します。
await expect(page.getByLabel("好きな言語")).toHaveValues(["typescript", "python"]);
toHaveCount
ロケータに一致する要素の数を検証します。
await expect(page.locator(".item")).toHaveCount(3);
よくある使い所: 一覧に想定通りの件数の項目が表示されているかを確認したいとき。地味ですが、地味に助かる場面が多いマッチャーです。
toHaveAttribute
要素が指定した属性を、指定した値で持っていることを検証します。
await expect(page.getByRole("link", { name: "詳細" })).toHaveAttribute(
"href",
"/items/1"
);
よくある使い所: リンクのhrefや、data-*のようなカスタム属性の値を確認したいとき。
toHaveClass
要素のclass属性の値を検証します。文字列・正規表現・配列で指定できます。
await expect(page.getByRole("button", { name: "送信" })).toHaveClass(/primary/);
toContainClass
要素のclass属性に、指定したクラス名が(他のクラス名と混ざっていても)含まれていることを検証します。toHaveClassと違い、class属性全体の完全一致を求めません。
await expect(page.getByRole("button", { name: "送信" })).toContainClass("is-active");
よくある使い所: class="btn btn-primary is-active"のように複数のクラスが並んでいる中から、特定の1つだけを確認したいとき。toHaveClassで完全一致にこだわって消耗するくらいなら、素直にこちらを使ったほうが早いです。
toHaveId
要素のid属性の値を検証します。
await expect(page.locator("form")).toHaveId("signup-form");
toHaveCSS
要素に実際に適用されている(ブラウザが計算した)CSSプロパティの値を検証します。
await expect(page.getByRole("button", { name: "送信" })).toHaveCSS(
"background-color",
"rgb(0, 123, 255)"
);
よくある使い所: ボタンの色が状態(有効/無効など)によって正しく切り替わっていることを確認したいとき。
toHaveJSProperty
要素のDOMノードが持つJavaScriptのプロパティの値を検証します。属性(attribute)ではなく、実行時のプロパティ(property)を見る点がtoHaveAttributeとの違いです。
await expect(page.getByLabel("同意する")).toHaveJSProperty("checked", true);
toHaveRole
要素のARIAロール(button、link、checkboxなど、支援技術に伝わる「役割」)を検証します。
await expect(page.getByText("送信")).toHaveRole("button");
toHaveAccessibleName
要素のアクセシブルネーム(スクリーンリーダーなどが読み上げる、その要素の「名前」)を検証します。
await expect(page.getByRole("button")).toHaveAccessibleName("送信する");
よくある使い所: アイコンだけのボタンにaria-labelで付けた名前が正しいかを確認したいとき。
toHaveAccessibleDescription
要素のアクセシブルな説明文(aria-describedbyなどで関連付けられた説明)を検証します。
await expect(page.getByLabel("パスワード")).toHaveAccessibleDescription(
"8文字以上で入力してください"
);
toHaveScreenshot
要素の見た目を画像として撮影し、あらかじめ保存しておいた基準画像(スナップショット)と比較します。ピクセル単位の見た目の差分を検出したいときに使う、ビジュアルリグレッションテスト用のマッチャーです。
await expect(page.getByTestId("card")).toHaveScreenshot("card.png");
補足: 初回実行時は基準画像がまだ無いため、まず画像を新規作成するところから始まります(--update-snapshotsオプションなどを使います)。環境によって微妙な見た目の違いが出やすく、CIとローカルで結果が変わることがあるので注意してください(フォントのレンダリングまわりだと思うのですが、正直このズレの原因を毎回きっちり追えているわけではありません)。
toMatchAriaSnapshot
要素配下のアクセシビリティツリー(支援技術から見た構造)を、YAML形式のスナップショットと比較します。
await expect(page.locator("nav")).toMatchAriaSnapshot(`
- list:
- listitem: "ホーム"
- listitem: "商品一覧"
`);
よくある使い所: 見た目のCSSではなく、「支援技術から見てどう構造化されているか」ごとレイアウトの崩れを検知したいとき。比較的新しいマッチャーなので、細かい挙動はバージョンによって変わる可能性があります。
自動リトライ系: ページのアサーション(PageAssertions)
await expect(page).xxx()の形で使います。ページ全体に関する検証です。
toHaveTitle
ページの<title>の内容を検証します。
await expect(page).toHaveTitle("商品一覧 | サンプルストア");
// 部分一致にしたい場合は正規表現も使える
await expect(page).toHaveTitle(/商品一覧/);
toHaveURL
現在のページのURLを検証します。
await expect(page).toHaveURL("https://example.com/items");
// パスの一部だけ確認したい場合
await expect(page).toHaveURL(/\/items\/\d+/);
よくある使い所: フォーム送信後に、想定した完了ページへ遷移したことを確認したいとき。
toHaveScreenshot(ページ全体)
ページ全体(またはビューポート)の見た目を、基準画像と比較します。使い方はロケータ版のtoHaveScreenshotと同様です。
await expect(page).toHaveScreenshot("top-page.png", { fullPage: true });
APIレスポンスのアサーション(APIResponseAssertions)
await expect(apiResponse).xxx()の形で使います。page.requestや専用のrequestフィクスチャで取得したAPIレスポンスを検証するためのものです。
toBeOK
レスポンスのステータスコードが200番台(成功)であることを検証します。
const response = await page.request.get("https://example.com/api/items");
await expect(response).toBeOK();
よくある使い所: UI操作の裏側で呼ばれているAPIが、エラーを返さず正常に完了しているかを確認したいとき。
汎用の値アサーション(generic matchers)
ここからは、ロケータやページではなく、普通のJavaScript/TypeScriptの値(数値・文字列・配列・オブジェクトなど)を検証するマッチャーです。値はその場で確定しているものとして扱われるため、awaitは不要で、その場で1回だけ判定されます。
toBe
値が、指定した値と厳密に同じ(Object.isによる比較)であることを検証します。プリミティブな値(数値・文字列・真偽値など)の比較に向いています。
const total = 1200 + 980;
expect(total).toBe(2180);
補足: オブジェクトや配列をtoBeで比較すると、中身が同じでも「同一のオブジェクトかどうか」(参照が同じか)で判定されてしまい、多くの場合失敗します。オブジェクト・配列の中身を比較したいときは、次のtoEqualを使ってください。
toEqual
オブジェクトや配列の中身が、再帰的に見て同じであることを検証します(深い比較)。
const item = { name: "練習用アイテムA", price: 1200 };
expect(item).toEqual({ name: "練習用アイテムA", price: 1200 });
toStrictEqual
toEqualよりもさらに厳密な比較を行います。undefinedのプロパティの有無や、配列の疎らな要素(空きスロット)、オブジェクトの型(クラス)の違いまで区別します。
expect({ a: 1, b: undefined }).toEqual({ a: 1 }); // 成功する
expect({ a: 1, b: undefined }).toStrictEqual({ a: 1 }); // 失敗する
toBeCloseTo
小数点を含む数値同士を、誤差を許容して比較します。浮動小数点の計算誤差(0.1 + 0.2が0.3ぴったりにならない、といった問題)を避けたいときに使います。
expect(0.1 + 0.2).toBeCloseTo(0.3, 5); // 小数第5位まで一致していればOK
toBeDefined
値がundefinedではないことを検証します。
const found = items.find((i) => i.name === "練習用アイテムA");
expect(found).toBeDefined();
toBeUndefined
値がundefinedであることを検証します。
const found = items.find((i) => i.name === "存在しない商品");
expect(found).toBeUndefined();
toBeNull
値がnullであることを検証します。
expect(await heading.getAttribute("data-missing")).toBeNull();
toBeNaN
値がNaN(Not a Number、数値として不正な値)であることを検証します。
expect(Number("abc")).toBeNaN();
toBeTruthy
値が真(truthy、if文の条件として書いたときに真になる値)であることを検証します。
expect(items.length).toBeTruthy();
toBeFalsy
値が偽(falsy、0・""・null・undefined・false・NaNなど)であることを検証します。
expect(errorMessage).toBeFalsy();
toBeGreaterThan
数値が、指定した値より大きいことを検証します。
expect(items.length).toBeGreaterThan(0);
toBeGreaterThanOrEqual
数値が、指定した値以上であることを検証します。
expect(totalPrice).toBeGreaterThanOrEqual(1000);
toBeLessThan
数値が、指定した値より小さいことを検証します。
expect(errorCount).toBeLessThan(5);
toBeLessThanOrEqual
数値が、指定した値以下であることを検証します。
expect(page.viewportSize()?.width ?? 0).toBeLessThanOrEqual(1920);
toContain
文字列が特定の部分文字列を含むこと、または配列が特定の要素を含むことを検証します。
expect("練習用アイテムA").toContain("アイテム");
expect(["a", "b", "c"]).toContain("b");
toContainEqual
配列の中に、指定したオブジェクトと深い比較で一致する要素が含まれていることを検証します。toContainが同一性(参照)を見るのに対し、こちらは中身を見ます。
const items = [{ name: "A", price: 1200 }, { name: "B", price: 980 }];
expect(items).toContainEqual({ name: "B", price: 980 });
toHaveLength
文字列や配列の長さ(lengthプロパティ)を検証します。
expect(items).toHaveLength(3);
toHaveProperty
オブジェクトが指定したプロパティ(ネストしたパスも"a.b.c"のように指定可能)を持っていることを検証します。第2引数を渡すと、その値まで検証できます。
expect(item).toHaveProperty("price", 1200);
expect(response).toHaveProperty("data.user.name", "たろう");
toMatch
文字列が指定した正規表現、または部分文字列にマッチすることを検証します。
expect(await page.title()).toMatch(/商品一覧/);
toMatchObject
オブジェクトが、指定したオブジェクトの内容を部分的に満たしていることを検証します。指定していない余分なプロパティがあっても失敗しません(toEqualとの違いです)。
expect(item).toMatchObject({ name: "練習用アイテムA" });
よくある使い所: APIレスポンスのうち、テストに関係のある一部のフィールドだけを確認したいとき。レスポンスの全項目を毎回律儀にtoEqualで比較していると、仕様変更のたびにテストが壊れて疲れます。
toThrow
関数を実行した際に、エラーが投げられる(throwされる)ことを検証します。検証対象は「関数そのもの」であり、expect(() => { ... })のように関数を渡す点に注意してください。
function parsePrice(text: string): number {
if (!text.endsWith("円")) {
throw new Error("不正な価格表記です");
}
return Number(text.replace("円", ""));
}
expect(() => parsePrice("1200")).toThrow("不正な価格表記です");
修飾子・便利機能
最後に、アサーションと組み合わせて使う修飾子や便利な機能を紹介します。地味ですが、覚えておくと確実に楽になります。
.not(否定)
マッチャーの前に.notを付けると、条件を反転できます。「〜ではない」ことを検証したいときに使います。
await expect(page.getByText("エラー")).not.toBeVisible();
expect(total).not.toBe(0);
expect.soft(ソフトアサーション)
通常のexpectは、検証に失敗した瞬間にそのテストを打ち切ります。expect.softを使うと、失敗してもテストの実行を止めずに次のコードへ進み、テスト全体が終わった時点でまとめて失敗を報告してくれます。
await expect.soft(page.getByText("見出し")).toHaveText("ようこそ");
await expect.soft(page.getByText("説明文")).toBeVisible();
// 1つ目が失敗しても、2つ目の検証まで実行される
よくある使い所: 1つの失敗のせいで、後続の検証結果(どこまで正しく動いているか)が分からなくなるのを避けたいとき。これ、意外と使えます。
expect.poll(関数の結果をリトライ)
ロケータではない、任意の非同期処理の結果を、条件を満たすまで繰り返しチェックしたいときに使います。関数を渡すと、その戻り値に対してマッチャーを適用し、失敗している間は自動的に再実行してくれます。
await expect.poll(async () => {
const response = await page.request.get("https://example.com/api/status");
return response.status();
}).toBe(200);
expect(…).toPass()(ブロック全体をリトライ)
複数行にまたがる処理のかたまりを、成功するまでまるごとリトライしたいときに使います。
await expect(async () => {
const count = await page.locator(".item").count();
expect(count).toBeGreaterThan(0);
}).toPass({ timeout: 10000 });
タイムアウトの個別指定
自動リトライ系のアサーションは、オプションで待機時間の上限(タイムアウト)を個別に変更できます。
await expect(page.getByText("処理完了")).toBeVisible({ timeout: 15000 });
よくある使い所: 通常より時間のかかる処理(大きなファイルのアップロード完了待ちなど)だけ、待ち時間を長めにしたいとき。
カスタムメッセージ
expectの第2引数にメッセージを渡すと、失敗したときのエラーメッセージにその説明文が追加され、原因を特定しやすくなります。
await expect(
page.getByRole("button", { name: "送信" }),
"送信ボタンは常に表示されているはず"
).toBeVisible();
初心者向けまとめ: 迷ったらこれ
- 相手が画面上の要素なら、まず
await expect(locator)...を使いましょう。toBeVisibleが一番よく使う入口です - 相手がページ全体(タイトルやURL)なら
await expect(page)... - 相手が普通の数値・文字列・配列・オブジェクト(
awaitが付かない、計算済みの値)ならexpect(値)...。オブジェクト・配列の中身比較はtoEqual、プリミティブ値の比較はtoBeです - 「表示されるまで待ちたいだけ」なら、難しく考える必要はありません。Playwrightの自動リトライ系アサーションが、要素が条件を満たすまで裏側で何度もチェックしてくれるので、
sleepやwaitForTimeoutを挟む必要は基本的にないはずです - どのマッチャーを使うか迷ったら、まず早見表に戻ってきてください
この記事で紹介したマッチャーは、公式ドキュメントで日々アップデートされています。バージョンによって新しいマッチャーが追加されることもあるので、より詳しい仕様や最新情報が必要なときは、あわせて公式のAPIリファレンスも確認することをおすすめします(正直、この手のリファレンス記事は書いた瞬間から少しずつ古くなっていくものだと思っています)。
というわけで、マッチャー一覧は以上です。ブックマークしておいて、次に「どのマッチャーだっけ」となったときにまた見に来てください。


コメント