126 lines
8.8 KiB
TypeScript
126 lines
8.8 KiB
TypeScript
import { expect, test } from '@playwright/test'
|
|
|
|
async function setup(page: any, replacing = false) {
|
|
const state = { userId: 1, role: 'purchaser', links: [] as any[], matches: [] as any[], linked: new Set<number>(), matched: new Set<number>(), aiFailure: false }
|
|
const replaced = new Set<number>()
|
|
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()), path = url.pathname
|
|
const ok = (data: any) => route.fulfill({ json: { code: 200, data } })
|
|
if (path.startsWith('/src/api/')) return route.continue()
|
|
if (path.endsWith('/getinfo')) return ok({ userId: state.userId, roles: [state.role], name: '合成测试', avatar: '', permissions: [] })
|
|
if (path.endsWith('/menurole')) return ok([{ path: '/workbench', component: 'Layout', menuName: 'Workbench', title: '工作台', visible: '0', children: [{ path: '/syb-products', component: '/goauto/syb-products/index', menuName: 'GoAutoSybProducts', title: 'SYB 商品', visible: '0' }] }])
|
|
if (path.endsWith('/syb-products')) return ok({ items: [1, 2].map(id => ({ id, orderCode: `TEST-${id}`, shopeeItemId: `100${id}`, shopeeProductId: id + 10, productTitle: '测试商品', targetColor: '黑色', targetSize: 'XL', quantity: 1, parseStatus: 'success' })), total: 2 })
|
|
if (path.endsWith('/devices')) return ok({ items: [{ id: 10, name: '手机A', status: 'offline' }, { id: 20, name: '手机B', status: 'online' }], total: 2 })
|
|
if (path.endsWith('/batch-preview')) return ok({ items: route.request().postDataJSON().sybProductIds.map((id: number) => replacing && !replaced.has(id)
|
|
? { sybProductId: id, pddProductId: 44, processStage: 'pdd_collection_failed', processNextAction: 'open_pdd' }
|
|
: { sybProductId: id, pddProductId: state.linked.has(id) ? 55 : null, eligible: state.matched.has(id), aiMatchEligible: state.linked.has(id) && !state.matched.has(id), processStage: state.matched.has(id) ? 'purchase_ready' : state.linked.has(id) ? 'color_mapping' : 'pdd_unlinked', processNextAction: state.linked.has(id) ? 'open_mapping' : 'open_pdd_link' }) })
|
|
const match = path.match(/shopee-products\/(\d+)(.*)/)
|
|
if (match) {
|
|
const productId = Number(match[1]), id = productId - 10
|
|
const product = { id: productId, pddProductId: state.linked.has(id) ? 55 : null, specContextVersion: state.linked.has(id) ? 'after' : 'before', specs: [] }
|
|
if (replacing && !replaced.has(id)) product.pddProductId = 44
|
|
if (match[2] === '/link-pdd') {
|
|
const body = route.request().postDataJSON()
|
|
if (body.latestCollection.replacement?.preview) return ok({ product, collectionSource: { taskId: 7, deviceId: 10, pddProductId: 55, targetVersion: 'frozen', title: '新测试商品', previousTitle: '原测试商品', deviceName: '手机A' } })
|
|
state.links.push(body); state.linked.add(id); replaced.add(id)
|
|
return ok({ product: { ...product, pddProductId: 55, specContextVersion: 'after' }, collectionSource: { taskId: 7, deviceId: body.latestCollection.deviceId, pddProductId: 55 } })
|
|
}
|
|
if (match[2] === '/specs/mapping/auto-match') {
|
|
state.matches.push(route.request().postDataJSON())
|
|
if (state.aiFailure) return route.fulfill({ status: 503, json: { code: 'AI_MATCHING_UNAVAILABLE', message: 'AI 匹配服务暂时不可用' } })
|
|
state.matched.add(id); return ok({ product, unmatchedCount: 0 })
|
|
}
|
|
return ok({ product, autoMatchTimeoutSeconds: 370 })
|
|
}
|
|
return ok([])
|
|
})
|
|
await page.goto('/#/syb-products')
|
|
await expect(page.getByRole('button', { name: replacing ? '一键替换' : '一键关联', exact: true })).toHaveCount(2)
|
|
return state
|
|
}
|
|
|
|
test('replacement reuses row position; cancel writes nothing; confirm freezes target and matches', async ({ page }, testInfo) => {
|
|
const state = await setup(page, true)
|
|
await expect(page.getByRole('button', { name: '查看 PDD 商品', exact: true })).toHaveCount(2)
|
|
await expect(page.getByRole('button', { name: '一键关联', exact: true })).toHaveCount(0)
|
|
await page.getByRole('button', { name: '一键替换', exact: true }).first().click()
|
|
await choose(page, '手机A', '选择并继续')
|
|
const confirm = page.getByRole('dialog', { name: '确认替换 PDD 商品' })
|
|
await expect(confirm).toContainText('共用此蝦皮商品')
|
|
await page.screenshot({ path: testInfo.outputPath('replacement-confirm.png'), animations: 'disabled' })
|
|
await confirm.getByRole('button', { name: '取消', exact: true }).click()
|
|
expect(state.links).toHaveLength(0); expect(state.matches).toHaveLength(0)
|
|
await page.getByRole('button', { name: '一键替换', exact: true }).first().click()
|
|
await confirm.getByRole('button', { name: '确认替换并匹配', exact: true }).click()
|
|
await expect.poll(() => state.matches.length).toBe(1)
|
|
expect(state.links).toHaveLength(1)
|
|
expect(state.links[0].latestCollection.replacement).toMatchObject({ expectedPddProductId: 44, taskId: 7, targetPddProductId: 55, targetVersion: 'frozen' })
|
|
await expect(page.locator('tbody').getByText('可创建采购', { exact: true })).toBeVisible()
|
|
await expect(confirm).toBeHidden()
|
|
})
|
|
|
|
async function choose(page: any, name: string, button: string) {
|
|
const dialog = page.getByRole('dialog', { name: /^(选择|切换)采集手机$/ })
|
|
await dialog.locator('.el-select').click()
|
|
await page.getByRole('option', { name: new RegExp(name) }).click()
|
|
await dialog.getByRole('button', { name: button, exact: true }).click()
|
|
await expect(dialog).toBeHidden()
|
|
}
|
|
|
|
test('first choose continues clicked row; saves match and real stage; preference survives refresh', async ({ page }) => {
|
|
const state = await setup(page)
|
|
await page.getByRole('button', { name: '一键关联', exact: true }).nth(1).click()
|
|
await choose(page, '手机A', '选择并继续')
|
|
await expect.poll(() => state.matches.length).toBe(1)
|
|
expect(state.links[0].latestCollection).toMatchObject({ sybProductId: 2, deviceId: 10 })
|
|
await expect(page.locator('tbody').getByText('可创建采购', { exact: true })).toBeVisible()
|
|
await page.reload()
|
|
await page.getByRole('button', { name: '一键关联', exact: true }).click()
|
|
await expect.poll(() => state.links.length).toBe(2)
|
|
expect(state.links[1].latestCollection.deviceId).toBe(10)
|
|
await expect(page.getByRole('dialog')).toHaveCount(0)
|
|
})
|
|
|
|
test('arrow first-use saves only; switch from either row affects all rows without linking', async ({ page }) => {
|
|
const state = await setup(page)
|
|
await page.getByRole('button', { name: '一键关联使用的手机' }).first().click()
|
|
await page.getByRole('menuitem', { name: '切换手机' }).click()
|
|
await choose(page, '手机A', '保存选择')
|
|
expect(state.links).toHaveLength(0)
|
|
await page.getByRole('button', { name: '一键关联使用的手机' }).nth(1).click()
|
|
await expect(page.getByRole('menuitem', { name: /当前手机:手机A/ })).toBeVisible()
|
|
await page.getByRole('menuitem', { name: '切换手机' }).click()
|
|
await choose(page, '手机B', '保存选择')
|
|
expect(state.links).toHaveLength(0)
|
|
await page.getByRole('button', { name: '一键关联', exact: true }).first().click()
|
|
await expect.poll(() => state.links.length).toBe(1)
|
|
expect(state.links[0].latestCollection.deviceId).toBe(20)
|
|
})
|
|
|
|
test('AI failure retains association, ends waiting and offers manual matching', async ({ page }) => {
|
|
const state = await setup(page); state.aiFailure = true
|
|
await page.getByRole('button', { name: '一键关联', exact: true }).first().click()
|
|
await choose(page, '手机A', '选择并继续')
|
|
await expect(page.getByText(/当前已有关联,匹配结果请去匹配查看/)).toBeVisible()
|
|
await expect(page.getByRole('button', { name: '去匹配', exact: true })).toBeVisible()
|
|
expect(state.links).toHaveLength(1); expect(state.matches).toHaveLength(1)
|
|
await expect(page.getByRole('button', { name: '一键关联', exact: true })).toBeEnabled()
|
|
})
|
|
|
|
test('cancel, different user and no purchaser role do not create links', async ({ page }) => {
|
|
const state = await setup(page)
|
|
await page.getByRole('button', { name: '一键关联', exact: true }).first().click()
|
|
await page.getByRole('dialog').getByRole('button', { name: '取消', exact: true }).click()
|
|
expect(state.links).toHaveLength(0)
|
|
await page.getByRole('button', { name: '一键关联使用的手机' }).first().click()
|
|
await page.getByRole('menuitem', { name: '切换手机' }).click()
|
|
await choose(page, '手机A', '保存选择')
|
|
state.userId = 2; await page.reload()
|
|
await page.getByRole('button', { name: '一键关联', exact: true }).first().click()
|
|
await expect(page.getByRole('dialog', { name: '选择采集手机' })).toBeVisible()
|
|
state.role = 'viewer'; await page.reload()
|
|
await expect(page.getByRole('button', { name: '一键关联', exact: true })).toHaveCount(0)
|
|
expect(state.links).toHaveLength(0)
|
|
})
|