test-vehicle-fuel-card.mjs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import assert from 'node:assert/strict'
  2. import { chromium } from 'playwright'
  3. const origin = process.env.FUEL_CARD_TEST_ORIGIN || 'http://localhost:3006'
  4. const screenshotPath = process.env.FUEL_CARD_SCREENSHOT || '/tmp/shanxia-vehicle-fuel-card.png'
  5. const browser = await chromium.launch({ headless: true, channel: 'chrome' })
  6. const context = await browser.newContext({ viewport: { width: 1440, height: 1000 } })
  7. const page = await context.newPage()
  8. page.setDefaultTimeout(20000)
  9. const errors = []
  10. page.on('pageerror', (error) => errors.push(error.message))
  11. page.on('console', (message) => {
  12. if (message.type() === 'error') console.error('Browser console:', message.text())
  13. })
  14. page.on('requestfailed', (request) =>
  15. console.error('Request failed:', request.url(), request.failure())
  16. )
  17. const longCard = '000100001234567890123456789012345'
  18. const base = '/transport/vehicle-fuel-card'
  19. const calls = []
  20. let rows = [
  21. {
  22. id: 'fixture-1',
  23. licensePlate: '鄂A12345',
  24. cardNo: longCard,
  25. remark: '初始备注',
  26. createTime: '2026-10-25 10:00:00',
  27. updateTime: null
  28. }
  29. ]
  30. await context.addInitScript(() => {
  31. localStorage.setItem(
  32. 'sys-v3.0.2-user',
  33. JSON.stringify({
  34. isLogin: true,
  35. accessToken: 'fuel-card-fixture',
  36. info: {
  37. id: 'fixture-user',
  38. userId: 'fixture-user',
  39. userName: '回归测试用户',
  40. role: 'admin',
  41. roles: ['admin']
  42. }
  43. })
  44. )
  45. localStorage.setItem('sys-version', '3.0.2')
  46. })
  47. await context.route('**/api/**', async (route) => {
  48. const request = route.request()
  49. const url = new URL(request.url())
  50. if (!url.pathname.startsWith('/api/')) return route.continue()
  51. const path = url.pathname.replace(/^\/api/, '')
  52. const method = request.method()
  53. let body = {}
  54. try {
  55. body = request.postDataJSON() || {}
  56. } catch {
  57. /* GET 无请求体 */
  58. }
  59. let data = {}
  60. if (path.startsWith(base)) {
  61. calls.push({ path, method, body, params: Object.fromEntries(url.searchParams) })
  62. if (path === base && method === 'GET') {
  63. const q = url.searchParams.get('q') || ''
  64. const filtered = rows.filter(
  65. (row) =>
  66. row.licensePlate.includes(q) || row.cardNo.includes(q) || (row.remark || '').includes(q)
  67. )
  68. const current = Number(url.searchParams.get('page') || 1)
  69. const size = Number(url.searchParams.get('pageSize') || 20)
  70. data = { list: filtered.slice((current - 1) * size, current * size), total: filtered.length }
  71. } else if (path === base && method === 'POST') {
  72. assert.deepEqual(Object.keys(body).sort(), ['cardNo', 'licensePlate', 'remark'])
  73. data = { ...body, id: 'fixture-2', createTime: '2026-10-25 11:00:00', updateTime: null }
  74. rows.push(data)
  75. } else if (path === `${base}/batch-delete`) {
  76. const previous = rows.length
  77. rows = rows.filter((row) => !body.ids.includes(row.id))
  78. data = { deleted: previous - rows.length }
  79. } else if (method === 'PUT') {
  80. assert.deepEqual(Object.keys(body).sort(), ['cardNo', 'licensePlate', 'remark'])
  81. const id = path.split('/').pop()
  82. const index = rows.findIndex((row) => row.id === id)
  83. data = rows[index] = { ...rows[index], ...body, updateTime: '2026-10-25 12:00:00' }
  84. } else if (method === 'GET') {
  85. data = rows.find((row) => row.id === path.split('/').pop())
  86. } else {
  87. throw new Error(`Unexpected fixture request: ${method} ${path}`)
  88. }
  89. return route.fulfill({ json: data })
  90. }
  91. if (path === '/web/auth/current-user') {
  92. data = {
  93. id: 'fixture-user',
  94. userId: 'fixture-user',
  95. userName: '回归测试用户',
  96. role: 'admin',
  97. roles: ['admin']
  98. }
  99. } else if (path === '/web/auth/menu-codes') {
  100. data = {
  101. all: false,
  102. menuCodes: ['pl:vehicle-fuel-card:list', 'pl:vehicle-recharge-workflow:list']
  103. }
  104. } else if (path.includes('favorites')) {
  105. data = []
  106. } else if (path.includes('dict-data')) {
  107. data = {}
  108. } else if (path.includes('upgrade-logs')) {
  109. data = []
  110. }
  111. await route.fulfill({ json: { code: path.startsWith('/transport/') ? 0 : 200, msg: 'ok', data } })
  112. })
  113. try {
  114. await page.goto(`${origin}/#/vehicle-mgmt/etc-recharge/vehicle-fuel-card`)
  115. await page.getByText('车油卡信息', { exact: true }).first().click()
  116. const panel = page
  117. .locator('.panel')
  118. .filter({ has: page.locator('h2.title-bar-heading', { hasText: '车油卡信息' }) })
  119. await panel.locator('.el-link').filter({ hasText: '鄂A12345' }).waitFor()
  120. assert.equal(await panel.locator('thead').getByText('操作', { exact: true }).count(), 0)
  121. await panel.locator('.el-link').filter({ hasText: '鄂A12345' }).click()
  122. const view = page.getByRole('dialog').filter({ has: page.locator('.view-hero') })
  123. await view.getByText(longCard, { exact: true }).waitFor()
  124. await view.getByRole('button', { name: '编辑', exact: true }).click()
  125. const edit = page.getByRole('dialog').filter({ hasText: '编辑车油卡信息' })
  126. await edit.getByPlaceholder('请输入油卡卡号').waitFor()
  127. assert.equal(await edit.getByPlaceholder('请输入油卡卡号').inputValue(), longCard)
  128. assert.equal(await edit.getByPlaceholder('请输入备注(选填)').inputValue(), '初始备注')
  129. await edit.getByPlaceholder('请输入备注(选填)').fill('')
  130. await edit.getByRole('button', { name: '确定', exact: true }).click()
  131. await edit.waitFor({ state: 'hidden' })
  132. assert.equal(calls.find((call) => call.method === 'PUT').body.cardNo, longCard)
  133. assert.equal(calls.find((call) => call.method === 'PUT').body.remark, null)
  134. await panel.getByRole('button', { name: '新增', exact: true }).click()
  135. const add = page.getByRole('dialog').filter({ hasText: '新增车油卡信息' })
  136. await add.getByPlaceholder('请输入车牌号').fill('鄂B23456')
  137. await add.getByPlaceholder('请输入油卡卡号').fill('00012345678901234567890')
  138. await add.getByPlaceholder('请输入备注(选填)').fill('新增备注')
  139. await add.getByRole('button', { name: '确定', exact: true }).click()
  140. await add.waitFor({ state: 'hidden' })
  141. await panel.locator('.el-link').filter({ hasText: '鄂B23456' }).waitFor()
  142. await page.locator('.el-message').first().waitFor({ state: 'hidden' })
  143. await page.screenshot({ path: screenshotPath, fullPage: true })
  144. await panel.getByPlaceholder('车牌号/油卡卡号/备注').fill('鄂B')
  145. await panel.getByPlaceholder('车牌号/油卡卡号/备注').press('Enter')
  146. await panel.locator('.el-link').filter({ hasText: '鄂A12345' }).waitFor({ state: 'hidden' })
  147. assert.equal(
  148. calls.filter((call) => call.path === base && call.method === 'GET').at(-1).params.q,
  149. '鄂B'
  150. )
  151. await panel.getByPlaceholder('车牌号/油卡卡号/备注').hover()
  152. await panel.locator('.quick-search-input .el-input__clear').click()
  153. await panel.locator('.el-link').filter({ hasText: '鄂A12345' }).waitFor()
  154. await panel.locator('thead .el-checkbox').first().click()
  155. await panel.getByRole('button', { name: '删除', exact: true }).click()
  156. await page.locator('.el-message-box').getByRole('button', { name: '删除', exact: true }).click()
  157. await panel.locator('.el-link').filter({ hasText: '鄂A12345' }).waitFor({ state: 'hidden' })
  158. assert.equal(rows.length, 0)
  159. assert.deepEqual(calls.find((call) => call.path.endsWith('batch-delete')).body.ids.sort(), [
  160. 'fixture-1',
  161. 'fixture-2'
  162. ])
  163. assert.deepEqual(errors, [])
  164. console.log(
  165. `PASS: 真实路由、列表、查看、编辑、长卡号、清空备注、新增、搜索、批量删除;截图 ${screenshotPath}`
  166. )
  167. } catch (error) {
  168. console.error('Browser state:', page.url(), await page.locator('body').innerText(), errors, calls)
  169. await page.screenshot({ path: '/tmp/shanxia-fuel-card-failure.png', fullPage: true })
  170. throw error
  171. } finally {
  172. await browser.close()
  173. }