Hyva Playwright Test

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

作者 hyva-themesa28a3333bcfae05dcba07e88f38a5ff9579903c7无许可证84 个星标收录于 2026年10月9日更新于 2026年10月9日仓库2个月前更新

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 端到端测试,涵盖选择器、交互与断言。

功能
该技能为针对基于 Alpine.js 和 Tailwind CSS 构建的 Hyvä Magento 店面编写 Playwright 测试提供说明和参考资料。它记录了隐藏的 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 从公开仓库中收录这些内容。

举报或申请下架