Commit 6f06c08d by DaiJiezhang

fix(frontend): stabilize test environment

- Run authentication mocks through the page fixture for every test file.\n- Load mode env files from the Vite config directory and pass pnpm flags directly.
parent 6371aee1
......@@ -31,7 +31,6 @@ backend/.mvn/jvm.config
.env
# 前端本机后端地址覆盖:只属于个人环境。.env.java 是团队共享的对拍配置,仍进仓库。
.env.local
frontend/.env.local
.idea/
.vite/
frontend/.playwright-cli/
......
......@@ -87,9 +87,11 @@ pnpm dev:java
|---|---|---|
| `vite.config.js` 默认值 | NestJS `7691``pnpm dev` 用它 | 是 |
| `frontend/.env.java` | Java `7690``pnpm dev:java` 用它 | 是 |
| `frontend/.env.local` | 本机临时覆盖,改一行即生效 | 否 |
| `frontend/.env.local` | 本机临时覆盖,只对 `pnpm dev` 生效 | 否 |
优先级:命令行环境变量 > `.env` 文件 > `vite.config.js` 默认值。
优先级:命令行环境变量 > `.env.<mode>` > `.env.local` > `.env` > `vite.config.js` 默认值。
注意:`pnpm dev:java``java` 模式,`.env.java` 的优先级高于 `.env.local`,此时本机覆盖不生效。要临时改 Java 对拍地址,直接改 `.env.java`,或在命令行设置 `VITE_API_TARGET`
### NestJS 后端(7691)
......
......@@ -9,7 +9,7 @@
"test:e2e": "playwright test"
},
"engines": {
"node": ">=20.19.0 || >=22.12.0"
"node": "^20.19.0 || >=22.12.0"
},
"packageManager": "pnpm@12.3.4",
"volta": {
......
......@@ -3,5 +3,5 @@ import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
use: { baseURL: 'http://127.0.0.1:5173', browserName: 'chromium' },
webServer: { command: 'pnpm dev -- --host 127.0.0.1', url: 'http://127.0.0.1:5173/', reuseExistingServer: true, timeout: 30_000 }
webServer: { command: 'pnpm dev --host 127.0.0.1', url: 'http://127.0.0.1:5173/', reuseExistingServer: true, timeout: 30_000 }
});
......@@ -4,5 +4,5 @@ import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
use: { baseURL: 'http://127.0.0.1:5174', browserName: 'chromium' },
webServer: { command: 'pnpm dev -- --host 127.0.0.1 --port 5174', url: 'http://127.0.0.1:5174/', reuseExistingServer: false, timeout: 30_000 }
webServer: { command: 'pnpm dev --host 127.0.0.1 --port 5174', url: 'http://127.0.0.1:5174/', reuseExistingServer: false, timeout: 30_000 }
});
import { expect, test as base } from '@playwright/test';
/** File purpose (plain language): supplies the same developer login fixture to every asset browser test without changing production authentication code. */
export const test = base;
/** 文件用途(白话):为每个资产页面测试提供独立、确定的开发者登录态,不改生产认证代码。 */
const developerUser = {
id: 1,
username: 'playwright-asset-admin',
roleCode: 'DEVELOPER',
pagePermissions: {
overview: 'EDIT',
domain: 'EDIT',
'reference-wecom': 'EDIT',
'phone-assets': 'EDIT',
'company-profile': 'EDIT',
'company-person': 'EDIT',
alerts: 'EDIT'
}
};
/** Plain purpose: register deterministic authentication responses before a test opens a protected route. Related files: auth-store.js, router/index.js, and all files in frontend/tests. Data flow: test setup -> mocked current-user/CSRF requests -> route guard -> protected page. */
test.beforeEach(async ({ page }) => {
/** Plain purpose: add the browser-visible CSRF Cookie used by the shared request helper in either local Playwright port. Related files: auth-api-client.js, SecurityConfig.java. Flow: test context Cookie -> X-XSRF-TOKEN header -> protected request fixture. */
await page.context().addCookies([{ name: 'XSRF-TOKEN', value: 'playwright-csrf', domain: '127.0.0.1', path: '/' }]);
const user = {
id: 1,
username: 'playwright-asset-admin',
roleCode: 'DEVELOPER',
pagePermissions: {
overview: 'EDIT',
domain: 'EDIT',
'reference-wecom': 'EDIT',
'phone-assets': 'EDIT',
'company-profile': 'EDIT',
'company-person': 'EDIT',
alerts: 'EDIT'
}
};
/** Plain purpose: return an administrator identity to the route guard. Related files: auth-store.js and router/index.js. Data flow: GET /api/auth/me -> mocked envelope -> auth state -> allowed route. */
await page.route('**/api/auth/me', async route => route.fulfill({
contentType: 'application/json',
body: JSON.stringify({ code: 200, message: 'success', data: user })
}));
/** Plain purpose: make protected test requests receive a successful CSRF preflight. Related files: api-client.js and all asset API clients. Data flow: API client -> CSRF request -> mocked success -> test-owned business request mock. */
await page.route('**/api/auth/csrf', async route => route.fulfill({
contentType: 'application/json',
body: JSON.stringify({ code: 200, message: 'success', data: null })
}));
/**
* 每个测试都重新装入 Cookie 与认证路由。
* 不能在模块顶层调用 beforeEach:同一 worker 载入第二个测试文件时模块已缓存,钩子不会再次注册。
*/
export const test = base.extend({
page: async ({ page }, use) => {
await page.context().addCookies([{ name: 'XSRF-TOKEN', value: 'playwright-csrf', domain: '127.0.0.1', path: '/' }]);
await page.route('**/api/auth/me', async route => route.fulfill({
contentType: 'application/json',
body: JSON.stringify({ code: 200, message: 'success', data: developerUser })
}));
await page.route('**/api/auth/csrf', async route => route.fulfill({
contentType: 'application/json',
body: JSON.stringify({ code: 200, message: 'success', data: null })
}));
await use(page);
}
});
export { expect };
import { fileURLToPath } from 'node:url';
import { defineConfig, loadEnv } from 'vite';
/**
......@@ -5,11 +6,13 @@ import { defineConfig, loadEnv } from 'vite';
* 关联文件:frontend/.env.java、frontend/src/router/index.js、frontend/src/modules/phone/phone-api-client.js、backend-nest/src/main.ts。
* 关联逻辑(调用链/消息链/数据流):/asset/#/phone-assets -> Vue Hash 路由;/api/phone-assets -> Vite proxy -> 127.0.0.1:7691 NestJS 接口。
*/
const configDir = fileURLToPath(new URL('.', import.meta.url));
export default defineConfig(({ mode }) => {
// 后端地址优先级:命令行环境变量 > .env 文件 > 默认值。
// 默认连 NestJS(7691);`pnpm dev:java` 走 --mode java 读 .env.java,回退到 Java(7690) 对拍。
const fileEnv = loadEnv(mode, process.cwd(), 'VITE_');
const apiTarget = process.env.VITE_API_TARGET || fileEnv.VITE_API_TARGET || 'http://127.0.0.1:7691';
const fileEnv = loadEnv(mode, configDir, 'VITE_');
const apiTarget = fileEnv.VITE_API_TARGET || 'http://127.0.0.1:7691';
return {
base: '/asset/',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment