- 删除开发遗留的 debug 测试文件: - debug-console.spec.ts - debug-login.spec.ts - debug-page.spec.ts - bug-630.spec.ts: 后端端口 18082 → 18080(匹配 application.yml) - 新增 bug-681-e2e.spec.ts: 真实登录+fetch+proxy 混合 E2E 验证 Jackson 3 迁移后 Long 字段以字符串形式返回
132 lines
5.3 KiB
TypeScript
132 lines
5.3 KiB
TypeScript
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();
|
||
});
|
||
});
|