test-vehicle-recharge-fuel-card.mjs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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-recharge-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. const calls = []
  11. page.on('pageerror', (error) => errors.push(error.message))
  12. const cardNo = '1000113400009309765'
  13. const oldCardNo = '1000113400009253456'
  14. const vehicles = ['皖LB1283', '皖H21522', '皖H09177'].map((licensePlate, index) => ({
  15. id: `vehicle-${index}`,
  16. licensePlate,
  17. vehicleType: 'self',
  18. vehicleStatus: 'available'
  19. }))
  20. const row = {
  21. id: 'apply-fixture',
  22. applyNo: 'CZ202610250001',
  23. licensePlate: '皖LB1283',
  24. rechargeType: 'fuel',
  25. boundFuelCardNo: cardNo,
  26. applicantId: 'fixture-user',
  27. applicantName: '回归测试用户',
  28. applyDate: '2026-10-25',
  29. odometerKm: 12345,
  30. status: 'disbursed',
  31. record: { cardNo: oldCardNo, amount: 1000, rechargeDate: '2026-10-25' }
  32. }
  33. await context.addInitScript(() => {
  34. localStorage.setItem(
  35. 'sys-v3.0.2-user',
  36. JSON.stringify({
  37. isLogin: true,
  38. accessToken: 'fuel-card-fixture',
  39. info: {
  40. id: 'fixture-user',
  41. userId: 'fixture-user',
  42. userName: '回归测试用户',
  43. role: 'admin',
  44. roles: ['admin']
  45. }
  46. })
  47. )
  48. localStorage.setItem('sys-version', '3.0.2')
  49. })
  50. await context.route('**/api/**', async (route) => {
  51. const request = route.request()
  52. const url = new URL(request.url())
  53. if (!url.pathname.startsWith('/api/')) return route.continue()
  54. const path = url.pathname.replace(/^\/api/, '')
  55. let data = {}
  56. if (path.startsWith('/transport/')) {
  57. calls.push({ path, method: request.method(), params: Object.fromEntries(url.searchParams) })
  58. assert.equal(request.method(), 'GET', 'browser check must not submit business data')
  59. if (path === '/transport/vehicle') data = { list: vehicles, total: vehicles.length }
  60. else if (path === '/transport/vehicle-recharge-apply/') data = { items: [row], total: 1 }
  61. else if (path.startsWith('/transport/vehicle-recharge-approval/'))
  62. data = { items: [row], total: 1 }
  63. else if (path === '/transport/vehicle-recharge-record/disburse-board')
  64. data = {
  65. items: [
  66. { ...row, applyId: row.id, applyStatus: row.status, recordId: 'r1', cardNo: oldCardNo }
  67. ],
  68. total: 1
  69. }
  70. else if (path === '/transport/vehicle-recharge-apply/fuel-card') {
  71. const licensePlate = url.searchParams.get('licensePlate')
  72. if (licensePlate === '皖H09177')
  73. return route.fulfill({ status: 503, json: { detail: '测试网络失败' } })
  74. return route.fulfill({
  75. json: { licensePlate, cardNo: licensePlate === '皖LB1283' ? cardNo : null }
  76. })
  77. } else if (path === '/transport/vehicle-recharge-apply/apply-fixture') data = row
  78. return route.fulfill({ json: data })
  79. }
  80. if (path === '/web/auth/current-user') {
  81. data = {
  82. id: 'fixture-user',
  83. userId: 'fixture-user',
  84. userName: '回归测试用户',
  85. role: 'admin',
  86. roles: ['admin']
  87. }
  88. } else if (path === '/web/auth/menu-codes') {
  89. data = {
  90. all: false,
  91. menuCodes: [
  92. 'pl:vehicle-recharge-apply:list',
  93. 'pl:vehicle-recharge-approval:list',
  94. 'pl:vehicle-recharge-record:list'
  95. ]
  96. }
  97. } else if (path === '/web/admin/users/options') {
  98. data = [{ label: '回归测试用户', value: 'fixture-user' }]
  99. } else if (path.includes('favorites') || path.includes('upgrade-logs')) data = []
  100. await route.fulfill({ json: { code: 200, msg: 'ok', data } })
  101. })
  102. function field(dialog, label) {
  103. return dialog.locator('.el-form-item').filter({
  104. has: page.locator('.el-form-item__label').filter({ hasText: new RegExp(`^${label}$`) })
  105. })
  106. }
  107. async function choose(dialog, label, text) {
  108. await field(dialog, label).locator('.el-select').click()
  109. await page.locator('.el-select-dropdown:visible').getByText(text, { exact: true }).click()
  110. }
  111. try {
  112. for (const kind of ['apply', 'approval', 'record']) {
  113. await page.goto(`${origin}/#/vehicle-mgmt/etc-recharge/vehicle-recharge-${kind}`)
  114. const table = page.locator('.list-table')
  115. await table.getByText('油卡卡号', { exact: true }).waitFor()
  116. await table.getByText(cardNo, { exact: true }).first().waitFor()
  117. assert.equal(await table.getByText(cardNo, { exact: true }).count(), 1)
  118. }
  119. await page.goto(`${origin}/#/vehicle-mgmt/etc-recharge/vehicle-recharge-apply`)
  120. await page.getByRole('button', { name: '新增申请', exact: true }).waitFor()
  121. await page.locator('.el-link').filter({ hasText: row.applyNo }).click()
  122. const view = page.getByRole('dialog').filter({ has: page.locator('.view-hero') })
  123. await view.getByText('绑定油卡卡号', { exact: true }).waitFor()
  124. await view.getByText(cardNo, { exact: true }).waitFor()
  125. await view.getByText(oldCardNo, { exact: true }).waitFor()
  126. await view.locator('.el-dialog__headerbtn').click()
  127. await view.waitFor({ state: 'hidden' })
  128. await page.getByRole('button', { name: '新增申请', exact: true }).click()
  129. const form = page.getByRole('dialog').filter({ hasText: '新增申请' })
  130. await choose(form, '充值类型', '油卡')
  131. const binding = field(form, '绑定油卡卡号').locator('input')
  132. await binding.waitFor()
  133. assert.equal(await binding.inputValue(), '请先选择车牌号')
  134. await choose(form, '车牌号', '皖LB1283')
  135. await page.waitForFunction(
  136. (expected) =>
  137. [...document.querySelectorAll('.el-dialog input')].some((input) => input.value === expected),
  138. cardNo
  139. )
  140. assert.equal(await binding.inputValue(), cardNo)
  141. assert.notEqual(await binding.getAttribute('readonly'), null)
  142. await choose(form, '充值类型', '山峡')
  143. await binding.waitFor({ state: 'hidden' })
  144. await choose(form, '充值类型', '油卡')
  145. await page.waitForFunction(
  146. (expected) =>
  147. [...document.querySelectorAll('.el-dialog input')].some((input) => input.value === expected),
  148. cardNo
  149. )
  150. await form.locator('.el-dialog__header').click()
  151. await page.locator('.el-select-dropdown:visible').waitFor({ state: 'hidden' })
  152. await page.screenshot({ path: screenshotPath, fullPage: true })
  153. await choose(form, '车牌号', '皖H21522')
  154. await page.waitForFunction(() =>
  155. [...document.querySelectorAll('.el-dialog input')].some((input) => input.value === '未绑定油卡')
  156. )
  157. assert.equal(await binding.inputValue(), '未绑定油卡')
  158. await choose(form, '车牌号', '皖H09177')
  159. await page.waitForFunction(() =>
  160. [...document.querySelectorAll('.el-dialog input')].some(
  161. (input) => input.value === '油卡信息加载失败'
  162. )
  163. )
  164. assert.equal(await binding.inputValue(), '油卡信息加载失败')
  165. assert.deepEqual(errors, [])
  166. assert.ok(calls.some((call) => call.path.endsWith('/fuel-card')))
  167. console.log(
  168. `PASS: 真实申请页详情/填写、长卡号、历史卡号保留、fuel/山峡切换、未绑定及失败;未提交数据。截图 ${screenshotPath}`
  169. )
  170. } catch (error) {
  171. console.error('Browser state:', page.url(), await page.locator('body').innerText(), errors, calls)
  172. await page.screenshot({ path: '/tmp/shanxia-recharge-fuel-card-failure.png', fullPage: true })
  173. throw error
  174. } finally {
  175. await browser.close()
  176. }