109 lines
7.7 KiB
TypeScript
109 lines
7.7 KiB
TypeScript
import { expect, test, type Page, type BrowserContext } from '@playwright/test'
|
|
|
|
const menus = [{ 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 setup(page: Page, context: BrowserContext, mode = 'success', role = 'after_sales') {
|
|
await page.setViewportSize({ width: 1920, height: 1080 })
|
|
await context.addCookies([{ name: 'Admin-Token', value: 'isolated-test-token', domain: 'localhost', path: '/' }])
|
|
const writes: number[] = []
|
|
const cancelled = new Set<number>()
|
|
const lists: URL[] = []
|
|
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(menus)
|
|
if (pathname.endsWith('/api/v1/getinfo')) return reply({ roles: [role], name: '模拟售后', avatar: '', permissions: [] })
|
|
if (pathname.endsWith('/api/admin/v1/syb-products')) {
|
|
lists.push(url)
|
|
const items = [1, 2, 3, 4].filter(id => url.searchParams.get('processStage') !== 'return_pending' || (id <= 2 && !cancelled.has(id))).map(id => ({ id, orderCode: `MOCK-${id}`, shopeeItemId: String(100 + id), shopeeProductId: 100 + id, imageUrl: '', productTitle: '模拟商品', quantity: 1, parseStatus: 'success' }))
|
|
return reply({ items, total: items.length })
|
|
}
|
|
if (pathname.endsWith('/purchase-tasks/batch-preview')) return reply({ items: [1, 2, 3, 4].map(id => ({ sybProductId: id, processStage: cancelled.has(id) ? 'pdd_unlinked' : id <= 2 ? 'return_pending' : id === 3 ? 'return_used' : 'purchase_ready', processStageLabel: cancelled.has(id) ? '未关联 PDD' : id <= 2 ? '退货待确认' : id === 3 ? '已用退货' : '可创建采购', eligible: id === 4, collectionEligible: false, aiMatchEligible: false })) })
|
|
if (pathname.endsWith('/return-matches')) {
|
|
if (mode === 'metadata-error') return route.fulfill({ status: 500, json: { code: 'INTERNAL' } })
|
|
return reply({ items: [1, 2, 3].filter(id => !cancelled.has(id)).map(id => ({ id: id + 10, sybProductId: id, status: id <= 2 ? 'matched' : 'confirmed', variationName: '模拟规格' })) })
|
|
}
|
|
if (/\/return-matches\/\d+\/cancel$/.test(pathname)) {
|
|
const id = Number(pathname.split('/').at(-2)); writes.push(id)
|
|
expect(route.request().postDataJSON()).toEqual({ expectedStatus: 'matched' })
|
|
if (mode === 'partial' && id === 12) return route.fulfill({ status: 500, json: { code: 'INTERNAL', message: '模拟取消失败' } })
|
|
if (mode === 'auth') return route.fulfill({ status: 401, json: { code: 401, message: '模拟登录失效' } })
|
|
if (mode === 'conflict' && id === 12) return route.fulfill({ status: 409, json: { code: 'STATE_CONFLICT' } })
|
|
await new Promise(resolve => setTimeout(resolve, 150))
|
|
cancelled.add(id - 10); return reply({ item: { id, status: 'cancelled' } })
|
|
}
|
|
if (/\/return-matches\/\d+$/.test(pathname)) {
|
|
const id = Number(pathname.split('/').at(-1)); return reply({ match: { id, sybProductId: id - 10, status: 'matched' }, logs: [] })
|
|
}
|
|
return reply({ items: [], total: 0 })
|
|
})
|
|
await page.goto('/#/syb-products')
|
|
await expect(page.getByRole('cell', { name: 'MOCK-1', exact: true })).toBeVisible()
|
|
return { writes, lists }
|
|
}
|
|
|
|
test('one confirmation, mixed selection, progress, success and filter retention', async ({ page, context }) => {
|
|
const { writes, lists } = await setup(page, context)
|
|
const cancel = page.getByRole('button', { name: /取消匹配\s*0/ }).first()
|
|
await expect(cancel).toBeDisabled()
|
|
await page.locator('thead .el-checkbox').first().click()
|
|
await expect(page.getByRole('button', { name: /创建采购\s*1/ })).toBeEnabled()
|
|
await page.getByRole('button', { name: /取消匹配\s*2/ }).click()
|
|
await expect(page.getByText('取消选中的 2 条退货匹配?取消后商品将重新计算采购准备状态。', { exact: true })).toBeVisible()
|
|
await page.getByRole('button', { name: '返回', exact: true }).click()
|
|
expect(writes).toEqual([])
|
|
await page.getByRole('button', { name: /取消匹配\s*2/ }).click()
|
|
await page.getByRole('button', { name: '确认取消匹配', exact: true }).click()
|
|
await expect(page.getByRole('button', { name: '返回列表', exact: true })).toBeDisabled()
|
|
await expect(page.getByText('成功 2 条', { exact: true })).toBeVisible()
|
|
await expect(page.getByText('跳过 1 条', { exact: true })).toBeVisible()
|
|
await expect(page.getByRole('button', { name: '返回列表', exact: true })).toBeEnabled()
|
|
expect(writes).toEqual([11, 12])
|
|
expect(lists.at(-1)?.searchParams.get('pageSize')).toBe('200')
|
|
await page.getByRole('button', { name: '返回列表', exact: true }).click()
|
|
await expect(page.getByText('本页选择 0 条', { exact: true })).toBeVisible()
|
|
})
|
|
|
|
for (const mode of ['partial', 'conflict', 'auth']) {
|
|
test(`${mode}: per-row outcome, failed selection and stop on login failure`, async ({ page, context }) => {
|
|
const { writes } = await setup(page, context, mode)
|
|
await page.locator('thead .el-checkbox').first().click()
|
|
await page.getByRole('button', { name: /取消匹配\s*2/ }).click()
|
|
await page.getByRole('button', { name: '确认取消匹配', exact: true }).click()
|
|
await expect(page.getByRole('button', { name: '返回列表', exact: true })).toBeEnabled()
|
|
expect(writes).toEqual(mode === 'auth' ? [11] : [11, 12])
|
|
await expect(page.getByText(mode === 'auth' ? '失败 2 条' : mode === 'partial' ? '失败 1 条' : '跳过 2 条', { exact: true })).toBeVisible()
|
|
await page.getByRole('button', { name: '返回列表', exact: true }).click()
|
|
if (mode === 'partial') {
|
|
await expect(page.getByText('本页选择 1 条', { exact: true })).toBeVisible()
|
|
await expect(page.getByRole('button', { name: /取消匹配\s*1/ }).first()).toBeEnabled()
|
|
}
|
|
})
|
|
}
|
|
|
|
test('match load failure disables bulk cancellation', async ({ page, context }) => {
|
|
await setup(page, context, 'metadata-error')
|
|
await page.locator('thead .el-checkbox').first().click()
|
|
await expect(page.getByRole('button', { name: /取消匹配\s*0/ }).first()).toBeDisabled()
|
|
})
|
|
test('unprivileged role cannot see cancellation or selection', async ({ page, context }) => {
|
|
await setup(page, context, 'success', 'viewer')
|
|
await expect(page.getByRole('button', { name: /取消匹配/ })).toHaveCount(0)
|
|
await expect(page.locator('thead .el-checkbox')).toHaveCount(0)
|
|
})
|
|
|
|
test('return-pending filter retained; successful rows leave current page', async ({ page, context }) => {
|
|
const { lists } = await setup(page, context)
|
|
await page.locator('.el-form-item').filter({ hasText: '处理阶段' }).locator('.el-select__wrapper').click()
|
|
await page.getByRole('option', { name: '退货待确认', exact: true }).click()
|
|
await page.getByRole('button', { name: '查询', exact: true }).click()
|
|
await expect(page.getByRole('cell', { name: 'MOCK-4', exact: true })).toHaveCount(0)
|
|
await page.locator('thead .el-checkbox').first().click()
|
|
await page.getByRole('button', { name: /取消匹配\s*2/ }).click()
|
|
await page.getByRole('button', { name: '确认取消匹配', exact: true }).click()
|
|
await expect(page.getByRole('button', { name: '返回列表', exact: true })).toBeEnabled()
|
|
expect(lists.at(-1)?.searchParams.get('processStage')).toBe('return_pending')
|
|
await page.getByRole('button', { name: '返回列表', exact: true }).click()
|
|
await expect(page.getByText('暂无 SYB 商品明细', { exact: true })).toBeVisible()
|
|
})
|