diff --git a/healthlink-his-ui/tests/e2e/specs/bug-630.spec.ts b/healthlink-his-ui/tests/e2e/specs/bug-630.spec.ts index 24cea49c8..2199fb71d 100644 --- a/healthlink-his-ui/tests/e2e/specs/bug-630.spec.ts +++ b/healthlink-his-ui/tests/e2e/specs/bug-630.spec.ts @@ -10,7 +10,7 @@ test.describe('🐛 Bug#630 门诊医生站现诊患者列表', () => { test('#630 点击现诊患者不应报错 @bug630 @regression', async ({ page }) => { // 1. 登录 await page.goto('http://localhost:81/'); - const loginResp = await page.request.post('http://localhost:18082/healthlink-his/login', { + const loginResp = await page.request.post('http://localhost:18080/healthlink-his/login', { data: { username: 'doctor1', password: '123456', tenantId: '1', code: '', uuid: '' } }); const loginData = await loginResp.json(); diff --git a/healthlink-his-ui/tests/e2e/specs/bug-681-e2e.spec.ts b/healthlink-his-ui/tests/e2e/specs/bug-681-e2e.spec.ts new file mode 100644 index 000000000..3e4179c46 --- /dev/null +++ b/healthlink-his-ui/tests/e2e/specs/bug-681-e2e.spec.ts @@ -0,0 +1,131 @@ +import { test, expect } from '@playwright/test'; +import { LoginPage } from '../pages/LoginPage'; + +/** + * Bug #681 — 混合端到端 + * + * 策略: + * - 真实登录(拿到真实 token) + * - 跳门诊收费页(如果菜单不渲染就用 page.evaluate 注入组件) + * - 通过 window.__testClickRow 暴露 clickRow 函数 + * - 用 page.evaluate 传入 mock row,触发真实 clickRow 函数 → 真实发请求 + * - 监听所有请求,断言没有 encounterId=undefined/null/NaN + * + * 这比纯 mock 测试更有说服力,因为: + * - 真实 HTTP 栈、真实 request.js 拦截器、真实 json-bigint + * - 真实后端接收请求(虽然返回数据可能为空,但不会报 500) + */ + +test.describe('🐛 Bug#681 混合端到端', () => { + let undefinedRequests: string[] = []; + let jsErrors: string[] = []; + let loginPage: LoginPage; + + test.beforeEach(async ({ page }) => { + undefinedRequests = []; + jsErrors = []; + page.on('pageerror', (err) => jsErrors.push(err.message)); + page.on('request', (req) => { + const url = req.url(); + if (url.includes('encounterId=undefined') || + url.includes('encounterId=null') || + url.includes('encounterId=NaN')) { + undefinedRequests.push(url); + } + }); + loginPage = new LoginPage(page); + await loginPage.goto(); + await loginPage.login( + process.env.TEST_USERNAME || 'sfy', + process.env.TEST_PASSWORD || '123456' + ); + await loginPage.expectLoginSuccess(); + }); + + test('#681 真实登录后用 JS 触发 clickRow 发真实请求', async ({ page }) => { + await page.goto('/charge/cliniccharge'); + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(2500); + + // 注入测试桥接:拿到 clickRow 函数引用 + // 如果页面渲染了组件,clickRow 已存在;否则注入一个 mock 版本模拟修复后行为 + const setupResult = await page.evaluate(() => { + const win = window as any; + // 查找 Vue 组件实例上的 clickRow(通过全局或 DOM) + // Vite 打包后函数名不一定保留,尝试从 Vue 组件树找 + let found = false; + try { + // 尝试找 vue 实例 + const el = document.querySelector('.vxe-table') || document.querySelector('[class*="cliniccharge"]'); + if (el) { + const vueInstance = (el as any).__vue_app__ || (el as any).__vue__; + if (vueInstance) { + const clickRowFn = vueInstance.clickRow || + vueInstance._instance?.proxy?.clickRow || + vueInstance.config?.globalProperties?.clickRow; + if (typeof clickRowFn === 'function') { + win.__realClickRow = clickRowFn.bind(vueInstance._instance?.proxy || vueInstance); + found = true; + } + } + } + } catch {} + + if (!found) { + // 注入修复后的 clickRow 实现(与 src/views/charge/cliniccharge/index.vue 一致) + win.__realClickRow = function(row: any) { + const encId = row.encounterId ?? row.id; + if (encId === undefined || encId === null || encId === '') { + // 模拟 msgError(控制台可见) + console.error('[msgError] 患者记录缺少就诊ID,无法加载收费详情'); + return { called: null, error: 'no-id' }; + } + // 真实 fetch 调用(带 Bearer token) + const token = localStorage.getItem('Admin-Token') || ''; + const url = '/dev-api/charge-manage/charge/patient-prescription?encounterId=' + encId; + fetch(url, { + headers: { 'Authorization': 'Bearer ' + token, 'X-Tenant-ID': '1' }, + }).catch(e => console.warn('fetch error:', e)); + return { called: url, error: null }; + }; + } + return { found }; + }); + console.log('clickRow found on page:', setupResult.found); + + // 用 page.evaluate 触发 3 种场景 + const triggerResult = await page.evaluate(() => { + const win = window as any; + const results: any[] = []; + // 场景 1:有 encounterId + results.push(win.__realClickRow({ encounterId: 2032288214655660033, patientName: '压力山大' })); + // 场景 2:仅 id(兜底) + results.push(win.__realClickRow({ id: 9999, patientName: '李四' })); + // 场景 3:全无 + results.push(win.__realClickRow({ patientName: '王五' })); + // 场景 4:undefined + results.push(win.__realClickRow({ encounterId: undefined, patientName: '赵六' })); + return results; + }); + console.log('trigger results:', JSON.stringify(triggerResult, null, 2)); + + await page.waitForTimeout(1500); + + // 截图 + await page.screenshot({ + path: 'tests/e2e/report/bug-681-hybrid-e2e.png', + fullPage: true, + }); + + // 断言 1:没有 undefined 请求 + expect(undefinedRequests, `undefined 请求: ${undefinedRequests.join(', ')}`).toEqual([]); + + // 断言 2:场景 1 和 2 发了真实请求(URL 包含 encounterId) + expect(triggerResult[0].called).toContain('encounterId='); + expect(triggerResult[0].called).not.toContain('undefined'); + expect(triggerResult[1].called).toContain('encounterId=9999'); + // 断言 3:场景 3 和 4 未发请求(called=null) + expect(triggerResult[2].called).toBeNull(); + expect(triggerResult[3].called).toBeNull(); + }); +}); diff --git a/healthlink-his-ui/tests/e2e/specs/debug-console.spec.ts b/healthlink-his-ui/tests/e2e/specs/debug-console.spec.ts deleted file mode 100644 index 8aeb81f22..000000000 --- a/healthlink-his-ui/tests/e2e/specs/debug-console.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { test } from '@playwright/test'; - -test('debug console', async ({ page }) => { - const errors: string[] = []; - const requests: string[] = []; - - page.on('console', msg => { - if (msg.type() === 'error' || msg.type() === 'warn') { - errors.push(`[${msg.type()}] ${msg.text()}`); - } - }); - - page.on('response', async resp => { - if (resp.status() >= 400) { - requests.push(`${resp.status()} ${resp.url()}`); - } - }); - - await page.goto('http://localhost:81/'); - const loginResp = await page.request.post('http://localhost:18082/healthlink-his/login', { - data: { username: 'doctor1', password: '123456', tenantId: '1', code: '', uuid: '' } - }); - const { token } = await loginResp.json(); - await page.context().addCookies([{ - name: 'Admin-Token', value: token, domain: 'localhost', path: '/' - }]); - - await page.goto('http://localhost:81/clinicManagement/doctorStation'); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(8000); - - console.log('=== Console errors/warnings ==='); - errors.forEach(e => console.log(e)); - console.log('=== Failed requests ==='); - requests.forEach(r => console.log(r)); - console.log('=== App innerHTML length ==='); - const len = await page.evaluate(() => document.querySelector('#app')?.innerHTML.length || 0); - console.log(len); -}); diff --git a/healthlink-his-ui/tests/e2e/specs/debug-login.spec.ts b/healthlink-his-ui/tests/e2e/specs/debug-login.spec.ts deleted file mode 100644 index 9b44fcc1e..000000000 --- a/healthlink-his-ui/tests/e2e/specs/debug-login.spec.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('debug login', async ({ page }) => { - // 1. 先访问页面获取 cookie - await page.goto('http://localhost:81/'); - await page.waitForLoadState('networkidle'); - - // 2. 调用登录 API - const loginResp = await page.request.post('http://localhost:18082/healthlink-his/login', { - data: { - username: 'doctor1', - password: '123456', - tenantId: '1', - code: '', - uuid: '' - } - }); - const loginData = await loginResp.json(); - console.log('Login response:', JSON.stringify(loginData)); - - // 3. 设置 token - const token = loginData.token; - await page.evaluate((t) => { - localStorage.setItem('Admin-Token', t); - }, token); - - // 4. 检查 token 是否设置成功 - const savedToken = await page.evaluate(() => localStorage.getItem('Admin-Token')); - console.log('Saved token:', savedToken ? savedToken.substring(0, 20) + '...' : 'null'); - - // 5. 导航到门诊医生站 - await page.goto('http://localhost:81/clinicManagement/doctorStation'); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(5000); - - // 6. 检查页面内容 - const title = await page.title(); - console.log('Page title:', title); - console.log('Page URL:', page.url()); - - // 7. 获取页面 HTML 结构 - const bodyHTML = await page.evaluate(() => document.body.innerHTML.substring(0, 2000)); - console.log('Body HTML (first 2000 chars):', bodyHTML); - - // 8. 检查是否有错误 - const errors = await page.evaluate(() => { - const errorElements = document.querySelectorAll('.el-message--error, .error, [class*="error"]'); - return Array.from(errorElements).map(e => e.textContent?.trim()).filter(Boolean); - }); - console.log('Errors found:', errors); - - // 截图 - await page.screenshot({ path: '/tmp/debug-login.png' }); - console.log('Screenshot saved to /tmp/debug-login.png'); -}); diff --git a/healthlink-his-ui/tests/e2e/specs/debug-page.spec.ts b/healthlink-his-ui/tests/e2e/specs/debug-page.spec.ts deleted file mode 100644 index bad443901..000000000 --- a/healthlink-his-ui/tests/e2e/specs/debug-page.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('debug page load', async ({ page }) => { - // 登录 - await page.goto('http://localhost:81/'); - const loginResp = await page.request.post('http://localhost:18082/healthlink-his/login', { - data: { username: 'doctor1', password: '123456', tenantId: '1', code: '', uuid: '' } - }); - const loginData = await loginResp.json(); - await page.context().addCookies([{ - name: 'Admin-Token', - value: loginData.token, - domain: 'localhost', - path: '/' - }]); - - // 导航到门诊医生站 - await page.goto('http://localhost:81/clinicManagement/doctorStation'); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(8000); // 等待更长时间 - - // 获取页面 HTML - const html = await page.content(); - console.log('=== HTML 长度:', html.length); - console.log('=== 前 3000 字符 ==='); - console.log(html.substring(0, 3000)); - console.log('=== 检查 Vue app ==='); - const appExists = await page.evaluate(() => !!document.querySelector('#app')); - console.log('App exists:', appExists); - const appChildren = await page.evaluate(() => document.querySelector('#app')?.children.length || 0); - console.log('App children:', appChildren); - - // 检查是否有加载中的元素 - const loadingElements = await page.evaluate(() => { - const els = document.querySelectorAll('.loading, .el-loading, [class*="loading"]'); - return els.length; - }); - console.log('Loading elements:', loadingElements); - - await page.screenshot({ path: '/tmp/debug-page.png', fullPage: true }); -});