import { expect, test, type Page, type BrowserContext } from '@playwright/test' async function setup(page: Page, context: BrowserContext, mode = 'success') { await page.setViewportSize({ width: 1920, height: 1080 }) await context.addCookies([{ name: 'Admin-Token', value: 'isolated-test-token', domain: 'localhost', path: '/' }]) const previews: number[][] = []; const writes: number[][] = [] await page.route('**/api/**', async route => { const url = new URL(route.request().url()); const pathname = url.pathname if (pathname.startsWith('/src/api/')) return route.continue() const reply = (data: unknown) => route.fulfill({ json: { code: 200, data } }) if (pathname.endsWith('/api/v1/menurole')) return reply([{ path: '/workbench', component: 'Layout', menuName: 'Workbench', title: '工作台', visible: '0', children: [{ path: '/syb-inner-codes', component: '/goauto/syb-inner-codes/index', menuName: 'GoAutoSybInnerCodes', title: '档口入库码', visible: '0' }] }]) if (pathname.endsWith('/api/v1/getinfo')) return reply({ roles: ['purchaser'], name: '模拟采购员', avatar: '', permissions: [] }) if (pathname.endsWith('/syb-inner-codes') && route.request().method() === 'GET') return reply({ items: ['updated', 'ready', 'failed', 'ready'].map((status, i) => ({ id: i + 1, businessDate: '2026-09-29', orderNumber: `MOCK-${i + 1}`, status, items: [{ code: `MOCK-CODE-${i}`, ordinal: 1 }], specRaw: '模拟规格' })), total: 4 }) if (pathname.endsWith('/apply-preview')) { const ids = route.request().postDataJSON().ids; previews.push(ids) if (mode === 'preview-error') return route.fulfill({ status: 500, json: { code: 'INTERNAL', message: '模拟预览失败' } }) return reply({ records: ids.length, inboundCodes: ids.length === 1 ? 3 : 2, blocked: mode === 'changed' && ids.length === 2 ? [{ id: 4, status: 'updated' }] : [] }) } if (pathname.endsWith('/apply')) { writes.push(route.request().postDataJSON().ids) return reply({ batchId: 'mock-batch', queued: writes.at(-1)!.length }) } if (pathname.includes('/apply-batches/')) return reply({ batch: { id: 'mock-batch', status: 'running', requested: writes.at(-1)!.length, processed: 0 } }) return reply({ items: [], total: 0 }) }) await page.goto('/#/syb-inner-codes') await expect(page.getByRole('cell', { name: 'MOCK-1', exact: true })).toBeVisible() await page.locator('.el-table__header-wrapper .el-checkbox').first().click() await page.getByRole('button', { name: '回写(2)', exact: true }).click() return { previews, writes, dialog: page.getByRole('dialog') } } test('mixed selection reports skipped items and submits only ready records', async ({ page, context }) => { const { previews, writes, dialog } = await setup(page, context) await expect(dialog.getByText('选中 4 条,可回写 2 条,跳过 2 条;只提交本次确认的可回写记录。')).toBeVisible() await expect(dialog.getByRole('button', { name: '回写 2 条', exact: true })).toBeEnabled() expect(previews).toEqual([[2, 4]]); expect(writes).toEqual([]) await dialog.getByRole('button', { name: '回写 2 条', exact: true }).click() await expect.poll(() => writes.length).toBe(1); expect(writes[0]).toEqual([2, 4]) await expect(dialog).not.toBeVisible() }) test('preview status changes update count and only submit remaining candidate', async ({ page, context }) => { const { previews, writes, dialog } = await setup(page, context, 'changed') await expect(dialog.getByText('选中 4 条,可回写 1 条,跳过 3 条;只提交本次确认的可回写记录。')).toBeVisible() await expect(dialog.getByRole('button', { name: '回写 1 条', exact: true })).toBeEnabled() expect(previews).toEqual([[2, 4], [2]]) await dialog.getByRole('button', { name: '回写 1 条', exact: true }).click() await expect.poll(() => writes.length).toBe(1); expect(writes[0]).toEqual([2]) }) test('preview failure is visible and cannot submit', async ({ page, context }) => { const { writes, dialog } = await setup(page, context, 'preview-error') await expect(dialog.locator('.el-alert--error')).toBeVisible() await expect(dialog.getByRole('button', { name: '回写 2 条', exact: true })).toBeDisabled() expect(writes).toEqual([]) })