mcpField
H
hyva-playwright-test

hyva-playwright-test

github.com/hyva-themesテスト・品質851.3k 回インストール13 週間前に更新
リポジトリ

hyva-playwright-test について

hyva-playwright-test は、Alpine.js を使う Hyvä ストアフロントの Playwright テスト作成向けスキルです。Alpine.js の DOM の動作を考慮したロケーター、操作、アサーションの方法をまとめています。

主な機能

  1. 01信頼性の高いスコープ付きロケーター戦略
  2. 02Alpine.js の操作パターン
  3. 03自動再試行する Web ファーストアサーション
  4. 04Hyvä と Luma のセレクター比較

活用シーン

  1. 01非表示要素との一致を避けてストアフロントのメッセージをテスト
  2. 02フィルター、メニュー、フォーム、カート更新のテスト作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/hyva-themes/hyva-ai-tools --skill hyva-playwright-test

インストール後、Claude Code で/hyva-playwright-testを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
account-patterns.md
cart-patterns.md
category-patterns.md
page-object-patterns.md
product-patterns.md
selector-patterns.md

詳細

Hyvä + Alpine.js の Playwright テストの作成

概要

Hyvä は、Luma の KnockoutJS/RequireJS/jQuery を Alpine.js + Tailwind CSS に置き換えます。Playwright の strict mode(複数の要素に一致するロケーターを拒否します)は、非表示の要素がページ全体に存在する Alpine.js の DOM パターンと競合します。このスキルでは、Hyvä のストアフロント向けに Playwright テストを作成する際に見つかった落とし穴と解決策について説明します。

最重要ルール:非表示の Alpine 要素

Hyvä のテンプレートでは、<div x-show="displayErrorMessage" class="message error"> のような要素が DOM のあちこちに配置されています。これらは非表示ですが DOM 上には存在するため、.message.error のような単独のセレクターは、非表示と表示中の両方の要素に一致し、Playwright の strict mode 違反を引き起こします。

ページレベルのメッセージは、必ず #messages コンテナーにスコープしてください:

// WRONG — matches hidden Alpine x-show elements throughout DOM
await expect(page.locator('.message.success')).toContainText('Added to cart');
await expect(page.locator('.message-error')).toContainText('Error');

// RIGHT — scoped to the visible messages container
await expect(page.locator('#messages .message.success')).toContainText('Added to cart');
await expect(page.locator('#messages .message-error, #messages .message.error')).toContainText('Error');

使用しないでください: セレクターとしての単独の .message、.message.error、.message.success、div.message。

例外 — ページ内メッセージ: すべての .message 要素がフラッシュメッセージというわけではありません。検索結果の「結果なし」通知(.message.notice)は、#messages コンテナー内ではなく、#maincontent 内に静的なページ内コンテンツとして描画されます。これらのページ内メッセージには、単独のクラスセレクターが適切です。

セレクターの方針

Playwright が推奨するロケーターの優先順位に従ってください:

  1. getByRole() — 常に優先してください — ユーザーのページの認識方法に最も近い方法です。同じテキストが見出し、リンク、パンくずリスト、sr-only のスパンに表示される場合の、テキストの曖昧さを避けられます。
  2. getByLabel() — フォームコントロール(チェックボックス、関連付けられたラベルを持つ入力欄)に使用します。
  3. getByText() — 対話型でない要素に使用し、コンテナーにスコープします(例:page.locator('#maincontent').getByText(...))。
  4. getByPlaceholder()、getByAltText() — それぞれ入力欄と画像に使用します。
  5. getByTestId() — Hyvä が data-testid 属性を提供する場合、またはカスタムテスト ID を追加する場合に使用します。
  6. CSS セレクター — 最後の手段として、ユーザー向けのロケーターが利用できない場合にのみ使用します。クラスベースのセレクターよりも、aria-* 属性セレクター(例:[aria-label="pagination"]、[aria-current="page"])を優先してください。CSS が必要な場合は、一意のコンテナーにスコープしてください(例:#messages .message.success)。

避けてください: :visible 疑似セレクター — Playwright のドキュメントにあるとおり、「通常は、要素を一意に識別できる、より信頼性の高い方法を見つけるほうが適切です」。代わりに、コンテナーにスコープするか、ロール/属性セレクターを使用してください。DOM に要素を区別する他の方法がない場合の、絶対的な最終手段としてのみ :visible を使用してください。

Alpine.js の操作パターン

パターン問題解決策
x-show で非表示になった要素strict mode:複数の要素に一致一意のコンテナー(#messages)にスコープするか、ロール/属性セレクターを使用する
x-defer="intersect"要素が表示されるまで初期化されない操作の前に scrollIntoViewIfNeeded() を実行する
x-if(テンプレート)条件が true になるまで要素が DOM に存在しない先にトリガーをクリックしてから、子要素を検索する
入力欄の x-modelフォーム送信後に Alpine が値をクリアする送信後に入力値をアサートしない。成功メッセージで確認する
x-text/x-html の非同期更新カートのバッジが非同期で更新されるタイムアウト付きのウェブファーストアサーションを使用する:not.toHaveText('0', { timeout: 15_000 })
x-show のサブメニューホバーするまで非表示子要素をクリックする前に、親要素に hover() を実行する
Alpine のフォーム表示チェックボックスがオンになるまでフィールドが非表示チェックボックスをオンにした後、waitFor({ state: 'visible' }) を実行する
入力欄での press('Enter')Alpine にバインドされたフォームが予期せず送信される可能性がある送信ボタンの明示的な .click() を優先する

アサーション

自動的に待機して再試行するウェブファーストアサーションを必ず使用してください:

// DO — auto-retries             // DON'T — no retry
await expect(loc).toBeVisible(); // expect(await loc.isVisible()).toBe(true);
await expect(loc).toContainText('X'); // expect(await loc.textContent()).toContain('X');

非同期の Alpine.js 更新(カートの数量、価格)には、アサーションに長めのタイムアウトを指定してください — waitForTimeout() は使用しないでください:

// Cart count updates asynchronously via Alpine x-text
await expect(page.locator('#menu-cart-icon span[x-text="summaryCount"]'))
  .not.toHaveText('0', { timeout: 15_000 });

Hyvä と Luma のセレクターの違い

要素Hyvä のセレクターLuma のセレクター
ページネーションのナビゲーションgetByRole('navigation', { name: 'pagination' })ul.pages-items
ページリンクgetByRole('link', { name: 'Page 2' }).pages-items li a
アクティブなページ[aria-current="page"]<strong> 要素
フィルターボタンgetByRole('button', { name: 'Color filter' }).filter-options-title
カートアイコンのバッジ#menu-cart-icon > span[x-text="summaryCount"].counter-number
アカウントメニュー#customer-menu + nav.customer-menu
成功メッセージ#messages .message.success.message-success
エラーメッセージ#messages .message-error, #messages .message.error.message-error
メインメニューgetByRole('navigation', { name: 'Main menu' })nav.navigation
フッターナビゲーションgetByRole('navigation', { name: 'Company Menu' }).getByRole('link', { name })nav ul li:nth-child(N) a
商品画像#gallery img[itemprop="image"]#gallery img:visible
カートに追加(カード)getByRole('button', { name: /Add to Cart/ }).first()button.btn-primary:visible

参考資料

コード例については references/ を参照してください。現在のタスクに関連するファイルを読み込んでください。

常に役立つ資料:

  • page-object-patterns.md — ページオブジェクトの構成、ナビゲーション、フォーム送信、リダイレクト
  • selector-patterns.md — セレクター修正の前後(メッセージ、テキストの曖昧さ、フォーム)

ページ別(そのページをテストする場合に読み込みます):

コメント