import { expect, test } from '@playwright/test'; async function authenticate(context: any) { await context.addCookies([{ name: 'Admin-Token', value: 'prototype-test-token', domain: 'localhost', path: '/' }]); } const syncMenu = [{ path: '/syb-sync-runs', component: 'Layout', visible: '0', menuName: 'SybSync', title: 'SYB 同步', children: [{ path: 'index', component: '/goauto/syb-sync-runs/index', visible: '0', menuName: 'GoAutoSybSyncRuns', title: 'SYB 同步记录' }] }]; test('部分成功可筛选并查看已保存数量和补齐提示', async ({ page, context }) => { await authenticate(context); const item = { id: 239, status: 'partial_success', dateFrom: '2026-08-01', dateTo: '2026-08-02', daysProcessed: 1, daysTotal: 2, progressPercent: 50, created: 2, updated: 1, orderCount: 3, detailCount: 3, acceptedCount: 3, shopSkipped: 0, errorMessage: '2026-08-01 第 2 页读取失败', shopBreakdown: [] }; let selected = ''; await page.route('**/api/**', route => { const url = new URL(route.request().url()); if (url.pathname.startsWith('/src/api/')) return route.continue(); if (url.pathname.endsWith('/getinfo')) return route.fulfill({ json: { code: 200, data: { roles: ['purchaser'], name: '测试用户', avatar: '', introduction: '', permissions: [] } } }); if (url.pathname.endsWith('/sync-runs/239')) return route.fulfill({ json: { code: 200, data: { item } } }); if (url.pathname.endsWith('/sync-runs')) { selected = url.searchParams.get('status') || ''; return route.fulfill({ json: { code: 200, data: { items: [item], total: 1 } } }); } return route.fulfill({ json: { code: 200, data: url.pathname.endsWith('/menurole') ? syncMenu : [] } }); }); await page.goto('/#/syb-sync-runs/index'); await expect(page.locator('.el-table').getByText('部分成功')).toBeVisible(); await page.locator('.search-form .el-select').click(); await page.getByRole('option', { name: '部分成功' }).click(); await page.getByRole('button', { name: '查询', exact: true }).click(); await expect.poll(() => selected).toBe('partial_success'); await page.getByRole('button', { name: '详情', exact: true }).click(); await expect(page.getByText(item.errorMessage)).toBeVisible(); await expect(page.getByText('已保存的数据会保留。', { exact: false })).toBeVisible(); await expect(page.locator('.el-drawer').getByText('2 / 1', { exact: true })).toBeVisible(); await expect.poll(async () => { const box = await page.locator('.el-drawer').boundingBox(); return box ? Math.round(box.x + box.width) : 0; }).toBe(page.viewportSize()!.width); }); test('同步记录展示失败原因和按店铺统计', async ({ page, context }) => { await authenticate(context); await page.route('**/api/**', async route => { const url = new URL(route.request().url()); if (url.pathname.startsWith('/src/api/')) return route.continue(); if (url.pathname.endsWith('/api/v1/getinfo')) return route.fulfill({ json: { code: 200, data: { roles: ['purchaser'], name: '采购员', avatar: '', introduction: '', permissions: [] } } }); if (url.pathname.endsWith('/api/admin/v1/syb-products/sync-runs/51')) return route.fulfill({ json: { code: 200, data: { item: { id: 51, dateFrom: '2026-08-18', dateTo: '2026-08-20', status: 'failed', daysProcessed: 2, daysTotal: 3, progressPercent: 66, orderCount: 12, detailCount: 8, acceptedCount: 10, shopSkipped: 2, created: 5, updated: 3, operatorName: '系统定时同步', startedAt: '2026-08-20T01:00:00Z', finishedAt: '2026-08-20T01:05:00Z', errorMessage: '第 3 天读取失败,等待下个调度周期重试', shopFilterHash: 'abc123', shopBreakdown: [{ shopName: '大碼臻選', accepted: 10, skipped: 0 }, { shopName: '未知店铺', accepted: 0, skipped: 2 }] } } } }); if (url.pathname.endsWith('/api/admin/v1/syb-products/sync-runs')) return route.fulfill({ json: { code: 200, data: { items: [{ id: 51, dateFrom: '2026-08-18', dateTo: '2026-08-20', status: 'failed', daysProcessed: 2, daysTotal: 3, orderCount: 12, detailCount: 8, created: 5, updated: 3, operatorName: '系统定时同步', startedAt: '2026-08-20T01:00:00Z' }], total: 1, page: 1, pageSize: 20 } } }); return route.fulfill({ json: { code: 200, data: url.pathname.endsWith('/api/v1/menurole') ? syncMenu : [] } }); }); await page.goto('/#/syb-sync-runs/index'); await expect(page.getByRole('heading', { name: 'SYB 同步记录' })).toBeVisible(); await expect(page.getByRole('button', { name: '立即同步' })).toBeVisible(); await expect(page.getByText('2026-08-18 至 2026-08-20')).toBeVisible(); await page.getByRole('button', { name: '详情' }).click(); await expect(page.getByText('第 3 天读取失败,等待下个调度周期重试')).toBeVisible(); await expect(page.getByText('大碼臻選')).toBeVisible(); await expect(page.getByText('未知店铺')).toBeVisible(); await expect(page.getByText('系统定时同步', { exact: true }).first()).toBeVisible(); }); for (const role of ['admin', 'purchaser']) { test(`${role} 可立即同步今天和昨天,受理后按钮进入运行中状态`, async ({ page, context }) => { await authenticate(context); let importBody: any = null; let importAccepted = false; await page.route('**/api/**', async route => { const url = new URL(route.request().url()); if (url.pathname.startsWith('/src/api/')) return route.continue(); if (url.pathname.endsWith('/api/v1/getinfo')) return route.fulfill({ json: { code: 200, data: { roles: [role], name: role, avatar: '', introduction: '', permissions: [] } } }); if (url.pathname.endsWith('/api/admin/v1/syb-products/import') && route.request().method() === 'POST') { importBody = route.request().postDataJSON(); await new Promise(resolve => setTimeout(resolve, 200)); importAccepted = true; return route.fulfill({ status: 202, json: { code: 200, data: { runId: 88, status: 'running' } } }); } if (url.pathname.endsWith('/api/admin/v1/syb-products/sync-runs')) { const items = importAccepted ? [{ id: 88, dateFrom: importBody.dateFrom, dateTo: importBody.dateTo, status: 'running', progressPercent: 0, daysProcessed: 0, daysTotal: 2, orderCount: 0, detailCount: 0, created: 0, updated: 0, operatorName: '管理员', startedAt: '2026-08-24T08:00:00Z' }] : []; return route.fulfill({ json: { code: 200, data: { items, total: items.length, page: 1, pageSize: 20 } } }); } return route.fulfill({ json: { code: 200, data: url.pathname.endsWith('/api/v1/menurole') ? syncMenu : [] } }); }); await page.goto('/#/syb-sync-runs/index'); const button = page.getByRole('button', { name: '立即同步' }); await expect(button).toBeVisible(); await button.click(); await expect(button).toBeDisabled(); await expect(page.getByText('已开始同步今天和昨天')).toBeVisible(); await expect(page.getByRole('button', { name: '同步进行中' })).toBeDisabled(); const now = new Date(); const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1); const formatDate = (value: Date) => `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, '0')}-${String(value.getDate()).padStart(2, '0')}`; expect(importBody).toEqual({ dateFrom: formatDate(yesterday), dateTo: formatDate(now) }); }); } for (const role of ['purchaser', 'viewer']) { test(`${role} 同步权限及失败恢复`, async ({ page, context }) => { await authenticate(context); let requests = 0; await page.route('**/api/**', async route => { const url = new URL(route.request().url()); if (url.pathname.startsWith('/src/api/')) return route.continue(); if (url.pathname.endsWith('/api/v1/getinfo')) return route.fulfill({ json: { code: 200, data: { roles: [role], name: role, avatar: '', permissions: [] } } }); if (url.pathname.endsWith('/api/admin/v1/syb-products/import')) { requests++; return route.fulfill({ status: 422, json: { code: 'INVALID_REQUEST', message: '已有同步正在执行,请稍后再试' } }); } if (url.pathname.endsWith('/api/admin/v1/syb-products/sync-runs')) return route.fulfill({ json: { code: 200, data: { items: [], total: 0 } } }); return route.fulfill({ json: { code: 200, data: url.pathname.endsWith('/api/v1/menurole') ? syncMenu : [] } }); }); await page.goto('/#/syb-sync-runs/index'); await expect(page.getByRole('heading', { name: 'SYB 同步记录' })).toBeVisible(); const button = page.getByRole('button', { name: '立即同步' }); if (role === 'viewer') { await expect(button).toHaveCount(0); expect(requests).toBe(0); } else { await button.click(); await expect(page.getByText('已有同步正在执行,请稍后再试')).toBeVisible(); await expect(button).toBeEnabled(); expect(requests).toBe(1); } }); } test('SYB 定时任务可进入执行记录,其他任务不显示该入口', async ({ page, context }) => { await authenticate(context); await page.route('**/api/**', async route => { const url = new URL(route.request().url()); if (url.pathname.startsWith('/src/api/')) return route.continue(); if (url.pathname.endsWith('/api/v1/getinfo')) return route.fulfill({ json: { code: 200, data: { roles: ['admin'], name: '管理员', avatar: '', introduction: '', permissions: [] } } }); if (url.pathname.endsWith('/api/v1/menurole')) return route.fulfill({ json: { code: 200, data: [{ path: '/schedule', component: 'Layout', visible: '0', menuName: 'Schedule', title: '定时任务', icon: 'time', children: [{ path: 'index', component: '/schedule/index', visible: '0', menuName: 'SysJobManage', title: '定时任务', icon: 'time' }] }] } }); if (url.pathname.endsWith('/api/v1/sysjob')) return route.fulfill({ json: { code: 200, data: { list: [{ jobId: 1, jobName: 'SYB 每小时自动同步', jobGroup: 'GoAuto', cronExpression: '0 5 * * * *', invokeTarget: 'GoAutoSYBHourlySync', status: 2, entry_id: 10 }, { jobId: 2, jobName: '其他任务', jobGroup: 'DEFAULT', cronExpression: '0 0 * * * *', invokeTarget: 'OtherJob', status: 2, entry_id: 11 }], count: 2 } } }); if (url.pathname.endsWith('/api/admin/v1/syb-products/sync-runs')) return route.fulfill({ json: { code: 200, data: { items: [], total: 0, page: 1, pageSize: 20 } } }); return route.fulfill({ json: { code: 200, data: [] } }); }); await page.goto('/#/schedule/index'); await expect(page.getByText('SYB 每小时自动同步', { exact: true })).toBeVisible(); await expect(page.getByRole('button', { name: '执行记录', exact: true })).toHaveCount(1); await page.getByRole('button', { name: '执行记录', exact: true }).click(); await expect(page).toHaveURL(/#\/syb-sync-runs\/index$/); await expect(page.getByRole('heading', { name: 'SYB 同步记录' })).toBeVisible(); });