QR Send

スマホとPCのChrome間で、URLやテキストをQRコード1回のペアリングで送り合える自作Chrome拡張。アカウント不要・無料。

使ってみる →

Playwright の expect マッチャー完全リファレンス【初心者向け・保存版】

Selenium・自動化

はじめに

Playwrightでテストを書いていると、expect(...)という書き方に必ず出会います。「今、この状態になっているはずだ」をコードで検証する仕組みで、アサーション(assertion)と呼ばれるものです。日本語では「表明」「検証」などと訳されますが、私は普段からそのまま「アサーション」と呼んでいます。

たとえば次のようなコードです。

await expect(page.getByRole("button", { name: "送信" })).toBeVisible();

送信という名前のボタンが画面に表示されているはずだ」を検証しているコードです。実際には表示されていなければテストは失敗し、「表示されていませんでした」というエラーメッセージが出ます。

この記事は、Playwrightのexpectで使えるマッチャー(.toBeVisible()のような、検証の種類を表す部分)を一望できる保存版リファレンスです。「この状態を確認したいけど、どのマッチャーを使えばいいんだっけ」となったときに、開いてもらえば見つかるようにまとめました。実在しないマッチャーを載せると余計に混乱を招くだけなので、この記事では実際にPlaywrightに存在するマッチャーだけを扱っています。

expectには2つの系統がある

初心者の方にまず押さえておいてほしいのは、Playwrightのexpectには性質の違う2つの系統がある、という点です。

  1. 自動リトライする非同期アサーション: await expect(locator).toBeVisible()のように、Playwrightが用意したロケータページAPIレスポンスを検証するものです。条件を満たすまで(デフォルトでは最大5秒間)自動的に何度もチェックを繰り返してくれます。要素がまだ表示されていなくても表示されるまで勝手に待ってくれるので、sleepのような待機処理を自分で書く必要はありません。ここは本当にありがたい仕組みです。
  2. 汎用の同期アサーション: 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
属性(hrefdata-*など)の値 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ロール(buttonlinkcheckboxなど、支援技術に伝わる「役割」)を検証します。

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.20.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""nullundefinedfalseNaNなど)であることを検証します。

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の自動リトライ系アサーションが、要素が条件を満たすまで裏側で何度もチェックしてくれるので、sleepwaitForTimeoutを挟む必要は基本的にないはずです
  • どのマッチャーを使うか迷ったら、まず早見表に戻ってきてください

この記事で紹介したマッチャーは、公式ドキュメントで日々アップデートされています。バージョンによって新しいマッチャーが追加されることもあるので、より詳しい仕様や最新情報が必要なときは、あわせて公式のAPIリファレンスも確認することをおすすめします(正直、この手のリファレンス記事は書いた瞬間から少しずつ古くなっていくものだと思っています)。

というわけで、マッチャー一覧は以上です。ブックマークしておいて、次に「どのマッチャーだっけ」となったときにまた見に来てください。

▲ 目次に戻る

コメント

タイトルとURLをコピーしました