# Instructions

- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.

# Test info

- Name: cashier-full-flow.spec.ts >> 会员做单流程 >> 会员 - 多服务结账
- Location: tests/cashier-full-flow.spec.ts:260:7

# Error details

```
Error: locator.click: Error: strict mode violation: getByRole('button', { name: '服务' }) resolved to 2 elements:
    1) <button class="px-2 py-4 mx-1.5 rounded-xl text-sm font-bold transition whitespace-normal text-center leading-tight bg-rose-600 text-white">服务</button> aka getByRole('button', { name: '服务', exact: true })
    2) <button disabled class="flex-[2] py-3.5 bg-rose-600 text-white rounded-2xl text-base font-semibold active:scale-95 transition disabled:opacity-40 disabled:active:scale-100">请选择服务员工</button> aka getByRole('button', { name: '请选择服务员工' })

Call log:
  - waiting for getByRole('button', { name: '服务' })

```

# Page snapshot

```yaml
- generic [ref=e2]:
  - main [ref=e4]:
    - generic [ref=e5]:
      - generic [ref=e6]:
        - button "返回" [ref=e7] [cursor=pointer]:
          - img [ref=e8]
        - navigation [ref=e10]:
          - generic [ref=e11]:
            - generic [ref=e12]: 1. 购物车
            - generic [ref=e13]: ›
          - generic [ref=e14]:
            - generic [ref=e15]: 2. 小费
            - generic [ref=e16]: ›
          - generic [ref=e18]: 3. 付款
      - main [ref=e19]:
        - generic [ref=e20]:
          - generic [ref=e21]:
            - navigation [ref=e22]:
              - button "推荐" [ref=e23] [cursor=pointer]
              - button "服务" [ref=e24] [cursor=pointer]
              - button "产品" [ref=e25] [cursor=pointer]
              - button "储值方案" [ref=e26] [cursor=pointer]
              - button "次卡" [ref=e27] [cursor=pointer]
              - button "套餐" [ref=e28] [cursor=pointer]
            - generic [ref=e29]:
              - generic [ref=e31]:
                - img [ref=e32]
                - textbox "搜索" [ref=e35]
              - generic [ref=e37]:
                - button "纯色美甲 60 min ¥88.00" [active] [ref=e38] [cursor=pointer]:
                  - paragraph [ref=e39]: 纯色美甲
                  - paragraph [ref=e40]: 60 min
                  - paragraph [ref=e41]: ¥88.00
                - button "渐变美甲 90 min ¥138.00" [ref=e42] [cursor=pointer]:
                  - paragraph [ref=e43]: 渐变美甲
                  - paragraph [ref=e44]: 90 min
                  - paragraph [ref=e45]: ¥138.00
                - button "日式手绘花艺 120 min ¥268.00" [ref=e46] [cursor=pointer]:
                  - paragraph [ref=e47]: 日式手绘花艺
                  - paragraph [ref=e48]: 120 min
                  - paragraph [ref=e49]: ¥268.00
                - button "日式极简线条 90 min ¥198.00" [ref=e50] [cursor=pointer]:
                  - paragraph [ref=e51]: 日式极简线条
                  - paragraph [ref=e52]: 90 min
                  - paragraph [ref=e53]: ¥198.00
                - button "韩式奶油肌 90 min ¥188.00" [ref=e54] [cursor=pointer]:
                  - paragraph [ref=e55]: 韩式奶油肌
                  - paragraph [ref=e56]: 90 min
                  - paragraph [ref=e57]: ¥188.00
                - button "韩式贝壳片 105 min ¥228.00" [ref=e58] [cursor=pointer]:
                  - paragraph [ref=e59]: 韩式贝壳片
                  - paragraph [ref=e60]: 105 min
                  - paragraph [ref=e61]: ¥228.00
                - button "经典法式美甲 75 min ¥128.00" [ref=e62] [cursor=pointer]:
                  - paragraph [ref=e63]: 经典法式美甲
                  - paragraph [ref=e64]: 75 min
                  - paragraph [ref=e65]: ¥128.00
                - button "彩色法式 90 min ¥168.00" [ref=e66] [cursor=pointer]:
                  - paragraph [ref=e67]: 彩色法式
                  - paragraph [ref=e68]: 90 min
                  - paragraph [ref=e69]: ¥168.00
                - button "新娘法式甲 120 min ¥328.00" [ref=e70] [cursor=pointer]:
                  - paragraph [ref=e71]: 新娘法式甲
                  - paragraph [ref=e72]: 120 min
                  - paragraph [ref=e73]: ¥328.00
                - button "自然嫁接美睫 90 min ¥198.00" [ref=e74] [cursor=pointer]:
                  - paragraph [ref=e75]: 自然嫁接美睫
                  - paragraph [ref=e76]: 90 min
                  - paragraph [ref=e77]: ¥198.00
                - button "轻盈自然款 75 min ¥168.00" [ref=e78] [cursor=pointer]:
                  - paragraph [ref=e79]: 轻盈自然款
                  - paragraph [ref=e80]: 75 min
                  - paragraph [ref=e81]: ¥168.00
                - button "浓密娃娃款 105 min ¥268.00" [ref=e82] [cursor=pointer]:
                  - paragraph [ref=e83]: 浓密娃娃款
                  - paragraph [ref=e84]: 105 min
                  - paragraph [ref=e85]: ¥268.00
                - button "浓密芭比款 120 min ¥298.00" [ref=e86] [cursor=pointer]:
                  - paragraph [ref=e87]: 浓密芭比款
                  - paragraph [ref=e88]: 120 min
                  - paragraph [ref=e89]: ¥298.00
                - button "山茶花美睫 120 min ¥328.00" [ref=e90] [cursor=pointer]:
                  - paragraph [ref=e91]: 山茶花美睫
                  - paragraph [ref=e92]: 120 min
                  - paragraph [ref=e93]: ¥328.00
                - button "山茶花仙子款 135 min ¥388.00" [ref=e94] [cursor=pointer]:
                  - paragraph [ref=e95]: 山茶花仙子款
                  - paragraph [ref=e96]: 135 min
                  - paragraph [ref=e97]: ¥388.00
                - button "基础手部护理 45 min ¥68.00" [ref=e98] [cursor=pointer]:
                  - paragraph [ref=e99]: 基础手部护理
                  - paragraph [ref=e100]: 45 min
                  - paragraph [ref=e101]: ¥68.00
                - button "深层滋养手膜 60 min ¥128.00" [ref=e102] [cursor=pointer]:
                  - paragraph [ref=e103]: 深层滋养手膜
                  - paragraph [ref=e104]: 60 min
                  - paragraph [ref=e105]: ¥128.00
                - button "手部SPA套餐 90 min ¥198.00" [ref=e106] [cursor=pointer]:
                  - paragraph [ref=e107]: 手部SPA套餐
                  - paragraph [ref=e108]: 90 min
                  - paragraph [ref=e109]: ¥198.00
                - button "基础足部护理 60 min ¥88.00" [ref=e110] [cursor=pointer]:
                  - paragraph [ref=e111]: 基础足部护理
                  - paragraph [ref=e112]: 60 min
                  - paragraph [ref=e113]: ¥88.00
                - button "足部SPA舒缓 90 min ¥168.00" [ref=e114] [cursor=pointer]:
                  - paragraph [ref=e115]: 足部SPA舒缓
                  - paragraph [ref=e116]: 90 min
                  - paragraph [ref=e117]: ¥168.00
                - button "足部深层修复 105 min ¥228.00" [ref=e118] [cursor=pointer]:
                  - paragraph [ref=e119]: 足部深层修复
                  - paragraph [ref=e120]: 105 min
                  - paragraph [ref=e121]: ¥228.00
                - button "基础修甲护理 30 min ¥38.00" [ref=e122] [cursor=pointer]:
                  - paragraph [ref=e123]: 基础修甲护理
                  - paragraph [ref=e124]: 30 min
                  - paragraph [ref=e125]: ¥38.00
                - button "精修甲型设计 45 min ¥58.00" [ref=e126] [cursor=pointer]:
                  - paragraph [ref=e127]: 精修甲型设计
                  - paragraph [ref=e128]: 45 min
                  - paragraph [ref=e129]: ¥58.00
                - button "甲片延长 120 min ¥268.00" [ref=e130] [cursor=pointer]:
                  - paragraph [ref=e131]: 甲片延长
                  - paragraph [ref=e132]: 120 min
                  - paragraph [ref=e133]: ¥268.00
                - button "甲油胶卸除 30 min ¥38.00" [ref=e134] [cursor=pointer]:
                  - paragraph [ref=e135]: 甲油胶卸除
                  - paragraph [ref=e136]: 30 min
                  - paragraph [ref=e137]: ¥38.00
                - button "光疗甲卸除 45 min ¥58.00" [ref=e138] [cursor=pointer]:
                  - paragraph [ref=e139]: 光疗甲卸除
                  - paragraph [ref=e140]: 45 min
                  - paragraph [ref=e141]: ¥58.00
                - button "美甲+手部护理套餐 120 min ¥148.00" [ref=e142] [cursor=pointer]:
                  - paragraph [ref=e143]: 美甲+手部护理套餐
                  - paragraph [ref=e144]: 120 min
                  - paragraph [ref=e145]: ¥148.00
                - button "日式美甲+美睫套餐 180 min ¥388.00" [ref=e146] [cursor=pointer]:
                  - paragraph [ref=e147]: 日式美甲+美睫套餐
                  - paragraph [ref=e148]: 180 min
                  - paragraph [ref=e149]: ¥388.00
                - button "新娘全套美甲美睫 240 min ¥588.00" [ref=e150] [cursor=pointer]:
                  - paragraph [ref=e151]: 新娘全套美甲美睫
                  - paragraph [ref=e152]: 240 min
                  - paragraph [ref=e153]: ¥588.00
                - button "足部护理+修甲套餐 105 min ¥118.00" [ref=e154] [cursor=pointer]:
                  - paragraph [ref=e155]: 足部护理+修甲套餐
                  - paragraph [ref=e156]: 105 min
                  - paragraph [ref=e157]: ¥118.00
          - complementary [ref=e158]:
            - generic [ref=e160]:
              - generic [ref=e161]: A
              - generic [ref=e162]:
                - paragraph [ref=e163]: Ada
                - generic [ref=e164]:
                  - generic [ref=e165]: "876542211111"
                  - generic [ref=e166]: · 40次到店
              - button "切换会员" [ref=e167] [cursor=pointer]:
                - text: 切换会员
                - img [ref=e168]
            - paragraph [ref=e172]: 添加项目开始结账
          - generic [ref=e175]:
            - generic [ref=e176]:
              - heading "纯色美甲" [level=3] [ref=e178]
              - generic [ref=e179]:
                - generic [ref=e180]: ¥88.00
                - button "close" [ref=e181] [cursor=pointer]: ✕
            - generic [ref=e182]:
              - generic [ref=e183]: 选择服务员工*
              - generic [ref=e184]:
                - button "a alice" [ref=e185] [cursor=pointer]:
                  - generic [ref=e186]: a
                  - generic [ref=e187]: alice
                - button "e echo" [ref=e188] [cursor=pointer]:
                  - generic [ref=e189]: e
                  - generic [ref=e190]: echo
                - button "刘 刘佳" [ref=e191] [cursor=pointer]:
                  - generic [ref=e192]: 刘
                  - generic [ref=e193]: 刘佳
                - button "周 周小雨" [ref=e194] [cursor=pointer]:
                  - generic [ref=e195]: 周
                  - generic [ref=e196]: 周小雨
                - button "孙 孙燕" [ref=e197] [cursor=pointer]:
                  - generic [ref=e198]: 孙
                  - generic [ref=e199]: 孙燕
                - button "张 张婷婷" [ref=e200] [cursor=pointer]:
                  - generic [ref=e201]: 张
                  - generic [ref=e202]: 张婷婷
                - button "李 李雪" [ref=e203] [cursor=pointer]:
                  - generic [ref=e204]: 李
                  - generic [ref=e205]: 李雪
                - button "王 王小丽" [ref=e206] [cursor=pointer]:
                  - generic [ref=e207]: 王
                  - generic [ref=e208]: 王小丽
                - button "赵 赵敏" [ref=e209] [cursor=pointer]:
                  - generic [ref=e210]: 赵
                  - generic [ref=e211]: 赵敏
                - button "陈 陈美美" [ref=e212] [cursor=pointer]:
                  - generic [ref=e213]: 陈
                  - generic [ref=e214]: 陈美美
                - button "黄 黄丽丽" [ref=e215] [cursor=pointer]:
                  - generic [ref=e216]: 黄
                  - generic [ref=e217]: 黄丽丽
            - generic [ref=e218]:
              - button "取消" [ref=e219] [cursor=pointer]
              - button "请选择服务员工" [disabled] [ref=e220]
  - region "Notifications (F8)":
    - list
```

# Test source

```ts
  1   | import { test, expect, Page } from '@playwright/test';
  2   | import { setupApiMonitor, ApiError } from './helpers';
  3   | 
  4   | // echo 账号有员工身份，可以开班和收银
  5   | const EMAIL = 'echo@shboka.com';
  6   | const PASSWORD = '123456';
  7   | const STORE = 'boka-nail';
  8   | 
  9   | async function loginAndEnterStore(page: Page) {
  10  |   await page.goto('/login');
  11  |   await page.getByLabel('邮箱').fill(EMAIL);
  12  |   await page.getByLabel('密码').fill(PASSWORD);
  13  |   await page.getByRole('button', { name: '登录' }).click();
  14  |   await expect(page.getByText('选择门店')).toBeVisible({ timeout: 10000 });
  15  |   await page.getByRole('button', { name: STORE }).click();
  16  |   await page.getByRole('button', { name: '进入门店' }).click();
  17  |   await expect(page.getByRole('heading', { name: '工作台' })).toBeVisible({ timeout: 10000 });
  18  | }
  19  | 
  20  | async function enterPOS(page: Page) {
  21  |   // 导航到收银页面
  22  |   await page.goto('/pos/orders');
  23  |   await expect(page.getByRole('heading', { name: '收银' })).toBeVisible({ timeout: 10000 });
  24  | 
  25  |   // 点击收银台按钮
  26  |   await page.getByRole('button', { name: '收银台' }).click();
  27  |   await page.waitForTimeout(1000);
  28  | 
  29  |   // 如果未开班，先开班
  30  |   const openShiftBtn = page.getByRole('button', { name: '开班' });
  31  |   if (await openShiftBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
  32  |     await openShiftBtn.click();
  33  |     await page.waitForTimeout(500);
  34  |     // 输入备用金
  35  |     const amountInput = page.getByRole('spinbutton');
  36  |     if (await amountInput.isVisible({ timeout: 2000 }).catch(() => false)) {
  37  |       await amountInput.fill('500');
  38  |     }
  39  |     await page.getByRole('button', { name: '确认' }).click();
  40  |     await page.waitForTimeout(2000);
  41  |   }
  42  | }
  43  | 
  44  | async function addServiceToCart(page: Page, serviceName: string) {
  45  |   // 切换到服务分类
> 46  |   await page.getByRole('button', { name: '服务' }).click();
      |                                                  ^ Error: locator.click: Error: strict mode violation: getByRole('button', { name: '服务' }) resolved to 2 elements:
  47  |   await page.waitForTimeout(500);
  48  |   // 点击服务
  49  |   await page.getByRole('button', { name: new RegExp(serviceName) }).click();
  50  |   await page.waitForTimeout(500);
  51  | }
  52  | 
  53  | async function goToPayment(page: Page) {
  54  |   // 购物车 → 小费
  55  |   await page.getByRole('button', { name: '继续付款 ›' }).click();
  56  |   await expect(page.getByRole('heading', { name: '小费' })).toBeVisible({ timeout: 5000 });
  57  |   // 无小费
  58  |   await page.getByRole('button', { name: '无小费' }).click();
  59  |   await page.waitForTimeout(300);
  60  |   // 小费 → 付款
  61  |   await page.getByRole('button', { name: '继续付款 ›' }).click();
  62  |   await expect(page.getByText('支付方式')).toBeVisible({ timeout: 5000 });
  63  | }
  64  | 
  65  | async function confirmPayment(page: Page) {
  66  |   await page.getByRole('button', { name: /确认收款/ }).click();
  67  |   await page.waitForTimeout(3000);
  68  | }
  69  | 
  70  | // ═══════════════════════════════════════════
  71  | // 散客做单子
  72  | // ═══════════════════════════════════════════
  73  | test.describe('散客做单流程', () => {
  74  | 
  75  |   test('散客 - 单服务现金结账', async ({ page }) => {
  76  |     const monitor = setupApiMonitor(page);
  77  |     await loginAndEnterStore(page);
  78  |     await enterPOS(page);
  79  | 
  80  |     // 不选客户 = 散客
  81  |     await addServiceToCart(page, '纯色美甲');
  82  | 
  83  |     // 验证购物车
  84  |     const cart = page.getByRole('complementary');
  85  |     await expect(cart.getByText('¥88.00').first()).toBeVisible();
  86  | 
  87  |     // 走到付款
  88  |     await goToPayment(page);
  89  | 
  90  |     // 验证客户来源是散客
  91  |     await expect(page.getByText('散客')).toBeVisible();
  92  | 
  93  |     // 验证应收金额
  94  |     await expect(page.getByText('¥88.00').first()).toBeVisible();
  95  | 
  96  |     // 确认收款
  97  |     await confirmPayment(page);
  98  | 
  99  |     // 验证 API 无错误
  100 |     await monitor.expectNoApiErrors();
  101 |   });
  102 | 
  103 |   test('散客 - 多服务结账', async ({ page }) => {
  104 |     const monitor = setupApiMonitor(page);
  105 |     await loginAndEnterStore(page);
  106 |     await enterPOS(page);
  107 | 
  108 |     // 加两个服务
  109 |     await addServiceToCart(page, '纯色美甲');
  110 |     await addServiceToCart(page, '渐变美甲');
  111 | 
  112 |     // 验证合计 88 + 138 = 226
  113 |     const cart = page.getByRole('complementary');
  114 |     await expect(cart.getByText('¥226.00').first()).toBeVisible();
  115 | 
  116 |     // 走到付款
  117 |     await goToPayment(page);
  118 | 
  119 |     // 确认收款
  120 |     await confirmPayment(page);
  121 |     await monitor.expectNoApiErrors();
  122 |   });
  123 | 
  124 |   test('散客 - 带小费结账', async ({ page }) => {
  125 |     const monitor = setupApiMonitor(page);
  126 |     await loginAndEnterStore(page);
  127 |     await enterPOS(page);
  128 | 
  129 |     await addServiceToCart(page, '纯色美甲');
  130 | 
  131 |     // 购物车 → 小费
  132 |     await page.getByRole('button', { name: '继续付款 ›' }).click();
  133 |     await expect(page.getByRole('heading', { name: '小费' })).toBeVisible({ timeout: 5000 });
  134 | 
  135 |     // 选 10% 小费
  136 |     await page.getByRole('button', { name: '10%' }).click();
  137 |     await page.waitForTimeout(300);
  138 | 
  139 |     // 小费 → 付款
  140 |     await page.getByRole('button', { name: '继续付款 ›' }).click();
  141 |     await expect(page.getByText('支付方式')).toBeVisible({ timeout: 5000 });
  142 | 
  143 |     // 确认收款
  144 |     await confirmPayment(page);
  145 |     await monitor.expectNoApiErrors();
  146 |   });
```