mcpField
H
hyva-playwright-test

hyva-playwright-test

github.com/hyva-themes测试与质量851.3k 次安装13 周前更新
代码仓库

关于 hyva-playwright-test

hyva-playwright-test 是用于编写 Hyvä 店面 Playwright 测试的技能,适用于使用 Alpine.js 的项目。它整理了考虑 Alpine.js DOM 行为的定位器、交互和断言方式。

主要功能

  1. 01可靠且限定范围的定位器策略
  2. 02Alpine.js 交互模式
  3. 03自动重试的网页优先断言
  4. 04Hyvä 与 Luma 选择器对照

适用场景

  1. 01测试 Hyvä 店面消息并避开隐藏元素匹配
  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ä 使用 Alpine.js + Tailwind CSS 替代了 Luma 的 KnockoutJS/RequireJS/jQuery。Playwright 的严格模式(拒绝匹配多个元素的定位器)与 Alpine.js 的 DOM 模式冲突,因为隐藏元素会遍布整个页面。本文档记录了为 Hyvä 店面编写 Playwright 测试时发现的陷阱和解决方案。

首要规则:隐藏的 Alpine 元素

Hyvä 模板会在 DOM 中散布诸如 <div x-show="displayErrorMessage" class="message error"> 这样的元素。这些元素不可见但存在,因此像 .message.error 这样的裸选择器会同时匹配隐藏和可见的实例,导致 Playwright 严格模式违规。

始终将页面级消息限定在 #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)会作为静态内联内容呈现在 #maincontent 内,而非 #messages 容器内。对于这些内联消息,使用裸类选择器是正确的。

选择器策略

遵循 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 隐藏元素严格模式:匹配多个元素限定在唯一容器内(#messages),使用角色/属性选择器
x-defer="intersect"元素在可见前尚未初始化交互前调用 scrollIntoViewIfNeeded()
x-if(模板)条件为真之前,元素不会出现在 DOM 中先点击触发器,再查询子元素
输入框上的 x-model提交表单后 Alpine 会清除值不要在提交后断言输入框的值;通过成功消息进行验证
x-text / x-html 异步更新购物车徽标会异步更新使用带超时的 Web 优先断言:not.toHaveText('0', { timeout: 15_000 })
x-show 子菜单悬停前处于隐藏状态点击子项前,对父项调用 hover()
Alpine 表单显示选中复选框前字段处于隐藏状态选中复选框后调用 waitFor({ state: 'visible' })
对输入框调用 press('Enter')可能会意外提交绑定到 Alpine 的表单优先对提交按钮显式调用 .click()

断言

始终使用会自动等待并重试的 Web 优先断言:

// 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/。请加载与当前任务相关的文件:

始终有用:

特定页面(测试相应页面时加载):

评论

支持 Markdown