|
|
@ -171,10 +171,25 @@ async function setupApiMocks(page) { |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
await page.route('**/api/mall/orders*', (route) => { |
|
|
await page.route('**/api/mall/orders*', (route) => { |
|
|
|
|
|
const url = new URL(route.request().url()); |
|
|
|
|
|
const status = url.searchParams.get('status'); |
|
|
|
|
|
const statusTotalMap = { |
|
|
|
|
|
pending: 3, |
|
|
|
|
|
paid: 2, |
|
|
|
|
|
shipped: 1, |
|
|
|
|
|
completed: 5 |
|
|
|
|
|
}; |
|
|
|
|
|
const total = status ? (statusTotalMap[status] || 0) : 0; |
|
|
route.fulfill({ |
|
|
route.fulfill({ |
|
|
status: 200, |
|
|
status: 200, |
|
|
contentType: 'application/json', |
|
|
contentType: 'application/json', |
|
|
body: JSON.stringify({ orders: [], total: 0, page: 1, page_size: 20 }) |
|
|
body: JSON.stringify({ |
|
|
|
|
|
orders: [], |
|
|
|
|
|
page_info: { total, page: 1, page_size: 20 }, |
|
|
|
|
|
total, |
|
|
|
|
|
page: 1, |
|
|
|
|
|
page_size: 20 |
|
|
|
|
|
}) |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
@ -191,10 +206,30 @@ async function setupApiMocks(page) { |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
await page.route('**/api/mall/member/points/records*', (route) => { |
|
|
await page.route('**/api/mall/member/points/records*', (route) => { |
|
|
|
|
|
const url = new URL(route.request().url()); |
|
|
|
|
|
const pageNo = Number(url.searchParams.get('page') || '1'); |
|
|
|
|
|
const pageSize = Number(url.searchParams.get('page_size') || '10'); |
|
|
|
|
|
const all = Array.from({ length: 12 }).map((_, idx) => ({ |
|
|
|
|
|
id: idx + 1, |
|
|
|
|
|
type: idx % 2 === 0 ? 'earn' : 'order', |
|
|
|
|
|
points: idx % 2 === 0 ? 10 : -5, |
|
|
|
|
|
balance: 100 + idx, |
|
|
|
|
|
source: 'test', |
|
|
|
|
|
remark: `积分记录${idx + 1}`, |
|
|
|
|
|
created_at: '2026-02-01 10:00:00' |
|
|
|
|
|
})); |
|
|
|
|
|
const start = (pageNo - 1) * pageSize; |
|
|
|
|
|
const records = all.slice(start, start + pageSize); |
|
|
route.fulfill({ |
|
|
route.fulfill({ |
|
|
status: 200, |
|
|
status: 200, |
|
|
contentType: 'application/json', |
|
|
contentType: 'application/json', |
|
|
body: JSON.stringify({ records: [], total: 0, page: 1, page_size: 20 }) |
|
|
body: JSON.stringify({ |
|
|
|
|
|
records, |
|
|
|
|
|
page_info: { total: all.length, page: pageNo, page_size: pageSize }, |
|
|
|
|
|
total: all.length, |
|
|
|
|
|
page: pageNo, |
|
|
|
|
|
page_size: pageSize |
|
|
|
|
|
}) |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
@ -353,13 +388,13 @@ async function testHeaderComponents(page) { |
|
|
await page.goto(MALL_URL, { waitUntil: 'networkidle', timeout: 10000 }).catch(() => {}); |
|
|
await page.goto(MALL_URL, { waitUntil: 'networkidle', timeout: 10000 }).catch(() => {}); |
|
|
await page.waitForTimeout(1000); |
|
|
await page.waitForTimeout(1000); |
|
|
|
|
|
|
|
|
// 检查 header 购物车图标
|
|
|
// 检查 header 购物车图标(角标为空时不一定存在 .cart-badge)
|
|
|
const headerCartBadge = page.locator('.cart-badge'); |
|
|
const headerCartIcon = page.locator('.header-right .header-icon').nth(1); |
|
|
const cartBadgeVisible = await headerCartBadge.isVisible().catch(() => false); |
|
|
const cartIconVisible = await headerCartIcon.isVisible().catch(() => false); |
|
|
logTest('Header购物车图标', cartBadgeVisible); |
|
|
logTest('Header购物车图标', cartIconVisible); |
|
|
|
|
|
|
|
|
// 点击购物车图标应跳转到购物车
|
|
|
// 点击购物车图标应跳转到购物车
|
|
|
await headerCartBadge.click(); |
|
|
await headerCartIcon.click(); |
|
|
await page.waitForTimeout(1000); |
|
|
await page.waitForTimeout(1000); |
|
|
const urlAfterCart = page.url(); |
|
|
const urlAfterCart = page.url(); |
|
|
logTest('Header购物车跳转', urlAfterCart.includes('/cart'), `URL: ${urlAfterCart}`); |
|
|
logTest('Header购物车跳转', urlAfterCart.includes('/cart'), `URL: ${urlAfterCart}`); |
|
|
@ -432,6 +467,134 @@ async function testOrdersPage(page) { |
|
|
logTest('订单页返回按钮', backVisible); |
|
|
logTest('订单页返回按钮', backVisible); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 测试 6B:我的页面增强功能(订单角标、积分分页、退出弹窗) |
|
|
|
|
|
*/ |
|
|
|
|
|
async function testMemberEnhancements(page) { |
|
|
|
|
|
console.log('\n【步骤6B】测试我的页面增强功能...'); |
|
|
|
|
|
await page.goto(MALL_URL + '/member', { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); |
|
|
|
|
|
await page.waitForTimeout(1500); |
|
|
|
|
|
|
|
|
|
|
|
// 订单状态角标(来自 /orders?status=xxx 统计)
|
|
|
|
|
|
const badgeCount = await page.locator('.order-tab-badge').count(); |
|
|
|
|
|
logTest('我的页订单状态角标', badgeCount > 0, `角标数量: ${badgeCount}`); |
|
|
|
|
|
|
|
|
|
|
|
// 积分明细分页加载(第2页追加)
|
|
|
|
|
|
await page.getByText('积分明细').first().click().catch(() => {}); |
|
|
|
|
|
await page.waitForTimeout(1200); |
|
|
|
|
|
const beforeCount = await page.locator('.points-record').count(); |
|
|
|
|
|
const loadMoreBtn = page.getByRole('button', { name: '加载更多' }); |
|
|
|
|
|
const canLoadMore = await loadMoreBtn.isVisible().catch(() => false); |
|
|
|
|
|
if (canLoadMore) { |
|
|
|
|
|
await loadMoreBtn.click(); |
|
|
|
|
|
await page.waitForTimeout(1200); |
|
|
|
|
|
} |
|
|
|
|
|
const afterCount = await page.locator('.points-record').count(); |
|
|
|
|
|
logTest('积分明细加载更多追加', afterCount > beforeCount, `之前: ${beforeCount}, 现在: ${afterCount}`); |
|
|
|
|
|
|
|
|
|
|
|
// 关闭积分抽屉,避免遮罩影响后续点击
|
|
|
|
|
|
await page.keyboard.press('Escape').catch(() => {}); |
|
|
|
|
|
await page.waitForTimeout(300); |
|
|
|
|
|
|
|
|
|
|
|
// 退出登录入口:出现确认弹窗(取消即可,不影响后续)
|
|
|
|
|
|
await page.getByText('退出登录').first().click().catch(() => {}); |
|
|
|
|
|
await page.waitForTimeout(500); |
|
|
|
|
|
const logoutDialog = page.locator('.el-message-box'); |
|
|
|
|
|
const logoutDialogVisible = await logoutDialog.isVisible().catch(() => false); |
|
|
|
|
|
logTest('退出登录确认弹窗', logoutDialogVisible); |
|
|
|
|
|
if (logoutDialogVisible) { |
|
|
|
|
|
await page.getByRole('button', { name: '取消' }).click().catch(() => {}); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 测试 6C:购物车增强功能(删除已选、清空购物车) |
|
|
|
|
|
*/ |
|
|
|
|
|
async function testCartEnhancements(browser) { |
|
|
|
|
|
console.log('\n【步骤6C】测试购物车增强功能...'); |
|
|
|
|
|
const context = await browser.newContext({ |
|
|
|
|
|
viewport: { width: 750, height: 1334 }, |
|
|
|
|
|
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15' |
|
|
|
|
|
}); |
|
|
|
|
|
const page = await context.newPage(); |
|
|
|
|
|
|
|
|
|
|
|
await setupAuth(context, page); |
|
|
|
|
|
await setupApiMocks(page); |
|
|
|
|
|
|
|
|
|
|
|
const state = { |
|
|
|
|
|
items: [ |
|
|
|
|
|
{ id: 1, product_id: 1, sku_id: 0, product_name: '枸杞红枣养生茶', sku_name: '', image: '', price: 49.9, quantity: 1, selected: true, stock: 20 }, |
|
|
|
|
|
{ id: 2, product_id: 2, sku_id: 0, product_name: '黄芪片', sku_name: '', image: '', price: 39.9, quantity: 2, selected: false, stock: 20 } |
|
|
|
|
|
] |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 覆盖购物车相关 mock,模拟真实增删清流程
|
|
|
|
|
|
await page.route('**/api/mall/cart/clear', async (route) => { |
|
|
|
|
|
state.items = []; |
|
|
|
|
|
await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({}) }); |
|
|
|
|
|
}); |
|
|
|
|
|
await page.route('**/api/mall/cart/*', async (route) => { |
|
|
|
|
|
const req = route.request(); |
|
|
|
|
|
const url = new URL(req.url()); |
|
|
|
|
|
if (url.pathname.endsWith('/cart/clear') && req.method() === 'DELETE') { |
|
|
|
|
|
state.items = []; |
|
|
|
|
|
await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({}) }); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
const id = Number(url.pathname.split('/').pop()); |
|
|
|
|
|
if (req.method() === 'DELETE') { |
|
|
|
|
|
state.items = state.items.filter((i) => i.id !== id); |
|
|
|
|
|
await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({}) }); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
if (req.method() === 'PUT') { |
|
|
|
|
|
const body = req.postDataJSON(); |
|
|
|
|
|
state.items = state.items.map((i) => (i.id === id ? { ...i, ...body } : i)); |
|
|
|
|
|
await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({}) }); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
await route.continue(); |
|
|
|
|
|
}); |
|
|
|
|
|
await page.route('**/api/mall/cart', async (route) => { |
|
|
|
|
|
const selectedItems = state.items.filter((i) => i.selected); |
|
|
|
|
|
const totalAmount = selectedItems.reduce((sum, i) => sum + i.price * i.quantity, 0); |
|
|
|
|
|
await route.fulfill({ |
|
|
|
|
|
status: 200, |
|
|
|
|
|
contentType: 'application/json', |
|
|
|
|
|
body: JSON.stringify({ |
|
|
|
|
|
items: state.items, |
|
|
|
|
|
total_count: state.items.length, |
|
|
|
|
|
selected_count: selectedItems.length, |
|
|
|
|
|
total_amount: Number(totalAmount.toFixed(2)) |
|
|
|
|
|
}) |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
await page.goto(MALL_URL + '/cart', { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); |
|
|
|
|
|
await page.waitForTimeout(1200); |
|
|
|
|
|
|
|
|
|
|
|
const deleteSelectedBtn = page.getByRole('button', { name: '删除已选' }); |
|
|
|
|
|
const clearCartBtn = page.getByRole('button', { name: '清空购物车' }); |
|
|
|
|
|
logTest('购物车增强-删除已选按钮', await deleteSelectedBtn.isVisible().catch(() => false)); |
|
|
|
|
|
logTest('购物车增强-清空购物车按钮', await clearCartBtn.isVisible().catch(() => false)); |
|
|
|
|
|
|
|
|
|
|
|
await deleteSelectedBtn.click(); |
|
|
|
|
|
await page.locator('.el-message-box__btns .el-button--primary').first().click().catch(() => {}); |
|
|
|
|
|
await page.waitForTimeout(1000); |
|
|
|
|
|
const itemCountAfterDelete = await page.locator('.cart-item').count(); |
|
|
|
|
|
logTest('购物车增强-删除已选生效', itemCountAfterDelete === 1, `剩余: ${itemCountAfterDelete}`); |
|
|
|
|
|
|
|
|
|
|
|
await clearCartBtn.click(); |
|
|
|
|
|
await page.locator('.el-message-box__btns .el-button--primary').first().click().catch(() => {}); |
|
|
|
|
|
await page.waitForTimeout(1000); |
|
|
|
|
|
const emptyVisible = await page.locator('.cart-empty').isVisible().catch(() => false); |
|
|
|
|
|
logTest('购物车增强-清空购物车生效', emptyVisible); |
|
|
|
|
|
} finally { |
|
|
|
|
|
await context.close(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 测试 7A:未登录公开浏览(应能正常访问) |
|
|
* 测试 7A:未登录公开浏览(应能正常访问) |
|
|
*/ |
|
|
*/ |
|
|
@ -630,6 +793,12 @@ async function runTests() { |
|
|
// 阶段 6:订单页
|
|
|
// 阶段 6:订单页
|
|
|
await testOrdersPage(page); |
|
|
await testOrdersPage(page); |
|
|
|
|
|
|
|
|
|
|
|
// 阶段 6B:我的页面增强
|
|
|
|
|
|
await testMemberEnhancements(page); |
|
|
|
|
|
|
|
|
|
|
|
// 阶段 6C:购物车增强
|
|
|
|
|
|
await testCartEnhancements(browser); |
|
|
|
|
|
|
|
|
// 阶段 7A:未登录公开浏览
|
|
|
// 阶段 7A:未登录公开浏览
|
|
|
await testPublicBrowsing(browser); |
|
|
await testPublicBrowsing(browser); |
|
|
|
|
|
|
|
|
|