feat: 模拟采集E2E测试(7项IIS模式全通过)+修复mock插件RegExp兼容问题

feat/windows-service-status-auto
haoliang 8 hours ago
parent 72cb43c493
commit 06d04c244e

@ -0,0 +1,106 @@
/**
* CNC - Playwright E2E
*
* IIS
* -
* - /simulatorUI
*
* `npx playwright test e2e/simulator.spec.ts --project=chromium`
* IISCncSimulator
*/
import { test, expect, type Page } from '@playwright/test'
// === 登录辅助函数 ===
// 使用完整URL避免baseURL(127.0.0.1)与IIS绑定(192.168.1.202)不匹配
const BASE = 'http://192.168.1.202/admin'
async function login(page: Page) {
await page.goto(`${BASE}/login`)
await page.waitForLoadState('networkidle')
await page.waitForSelector('input', { timeout: 10000 })
const inputs = page.locator('input')
await inputs.nth(0).fill('admin')
await inputs.nth(1).fill('admin123')
await page.locator('button').last().click()
await page.waitForURL(/\/(dashboard|admin\/?$)/, { timeout: 15000 })
}
// ============================================================
// 套件1侧边栏导航
// ============================================================
test.describe('模拟采集侧边栏导航', () => {
test.beforeEach(async ({ page }) => {
await login(page)
})
test('侧边栏有"模拟采集"菜单项', async ({ page }) => {
const menuItems = page.locator('.el-menu-item')
const simulatorItem = menuItems.filter({ hasText: '模拟采集' })
await expect(simulatorItem).toBeVisible()
})
test('点击"模拟采集"菜单跳转到总览页', async ({ page }) => {
const menuItems = page.locator('.el-menu-item')
const simulatorItem = menuItems.filter({ hasText: '模拟采集' })
await simulatorItem.click()
await page.waitForURL(/\/simulator$/, { timeout: 10000 })
expect(page.url()).toContain('/simulator')
})
})
// ============================================================
// 套件2总览页——模拟器未连接状态
// ============================================================
test.describe('模拟采集总览页(模拟器未连接)', () => {
test.beforeEach(async ({ page }) => {
await login(page)
// 通过侧边栏菜单导航到模拟采集页面
const menuItems = page.locator('.el-menu-item')
const simulatorItem = menuItems.filter({ hasText: '模拟采集' })
await simulatorItem.click()
await page.waitForURL(/\/simulator$/, { timeout: 10000 })
// 等待ping API调用完成
await page.waitForTimeout(2000)
})
test('页面显示"模拟器未连接"', async ({ page }) => {
await expect(page.locator('text=模拟器未连接')).toBeVisible()
})
test('页面显示友好提示信息', async ({ page }) => {
await expect(page.locator('text=模拟器未启动')).toBeVisible()
})
test('三个操作按钮正确禁用', async ({ page }) => {
await expect(page.locator('button:has-text("全部启动")')).toBeDisabled()
await expect(page.locator('button:has-text("全部停止")')).toBeDisabled()
await expect(page.locator('button:has-text("刷新配置")')).toBeDisabled()
})
test('面包屑导航显示"首页 / 模拟采集"', async ({ page }) => {
const breadcrumb = page.locator('.el-breadcrumb')
await expect(breadcrumb).toBeVisible()
await expect(breadcrumb.locator('text=首页')).toBeVisible()
await expect(breadcrumb.locator('text=模拟采集')).toBeVisible()
})
test('页面无JavaScript异常排除预期的网络错误', async ({ page }) => {
const errors: string[] = []
page.on('console', (msg) => {
if (msg.type() === 'error') errors.push(msg.text())
})
await page.reload({ waitUntil: 'networkidle' })
await page.waitForTimeout(3000)
// 过滤掉模拟器不可达的预期错误
const realErrors = errors.filter(e =>
!e.includes('simulator') &&
!e.includes('50001') &&
!e.includes('Network Error') &&
!e.includes('ERR_CONNECTION_REFUSED') &&
!e.includes('404')
)
expect(realErrors).toEqual([])
})
})

@ -87,8 +87,8 @@ const mocks: MockMethod[] = [
// 重新加载
{ url: '/api/admin/simulator/reload', method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true, count: 2 } }) },
// 单地址状态(匹配 /address/{port}/status
{ url: /\/api\/admin\/simulator\/address\/\d+\/status$/, method: 'get', response: () => ({
// 单地址状态(参数化路由 :port 匹配动态端口
{ url: '/api/admin/simulator/address/:port/status', method: 'get', response: () => ({
code: 0, message: 'success', data: {
name: 'FANUC-1号模拟', port: 9001, isRunning: true,
requestCount: 1560, successCount: 1540, failCount: 20,
@ -100,20 +100,20 @@ const mocks: MockMethod[] = [
})},
// 单地址启动/停止/事件/设置POST类统返回ok
{ url: /\/api\/admin\/simulator\/address\/\d+\/start$/, method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) },
{ url: /\/api\/admin\/simulator\/address\/\d+\/stop$/, method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) },
{ url: /\/api\/admin\/simulator\/address\/\d+\/event$/, method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) },
{ url: /\/api\/admin\/simulator\/address\/\d+\/interval$/, method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) },
{ url: /\/api\/admin\/simulator\/address\/\d+\/network$/, method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) },
{ url: /\/api\/admin\/simulator\/address\/\d+\/mode$/, method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) },
{ url: /\/api\/admin\/simulator\/address\/\d+\/add-device$/, method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) },
{ url: /\/api\/admin\/simulator\/address\/\d+\/remove-device$/, method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) },
{ url: '/api/admin/simulator/address/:port/start', method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) },
{ url: '/api/admin/simulator/address/:port/stop', method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) },
{ url: '/api/admin/simulator/address/:port/event', method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) },
{ url: '/api/admin/simulator/address/:port/interval', method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) },
{ url: '/api/admin/simulator/address/:port/network', method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) },
{ url: '/api/admin/simulator/address/:port/mode', method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) },
{ url: '/api/admin/simulator/address/:port/add-device', method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) },
{ url: '/api/admin/simulator/address/:port/remove-device', method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) },
// 日志
{ url: /\/api\/admin\/simulator\/address\/\d+\/logs$/, method: 'get', response: () => ({ code: 0, message: 'success', data: mockLogs }) },
{ url: '/api/admin/simulator/address/:port/logs', method: 'get', response: () => ({ code: 0, message: 'success', data: mockLogs }) },
// 统计
{ url: /\/api\/admin\/simulator\/address\/\d+\/stats$/, method: 'get', response: () => ({
{ url: '/api/admin/simulator/address/:port/stats', method: 'get', response: () => ({
code: 0, message: 'success', data: {
totalDevices: 32, onlineDevices: 28, totalParts: 128,
partsByDevice: {
@ -124,16 +124,16 @@ const mocks: MockMethod[] = [
})},
// 事件历史
{ url: /\/api\/admin\/simulator\/address\/\d+\/event-history$/, method: 'get', response: () => ({ code: 0, message: 'success', data: [
{ url: '/api/admin/simulator/address/:port/event-history', method: 'get', response: () => ({ code: 0, message: 'success', data: [
{ timestamp: '2026-05-06 14:30:00', deviceCode: 'fanake_1.2', eventType: 'change_program', oldProgram: 'O200', newProgram: 'O504', partCountBefore: 10, partCountAfter: 14, detail: '程序切换' },
{ timestamp: '2026-05-06 14:25:00', deviceCode: 'fanake_1.3', eventType: 'part_count_increase', oldProgram: 'O1', newProgram: 'O1', partCountBefore: 52, partCountAfter: 53, detail: '零件数+1' }
] })},
// 完整汇总
{ url: /\/api\/admin\/simulator\/address\/\d+\/full-summary$/, method: 'get', response: () => ({ code: 0, message: 'success', data: { exportTime: '2026-05-06 14:35:00', addressName: 'FANUC-1号模拟', port: 9001, totalDevices: 32, onlineDevices: 28, totalParts: 128 } }) },
{ url: '/api/admin/simulator/address/:port/full-summary', method: 'get', response: () => ({ code: 0, message: 'success', data: { exportTime: '2026-05-06 14:35:00', addressName: 'FANUC-1号模拟', port: 9001, totalDevices: 32, onlineDevices: 28, totalParts: 128 } }) },
// 异常日志
{ url: /\/api\/admin\/simulator\/address\/\d+\/error-log$/, method: 'get', response: () => ({ code: 0, message: 'success', data: [] }) },
{ url: '/api/admin/simulator/address/:port/error-log', method: 'get', response: () => ({ code: 0, message: 'success', data: [] }) },
]
export default mocks

Loading…
Cancel
Save