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 ...@@ -31,7 +31,6 @@ backend/.mvn/jvm.config
.env .env
# 前端本机后端地址覆盖:只属于个人环境。.env.java 是团队共享的对拍配置,仍进仓库。 # 前端本机后端地址覆盖:只属于个人环境。.env.java 是团队共享的对拍配置,仍进仓库。
.env.local .env.local
frontend/.env.local
.idea/ .idea/
.vite/ .vite/
frontend/.playwright-cli/ frontend/.playwright-cli/
......
...@@ -87,9 +87,11 @@ pnpm dev:java ...@@ -87,9 +87,11 @@ pnpm dev:java
|---|---|---| |---|---|---|
| `vite.config.js` 默认值 | NestJS `7691``pnpm dev` 用它 | 是 | | `vite.config.js` 默认值 | NestJS `7691``pnpm dev` 用它 | 是 |
| `frontend/.env.java` | Java `7690``pnpm dev:java` 用它 | 是 | | `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) ### NestJS 后端(7691)
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
"test:e2e": "playwright test" "test:e2e": "playwright test"
}, },
"engines": { "engines": {
"node": ">=20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
}, },
"packageManager": "pnpm@12.3.4", "packageManager": "pnpm@12.3.4",
"volta": { "volta": {
......
...@@ -3,5 +3,5 @@ import { defineConfig } from '@playwright/test'; ...@@ -3,5 +3,5 @@ import { defineConfig } from '@playwright/test';
export default defineConfig({ export default defineConfig({
testDir: './tests', testDir: './tests',
use: { baseURL: 'http://127.0.0.1:5173', browserName: 'chromium' }, 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'; ...@@ -4,5 +4,5 @@ import { defineConfig } from '@playwright/test';
export default defineConfig({ export default defineConfig({
testDir: './tests', testDir: './tests',
use: { baseURL: 'http://127.0.0.1:5174', browserName: 'chromium' }, 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'; 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 = {
/** 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, id: 1,
username: 'playwright-asset-admin', username: 'playwright-asset-admin',
roleCode: 'DEVELOPER', roleCode: 'DEVELOPER',
...@@ -20,19 +14,25 @@ test.beforeEach(async ({ page }) => { ...@@ -20,19 +14,25 @@ test.beforeEach(async ({ page }) => {
'company-person': 'EDIT', 'company-person': 'EDIT',
alerts: '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. */ /**
* 每个测试都重新装入 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({ await page.route('**/api/auth/me', async route => route.fulfill({
contentType: 'application/json', contentType: 'application/json',
body: JSON.stringify({ code: 200, message: 'success', data: user }) body: JSON.stringify({ code: 200, message: 'success', data: developerUser })
})); }));
/** 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({ await page.route('**/api/auth/csrf', async route => route.fulfill({
contentType: 'application/json', contentType: 'application/json',
body: JSON.stringify({ code: 200, message: 'success', data: null }) body: JSON.stringify({ code: 200, message: 'success', data: null })
})); }));
await use(page);
}
}); });
export { expect }; export { expect };
import { fileURLToPath } from 'node:url';
import { defineConfig, loadEnv } from 'vite'; import { defineConfig, loadEnv } from 'vite';
/** /**
...@@ -5,11 +6,13 @@ 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。 * 关联文件: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 接口。 * 关联逻辑(调用链/消息链/数据流):/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 }) => { export default defineConfig(({ mode }) => {
// 后端地址优先级:命令行环境变量 > .env 文件 > 默认值。 // 后端地址优先级:命令行环境变量 > .env 文件 > 默认值。
// 默认连 NestJS(7691);`pnpm dev:java` 走 --mode java 读 .env.java,回退到 Java(7690) 对拍。 // 默认连 NestJS(7691);`pnpm dev:java` 走 --mode java 读 .env.java,回退到 Java(7690) 对拍。
const fileEnv = loadEnv(mode, process.cwd(), 'VITE_'); const fileEnv = loadEnv(mode, configDir, 'VITE_');
const apiTarget = process.env.VITE_API_TARGET || fileEnv.VITE_API_TARGET || 'http://127.0.0.1:7691'; const apiTarget = fileEnv.VITE_API_TARGET || 'http://127.0.0.1:7691';
return { return {
base: '/asset/', 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