import { expect, test } from '@playwright/test' const required = ['purchase.live.v1', 'purchase.address-update.v1', 'purchase.order-create.v1', 'purchase.spec-probe.v1'] const routes = [{ path: '/workbench', component: 'Layout', menuName: 'Workbench', title: '工作台', visible: '0', children: [ { path: '/syb-products', component: '/goauto/syb-products/index', menuName: 'GoAutoSybProducts', title: 'SYB 商品', visible: '0' } ] }] async function mock(page: any) { const state = { userId: 1, available: true, previewFailed: false, previews: [] as any[], created: [] as any[] } await page.context().addCookies([{ name: 'Admin-Token', value: 'mock-token', domain: 'localhost', path: '/' }]) await page.route('**/api/**', async (route: any) => { const url = new URL(route.request().url()) const ok = (data: any) => route.fulfill({ json: { code: 200, data } }) if (url.pathname.startsWith('/src/api/')) return route.continue() if (url.pathname.endsWith('/api/v1/getinfo')) return ok({ userId: state.userId, roles: ['admin'], name: '同名测试用户', avatar: '', introduction: '', permissions: [] }) if (url.pathname.endsWith('/api/v1/menurole')) return ok(routes) if (url.pathname.endsWith('/api/admin/v1/syb-products')) return ok({ items: [ { id: 1, orderCode: 'TEST-1', shopeeItemId: '1001', shopeeProductId: 2, productTitle: '测试商品', targetColor: '黑色', targetSize: 'XL', quantity: 1, parseStatus: 'success' } ], total: 1, page: 1, pageSize: 20 }) if (url.pathname.endsWith('/api/admin/v1/devices')) return ok({ items: [ { id: 10, name: '测试设备A', model: 'A', selectable: state.available, capabilities: required }, { id: 20, name: '测试设备B', model: 'B', selectable: true, capabilities: required } ], total: 2 }) if (url.pathname.endsWith('/purchase-tasks/batch-preview')) { const body = route.request().postDataJSON() state.previews.push(body) if (state.previewFailed) return route.fulfill({ status: 500, json: { code: 500, message: '模拟预检失败' } }) return ok({ items: [{ sybProductId: 1, eligible: true, processStage: 'purchase_ready', reason: '规格就绪', quantity: 1 }], eligibleCount: 1, skippedCount: 0 }) } if (url.pathname.endsWith('/purchase-tasks/batch')) { state.created.push(route.request().postDataJSON()) return ok({ items: [], createdCount: 1, failedCount: 0 }) } return ok([]) }) return state } async function open(page: any, reload = false) { if (reload) await page.reload() else await page.goto('/#/syb-products') await expect(page.locator('tbody').getByText('可创建采购', { exact: true })).toBeVisible() await page.locator('.el-table__body-wrapper tbody tr').first().locator('.el-checkbox').click() await page.getByRole('button', { name: /^创建采购\s*1$/ }).click() const dialog = page.getByRole('dialog', { name: '批量创建采购任务' }) await expect(dialog).toBeVisible() await expect(dialog.locator('.el-loading-mask')).toBeHidden() return dialog } async function choose(page: any, dialog: any, name: string) { await dialog.locator('.el-select').click() await page.getByRole('option', { name, exact: true }).click() await expect(dialog.locator('.el-loading-mask')).toBeHidden() } test('设备选择跨弹窗和刷新恢复,预检与创建使用同一设备', async ({ page }) => { const state = await mock(page) let dialog = await open(page) await choose(page, dialog, '测试设备A · A') await dialog.getByRole('button', { name: '返回列表' }).click() await page.getByRole('button', { name: /^创建采购\s*1$/ }).click() await expect(dialog.getByText('测试设备A · A', { exact: true })).toBeVisible() await expect.poll(() => state.previews.at(-1)?.deviceId).toBe(10) dialog = await open(page, true) await expect(dialog.getByText('测试设备A · A', { exact: true })).toBeVisible() await dialog.getByRole('button', { name: '创建 1 个采购任务' }).click() await expect.poll(() => state.created.length).toBe(1) expect(state.created[0].deviceId).toBe(10) }) test('不可用设备不被静默替换,主动清空后保持不指定', async ({ page }) => { const state = await mock(page) let dialog = await open(page) await choose(page, dialog, '测试设备A · A') state.available = false dialog = await open(page, true) await expect(dialog.getByRole('alert').filter({ hasText: '上次选择的设备当前不可用' })).toBeVisible() await expect(dialog.getByRole('button', { name: /创建 .* 个采购任务/ })).toBeDisabled() await dialog.locator('.el-select').hover() await dialog.locator('.el-select__clear').click() await expect.poll(() => state.previews.at(-1)?.deviceId).toBeUndefined() dialog = await open(page, true) await expect(dialog.getByText('上次选择的设备(当前不可用)', { exact: true })).toHaveCount(0) await expect(dialog.getByText('不指定,由空闲设备领取', { exact: true })).toBeVisible() }) test('同名用户偏好按ID隔离,预检失败禁用创建', async ({ page }) => { const state = await mock(page) let dialog = await open(page) await choose(page, dialog, '测试设备A · A') state.userId = 2 dialog = await open(page, true) await expect(dialog.getByText('不指定,由空闲设备领取', { exact: true })).toBeVisible() await choose(page, dialog, '测试设备B · B') state.userId = 1 dialog = await open(page, true) await expect(dialog.getByText('测试设备A · A', { exact: true })).toBeVisible() state.previewFailed = true await choose(page, dialog, '测试设备B · B') await expect(dialog.getByText('采购预检失败,请重试。', { exact: true })).toBeVisible() await expect(dialog.getByRole('button', { name: /创建 .* 个采购任务/ })).toBeDisabled() expect(state.created).toHaveLength(0) }) test('关闭重开后旧预检响应不覆盖新设备结果', async ({ page }) => { await mock(page) const dialog = await open(page) let held = false let finished = false let release: () => void = () => {} const gate = new Promise(resolve => { release = resolve }) await page.route('**/purchase-tasks/batch-preview', async route => { if (route.request().postDataJSON().deviceId !== 10 || held) return route.fallback() held = true await gate await route.fulfill({ json: { code: 200, data: { items: [], eligibleCount: 0, skippedCount: 1 } } }) finished = true }) await dialog.locator('.el-select').click() await page.getByRole('option', { name: '测试设备A · A', exact: true }).click() await expect.poll(() => held).toBe(true) await expect(dialog.getByRole('button', { name: /创建 .* 个采购任务/ })).toBeDisabled() await dialog.getByRole('button', { name: '返回列表' }).click() await page.getByRole('button', { name: /^创建采购\s*1$/ }).click() await expect(dialog.locator('.el-loading-mask')).toBeHidden() await choose(page, dialog, '测试设备B · B') release() await expect.poll(() => finished).toBe(true) await expect(dialog.getByText('测试设备B · B', { exact: true })).toBeVisible() await expect(dialog.getByRole('button', { name: '创建 1 个采购任务' })).toBeEnabled() })