ymmr
Recent Articles
    公開日: 2023/11/15

    Playwright でリスト内の要素の並び順をチェックする

    リスト内の要素の並び順を Playwright でテストしたかったのですが、Locator オブジェクトをうまく扱えずに少し躓きました。備忘録として、試行錯誤の結果を記事にします。

    やりたいことはとても単純で、ページ内に以下のような DOM が存在することをチェックするだけです。

    <ul>
      <li>Antonette</li>
      <li>Bret</li>
      <li>Samantha</li>
    </ul>
    

    結論から

    リスト内の要素を指す Locator オブジェクトに対して、toHaveText を呼び出します。この時に引数として文字列を格納した配列を渡すことで、リスト内の要素の textContent と、配列の値をを突き合わせて検証してくれます。

    import { expect, test } from '@playwright/test';
    
    test('should show the sorted usernames', async ({ page }) => {
      await page.goto('/users');
    
      await expect(page.getByRole('list').getByRole('listitem')).toHaveText([
        'Antonette',
        'Bret',
        'Samantha',
      ]);
    });
    

    試行錯誤の結果

    はじめは Testing Library と同じ感覚で以下のようなテストを書きました。

    import { expect, test } from '@playwright/test';
    
    test('should show the sorted usernames', async ({ page }) => {
      await page.goto('/users');
    
      const expectedUsernames = ['Antonette', 'Bret', 'Samantha'];
      const usernames = await page.getByRole('list').getByRole('listitem').all();
    
      expect(usernames).toHaveLength(3);
      for (let i = 0; i < usernames.length; i++) {
        await expect(usernames[i]).toHaveText(expectedUsernames[i]);
      }
    });
    

    Testing Library には要素を配列として取得する screen.getAllByRole が用意されている一方で、Playwright では単一の Locator オブジェクトを取得する page.getByRole を使うことになります。そのため、Locator.all を呼び出すことで、リスト内の要素を指す Locator オブジェクトを配列として取り出してあげます。

    配列として取得できてしまえば、あとはループ内でアサーションを回すだけだと思っていました。しかし、結果は何度やっても空の配列となってしまいます。

    この時点の理解では all 関数の返り値である Promise を await で待ってから検証しているため、結果が空の配列になってしまう理由に検討がつきませんでした。しかし、ドキュメントを読み返したところ、しっかりと理由が書かれていることに気づきました。

    locator.all() does not wait for elements to match the locator, and instead immediately returns whatever is present in the page. When the list of elements changes dynamically, locator.all() will produce unpredictable and flaky results. When the list of elements is stable, but loaded dynamically, wait for the full list to finish loading before calling locator.all().

    all 関数は API からのデータ取得を待たずに結果を返すため、getByRole('listitem') の検索結果が確定する前に空の配列を返してしまいます。意図した通りに実装できていなことがわかったので、waitFor でデータ取得の結果を待つ様に書き換えます。

    import { expect, test } from '@playwright/test';
    
    test('should show the sorted usernames', async ({ page }) => {
      await page.goto('/users');
    
      const expectedUsernames = ['Antonette', 'Bret', 'Samantha'];
    
      const userList = page.getByRole('list');
      await userList.waitFor();
      const usernames = await userList.getByRole('listitem').all();
    
      expect(usernames).toHaveLength(3);
      for (let i = 0; i < usernames.length; i++) {
        await expect(usernames[i]).toHaveText(expectedUsernames[i]);
      }
    });
    

    これで意図した結果は得られるのですが、冒頭で述べた通り公式ドキュメントでは toHaveText を使う方法が紹介されています。

    LocatorAssertions | PlaywrightThe [LocatorAssertions] class provides assertion methods that can be used to make assertions about the [Locator] state in the tests.playwright.dev

    おわりに

    今回は明らかに試行錯誤するより、公式ドキュメントや Google 検索に頼った方がよいケースでした・・・。この辺りの見極めをきちんとできるようになりたいです。