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 lists: URL[] = []; const writes: number[][] = []; const otherWrites: string[] = [] let submitted = false 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') { lists.push(url) if (mode === 'list-error') return route.fulfill({ status: 500, json: { code: 'INTERNAL', message: '模拟加载失败' } }) const statuses = ['pending', 'failed', 'skipped', 'updated'] return reply({ items: statuses.map((status, i) => ({ id: i + 1, businessDate: mode === 'mixed-dates' && i === 1 ? '2026-09-28' : '2026-09-29', orderNumber: `MOCK-${i + 1}`, status: submitted && i < 3 ? 'matching' : status, items: [{ code: `MOCK-CODE-${i}`, ordinal: 1 }], specRaw: '模拟规格' })), total: 4 }) } if (pathname.endsWith('/rematch')) { writes.push(route.request().postDataJSON().ids) if (mode === 'submit-error') return route.fulfill({ status: 409, json: { code: 'CONFLICT', message: '模拟状态已变化' } }) submitted = true return reply({ matchJobId: 'isolated-job', queued: writes.at(-1)!.length }) } if (pathname.includes('/match-jobs/')) return reply({ item: { id: 'isolated-job', status: 'running', total: 3, processed: 1, ready: 1, failed: 0 } }) if (route.request().method() !== 'GET') otherWrites.push(pathname) return reply({ items: [], total: 0 }) }) await page.goto('/#/syb-inner-codes') if (mode !== 'list-error') await expect(page.getByRole('cell', { name: 'MOCK-1', exact: true })).toBeVisible() return { lists, writes, otherWrites } } test('today default, mixed selection only matches eligible rows and never writes SYB', async ({ page, context }) => { const { lists, writes, otherWrites } = await setup(page, context) const today = await page.evaluate(() => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}` }) expect(lists[0].searchParams.get('dateFrom')).toBe(today); expect(lists[0].searchParams.get('dateTo')).toBe(today) await expect(page.getByRole('button', { name: '匹配 0', exact: true })).toBeDisabled() await page.locator('.el-table__header-wrapper .el-checkbox').first().click() await page.getByRole('button', { name: '匹配 3', exact: true }).click() await expect.poll(() => writes.length).toBe(1); expect(writes[0]).toEqual([1, 2, 3]) await expect(page.getByText('自动匹配:1/3,可回写 1,异常 0')).toBeVisible() await expect(page.getByRole('button', { name: /^匹配 \d+$/ })).toBeDisabled() expect(otherWrites).toEqual([]) }) test('explicitly clear filters removes date range', async ({ page, context }) => { const { lists } = await setup(page, context) await page.getByRole('button', { name: '清除', exact: true }).click() await expect.poll(() => lists.length).toBeGreaterThan(1) expect(lists.at(-1)!.searchParams.get('dateFrom')).toBe('') }) test('mixed dates warn without submitting', async ({ page, context }) => { const { writes } = await setup(page, context, 'mixed-dates') await page.locator('.el-table__header-wrapper .el-checkbox').first().click() await page.getByRole('button', { name: '匹配 3', exact: true }).click() await expect(page.getByText('请按同一营业日期分别匹配')).toBeVisible(); expect(writes).toEqual([]) }) test('submission failure keeps retry available without automatic retries', async ({ page, context }) => { const { writes } = await setup(page, context, 'submit-error') await page.locator('.el-table__header-wrapper .el-checkbox').first().click() await page.getByRole('button', { name: '匹配 3', exact: true }).click() await expect(page.getByRole('button', { name: '匹配 3', exact: true })).toBeEnabled() expect(writes.length).toBe(1) }) test('list failure disables matching', async ({ page, context }) => { await setup(page, context, 'list-error') await expect(page.getByRole('button', { name: '匹配 0', exact: true })).toBeDisabled() })