Hyva Playwright Test

hyva-themes/hyva-ai-tools/skills/hyva-playwright-test

作者 hyva-themesa28a3333bcfae05dcba07e88f38a5ff9579903c7無授權條款收錄於 2026年10月9日更新於 2026年10月9日

Write Playwright tests for Hyvä themes with Alpine.js components. This skill should be used when writing e2e tests, creating page objects, or debugging selector issues in Playwright tests for Hyvä Magento storefronts. Trigger phrases include "write playwright test", "playwright alpine", "test hyva page", "e2e test", "playwright selector".

AI 產生的概覽

指導為使用 Alpine.js 的 Hyvä Magento 店面撰寫 Playwright 端對端測試,涵蓋選擇器、互動與斷言。

功能
此技能提供撰寫 Playwright 測試的說明與參考資料,對象是以 Alpine.js 與 Tailwind CSS 建構的 Hyvä Magento 店面。它記錄了隱藏的 Alpine x-show 元素造成的選擇器陷阱,建議定位器策略,並提供 Alpine 互動、web-first 斷言以及 Hyvä 與 Luma 選擇器差異的模式。它產出的是測試撰寫指引,而非可直接執行的測試檔案。
適用情境
在為 Hyvä Magento 店面撰寫或偵錯 Playwright 端對端測試時使用,尤其是在選擇器比對到多個元素或 Alpine.js 元件行為異常時。建立頁面物件或為 Hyvä 頁面挑選定位器時同樣適用。
執行需求
不隨附指令碼,僅為說明文件與參考 markdown 檔案。使用這些指引需要 Playwright 測試環境與可存取的 Hyvä Magento 店面,但技能本身除代理外不需要其他條件。

Writing Playwright Tests for Hyvä + Alpine.js

Overview

Hyvä replaces Luma's KnockoutJS/RequireJS/jQuery with Alpine.js + Tailwind CSS. Playwright's strict mode (rejects locators matching multiple elements) conflicts with Alpine.js DOM patterns where hidden elements exist throughout the page. This skill documents pitfalls and solutions discovered while writing Playwright tests for Hyvä storefronts.

The #1 Rule: Hidden Alpine Elements

Hyvä templates scatter elements like <div x-show="displayErrorMessage" class="message error"> throughout the DOM. These are invisible but present, so a bare selector like .message.error matches both hidden and visible instances, causing Playwright strict mode violations.

Always scope page-level messages to the #messages container:

typescript
// WRONG — matches hidden Alpine x-show elements throughout DOMawait expect(page.locator('.message.success')).toContainText('Added to cart');await expect(page.locator('.message-error')).toContainText('Error');
// RIGHT — scoped to the visible messages containerawait expect(page.locator('#messages .message.success')).toContainText('Added to cart');await expect(page.locator('#messages .message-error, #messages .message.error')).toContainText('Error');

Never use: bare .message, .message.error, .message.success, or div.message as selectors.

Exception — inline page messages: Not all .message elements are flash messages. The search results "no results" notice (.message.notice) renders as static inline content inside #maincontent, not inside the #messages container. For these inline messages, the bare class selector is correct.

Selector Strategy

Follow Playwright's recommended locator priority:

  1. getByRole() — always prefer — closest to how users perceive the page. Avoids text ambiguity where the same text appears in headings, links, breadcrumbs, and sr-only spans.
  2. getByLabel() — for form controls (checkboxes, inputs with associated labels).
  3. getByText() — for non-interactive elements, scoped to a container (e.g., page.locator('#maincontent').getByText(...)).
  4. getByPlaceholder(), getByAltText() — for inputs and images respectively.
  5. getByTestId() — when Hyvä provides data-testid attributes or when adding custom test IDs.
  6. CSS selectors — last resort, only when user-facing locators aren't available. Prefer aria-* attribute selectors (e.g., [aria-label="pagination"], [aria-current="page"]) over class-based selectors. When CSS is necessary, scope to a unique container (e.g., #messages .message.success).

Avoid: :visible pseudo-selector — per Playwright docs, "it's usually better to find a more reliable way to uniquely identify the element." Scope to a container or use role/attribute selectors instead. Only use :visible as an absolute last resort when the DOM provides no other way to distinguish elements.

Alpine.js Interaction Patterns

PatternProblemSolution
x-show hidden elementsStrict mode: multiple matchesScope to unique container (#messages), use role/attribute selectors
x-defer="intersect"Element not initialized until visiblescrollIntoViewIfNeeded() before interacting
x-if (template)Elements don't exist in DOM until condition trueClick the trigger first, then query children
x-model on inputsAlpine clears value after form submitDon't assert input value post-submit; verify via success message
x-text / x-html asyncCart badge updates asynchronouslyUse web-first assertions with timeout: not.toHaveText('0', { timeout: 15_000 })
x-show submenusHidden until hoverhover() on parent before clicking child
Alpine form revealFields hidden until checkbox checkedwaitFor({ state: 'visible' }) after checking the checkbox
press('Enter') on inputMay submit Alpine-bound form unexpectedlyPrefer explicit .click() on submit button

Assertions

Always use web-first assertions that auto-wait and retry:

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

For async Alpine.js updates (cart counts, prices), use extended timeouts on the assertion — never waitForTimeout():

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

Hyvä vs Luma Selector Differences

ElementHyvä SelectorLuma Selector
Pagination navgetByRole('navigation', { name: 'pagination' })ul.pages-items
Page linkgetByRole('link', { name: 'Page 2' }).pages-items li a
Active page[aria-current="page"]<strong> element
Filter buttongetByRole('button', { name: 'Color filter' }).filter-options-title
Cart icon badge#menu-cart-icon > span[x-text="summaryCount"].counter-number
Account menu#customer-menu + nav.customer-menu
Success message#messages .message.success.message-success
Error message#messages .message-error, #messages .message.error.message-error
Main menugetByRole('navigation', { name: 'Main menu' })nav.navigation
Footer navgetByRole('navigation', { name: 'Company Menu' }).getByRole('link', { name })nav ul li:nth-child(N) a
Product image#gallery img[itemprop="image"]#gallery img:visible
Add to Cart (card)getByRole('button', { name: /Add to Cart/ }).first()button.btn-primary:visible

References

See references/ for code examples. Load files relevant to the current task:

Always useful:

  • page-object-patterns.md [blocked] — Page object structure, navigation, form submits, redirects
  • selector-patterns.md [blocked] — Before/after selector fixes (messages, text ambiguity, forms)

Page-specific (load when testing that page):

  • cart-patterns.md [blocked] — Cart spinner wait, quantity changes, mini cart
  • product-patterns.md [blocked] — Bundle quantities, gallery images
  • account-patterns.md [blocked] — Password change (Alpine checkbox reveal)
  • category-patterns.md [blocked] — Filters (x-defer scroll), pagination (ARIA)
<!-- Copyright © Hyvä Themes https://hyva.io. All rights reserved. Licensed under OSL 3.0 -->

來源與署名

來源:hyva-themes/hyva-ai-tools位於skills/hyva-playwright-test提交a28a333

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架