0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PlaywrightのE2Eで、プルダウン選択だけが時々タイムアウトしました。

再実行では通ります。当初は描画待ちを疑いました。しかし、原因は待機時間以外にありました。テスト側は、画面から操作不能な候補を選択していました。

一覧に存在しても、UIから選べるとは限らない

テストは、条件なしの一覧APIから候補を1件取得していました。

一方、画面側はキーワード検索後の結果をプルダウンへ表示します。

以下は説明用に置き換えた架空APIです。名称、パス、パラメータ、データ構造は実在するプロジェクトと無関係です。

GET /example-api/items/search
  ?keyword=...
  &active_only=true
  &leaf_only=true
  &match_aliases=true

一覧APIと検索APIでは条件が異なります。そのため、一覧へ現れても検索結果から外れる候補が混ざります。例として、無効状態、階層条件、別名検索の差があります。

該当候補をテストが引くと、クリック対象は最後まで現れません。

UIと同じ条件で候補を絞る

ブラウザ操作の前に、画面側と同じ条件で候補を再照会します。

type ItemCandidate = {
  id: number;
  label: string;
};

type ItemSearchResponse = {
  data?: Array<{ id: number }>;
};

private readonly isIncludedInDropdownResults = async (
  item: ItemCandidate,
): Promise<boolean> => {
  const searchParams = new URLSearchParams({
    keyword: item.label,
    active_only: 'true',
    leaf_only: 'true',
    match_aliases: 'true',
  });

  const response = await this.json<ItemSearchResponse>(
    `/example-api/items/search?${searchParams.toString()}`,
  );

  return (response.data ?? []).some((candidate) => candidate.id === item.id);
};

重要なのは、画面側が実際に送る条件を再現する点です。

ただし、この確認だけでは「描画済み」を保証しません。プルダウンには、件数上限、ページング、並び順、遅延読み込みなどもあります。表示範囲まで一致させる必要があります。仮想スクロールを使う画面なら、対象行がDOMへ現れる条件まで確認します。

選択可能性の確認には追加リクエストが必要です。そこで、安価な条件を先に適用します。

private readonly findSelectableItem = async (
  items: Array<ItemCandidate & { relatedCount: number }>,
  minimumRelatedCount: number,
): Promise<ItemCandidate | undefined> => {
  const primaryCandidates = items.filter(
    (item) => item.relatedCount >= minimumRelatedCount,
  );

  for (const item of primaryCandidates) {
    if (await this.isIncludedInDropdownResults(item)) {
      return item;
    }
  }

  return undefined;
};

保証対象は「一覧にある候補」から「画面検索の条件を満たす候補」へ変わります。

不明なUIタイムアウトを事前条件エラーへ変える

本当の利点は別にあります。flakeを減らすだけの対策ではありません。

適格な候補が見つからない場合、操作前に明確な理由で失敗させられます。

private readonly requireSelectableItem = async (
  items: Array<ItemCandidate & { relatedCount: number }>,
  minimumRelatedCount: number,
): Promise<ItemCandidate> => {
  const item = await this.findSelectableItem(items, minimumRelatedCount);

  if (!item) {
    throw new Error(
      'E2E事前条件エラー: プルダウン検索の条件を満たすテストデータがありません',
    );
  }

  return item;
};

従来は、クリック処理が30秒後に落ちていました。修正後は、データ準備の段階で即座に止まります。原因もログから判別できます。

リトライでは直らない

表示遅延が原因ではありません。対象は検索結果へ含まれないため、何秒待っても現れません。

再実行が通る理由は単純です。次回は別候補を選ぶ可能性があります。タイムアウト延長やクリック再試行は、原因を隠します。

同じ考え方を使える場面

画面側の制約 データ選択側のガード
親項目のクリックは展開になる 末端項目だけを選ぶ
未入力欄があると検証できない 必須値が揃った候補だけを選ぶ
自動入力は有効候補が1件の場合だけ動く 有効候補を1件だけ持つデータを選ぶ
一覧にはあるが検索結果へ出ない 同じ検索条件でIDを確認する

共通点は、失敗する候補を操作前に除外する設計です。

操作後に行うリトライを、事前の適格性判定へ置き換えます。

検索条件の同期ずれを検知する

コメントだけでは、仕様変更を見逃す可能性があります。

小さなcanary testを用意します。画面側の実リクエストと、探索側が想定する条件を比較可能です。監視だけなら、通信を横取りするpage.route()よりpage.waitForRequest()の方が単純です。

import { expect, test } from '@playwright/test';

const createExpectedSearchParams = (keyword: string): URLSearchParams =>
  new URLSearchParams({
    keyword,
    active_only: 'true',
    leaf_only: 'true',
    match_aliases: 'true',
  });

test('プルダウン検索とデータ探索で条件が一致する', async ({ page }) => {
  const keyword = 'sample';
  const requestPromise = page.waitForRequest((request) => {
    const url = new URL(request.url());

    return (
      request.method() === 'GET' &&
      url.pathname === '/example-api/items/search'
    );
  });

  await page.getByRole('combobox', { name: '項目' }).fill(keyword);

  const request = await requestPromise;
  const actual = new URL(request.url()).searchParams;
  const expected = createExpectedSearchParams(keyword);

  expect(Object.fromEntries(actual)).toEqual(Object.fromEntries(expected));
});

このテストは、パラメータ追加や既定値変更をCIで検知します。件数上限や並び順も表示条件に含まれる場合、比較対象へ追加します。

注意点

追加APIは、新しい失敗要因にもなります。通信失敗をUI操作のflakeへ混ぜないでください。データ準備エラーとして報告してください。候補数が多い場合は、一次条件による絞り込みや結果キャッシュも有効です。

本番コードの検索関数を直接流用すると、実装ミスまで共有する恐れがあります。条件生成だけをテスト側へ置きます。実リクエストとの一致は、canary testを使って確認する構成が安全です。

まとめ

  • 一覧へ出ることと、画面検索の条件を満たすことは別
  • 件数上限やページングがある場合、表示範囲も再現する
  • 適格な候補がなければ、UI操作前に明確なエラーを返す
  • 検索条件の同期ずれはcanary testで検知する
  • flakeはリトライで隠さず、適格な候補だけを選ぶ

E2Eの不安定さは、データ選択に原因がある場合もあります。クリック待機の調整前に、対象候補を確認してください。対象は、画面検索で要求される条件を満たす必要があります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?