test(e2e): add frontend smoke coverage
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
name: Frontend E2E
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
playwright:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
container:
|
||||||
|
image: mcr.microsoft.com/playwright:v1.63.0-noble
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: sh
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
env:
|
||||||
|
SERVER_URL: ${{ github.server_url }}
|
||||||
|
REPOSITORY: ${{ github.repository }}
|
||||||
|
COMMIT_SHA: ${{ github.sha }}
|
||||||
|
GIT_USERNAME: ${{ github.actor }}
|
||||||
|
GIT_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
case "$SERVER_URL" in
|
||||||
|
http://*)
|
||||||
|
AUTH_SERVER_URL="http://${GIT_USERNAME}:${GIT_TOKEN}@${SERVER_URL#http://}"
|
||||||
|
;;
|
||||||
|
https://*)
|
||||||
|
AUTH_SERVER_URL="https://${GIT_USERNAME}:${GIT_TOKEN}@${SERVER_URL#https://}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
AUTH_SERVER_URL="$SERVER_URL"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ ! -d .git ]; then
|
||||||
|
git init .
|
||||||
|
fi
|
||||||
|
|
||||||
|
if git remote get-url origin >/dev/null 2>&1; then
|
||||||
|
git remote set-url origin "${AUTH_SERVER_URL}/${REPOSITORY}.git"
|
||||||
|
else
|
||||||
|
git remote add origin "${AUTH_SERVER_URL}/${REPOSITORY}.git"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git fetch --depth=1 origin "$COMMIT_SHA"
|
||||||
|
git checkout --force --detach FETCH_HEAD
|
||||||
|
git clean -ffdx
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run Playwright tests
|
||||||
|
env:
|
||||||
|
CI: "true"
|
||||||
|
run: npm run test:e2e
|
||||||
@@ -8,6 +8,9 @@
|
|||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
|
/playwright-report/
|
||||||
|
/test-results/
|
||||||
|
/e2e/.auth/
|
||||||
|
|
||||||
# next.js
|
# next.js
|
||||||
/.next/
|
/.next/
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ npm run dev
|
|||||||
npm run lint
|
npm run lint
|
||||||
npm test
|
npm test
|
||||||
npm run test:coverage
|
npm run test:coverage
|
||||||
|
npm run test:e2e
|
||||||
npm run build
|
npm run build
|
||||||
npm run start
|
npm run start
|
||||||
docker build -t tjwater-frontend:local .
|
docker build -t tjwater-frontend:local .
|
||||||
@@ -52,6 +53,7 @@ docker build -t tjwater-frontend:local .
|
|||||||
- `npm run lint`:运行 ESLint。
|
- `npm run lint`:运行 ESLint。
|
||||||
- `npm test`:运行 Jest。
|
- `npm test`:运行 Jest。
|
||||||
- `npm run test:coverage`:生成测试覆盖率。
|
- `npm run test:coverage`:生成测试覆盖率。
|
||||||
|
- `npm run test:e2e`:启动本地 Next.js 与 Playwright Chromium 烟测。
|
||||||
- `npm run build`:生成生产构建。
|
- `npm run build`:生成生产构建。
|
||||||
- `npm run start`:启动生产模式服务。
|
- `npm run start`:启动生产模式服务。
|
||||||
|
|
||||||
@@ -86,6 +88,36 @@ npm run build
|
|||||||
|
|
||||||
Gitea 包工作流位于 `.gitea/workflows/package.yml`,通常由 tag 触发构建和推送镜像。
|
Gitea 包工作流位于 `.gitea/workflows/package.yml`,通常由 tag 触发构建和推送镜像。
|
||||||
|
|
||||||
|
### Playwright E2E
|
||||||
|
|
||||||
|
首次运行先安装 Chromium:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run e2e:install
|
||||||
|
npm run test:e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
本地测试会自动构建生产版本并启动隔离的 `http://127.0.0.1:3100`,生成仅用于测试的 NextAuth
|
||||||
|
会话,并模拟后端 API,因此不要求启动 Keycloak、Server 或 Agent。失败时可通过
|
||||||
|
`npm run test:e2e:report` 查看 HTML 报告,交互调试可使用
|
||||||
|
`npm run test:e2e:ui` 或 `npm run test:e2e:debug`。
|
||||||
|
|
||||||
|
在不能访问 Playwright CDN 的内网环境,可设置
|
||||||
|
`E2E_CHROMIUM_PATH=/absolute/path/to/chrome` 复用预装的 Chromium/Chrome。
|
||||||
|
|
||||||
|
若要对已部署环境运行测试,请传入环境地址和预先保存的管理员 Playwright 登录状态:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
E2E_BASE_URL=https://example.test \
|
||||||
|
E2E_STORAGE_STATE=/absolute/path/to/storage-state.json \
|
||||||
|
E2E_USE_REAL_SERVICES=true \
|
||||||
|
npm run test:e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
`E2E_BASE_URL` 会关闭本地开发服务器;`E2E_STORAGE_STATE` 避免在仓库中保存账号或
|
||||||
|
会话;`E2E_USE_REAL_SERVICES=true` 会关闭 API 模拟。Gitea 的
|
||||||
|
`.gitea/workflows/e2e.yml` 在分支推送和 PR 中运行同一组 Chromium 测试。
|
||||||
|
|
||||||
## 安全规则
|
## 安全规则
|
||||||
|
|
||||||
不要提交 `.env`、`.next/`、`node_modules/`、本地缓存、私有地图/API token、客户数据或部署密钥。CI/CD 凭据应放在 Gitea secrets 中。
|
不要提交 `.env`、`.next/`、`node_modules/`、本地缓存、私有地图/API token、客户数据或部署密钥。CI/CD 凭据应放在 Gitea secrets 中。
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
import { mockBackend } from "./support/mockBackend";
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await mockBackend(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("已登录管理员可以打开审计日志页面", async ({ page }) => {
|
||||||
|
await page.goto("/audit-logs");
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole("heading", { name: "审计日志", exact: true }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("系统管理员权限已验证")).toBeVisible();
|
||||||
|
await expect(page.getByRole("heading", { name: "查询结果" })).toBeVisible();
|
||||||
|
});
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import fs from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import { encode } from "next-auth/jwt";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AUTH_STATE_PATH,
|
||||||
|
E2E_AUTH_SECRET,
|
||||||
|
E2E_BASE_URL,
|
||||||
|
} from "./support/environment";
|
||||||
|
|
||||||
|
const SESSION_MAX_AGE_SECONDS = 12 * 60 * 60;
|
||||||
|
|
||||||
|
export default async function globalSetup() {
|
||||||
|
if (process.env.E2E_STORAGE_STATE) return;
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const baseUrl = new URL(E2E_BASE_URL);
|
||||||
|
const secure = baseUrl.protocol === "https:";
|
||||||
|
const token = await encode({
|
||||||
|
secret: E2E_AUTH_SECRET,
|
||||||
|
maxAge: SESSION_MAX_AGE_SECONDS,
|
||||||
|
token: {
|
||||||
|
sub: "playwright-user",
|
||||||
|
username: "playwright",
|
||||||
|
name: "E2E 测试用户",
|
||||||
|
email: "playwright@example.invalid",
|
||||||
|
accessToken: "playwright-access-token",
|
||||||
|
accessTokenIssuedAt: now,
|
||||||
|
accessTokenExpires: now + SESSION_MAX_AGE_SECONDS * 1000,
|
||||||
|
sessionExpiresAt: now + SESSION_MAX_AGE_SECONDS * 1000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await fs.mkdir(path.dirname(AUTH_STATE_PATH), { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
AUTH_STATE_PATH,
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
cookies: [
|
||||||
|
{
|
||||||
|
name: secure
|
||||||
|
? "__Secure-next-auth.session-token"
|
||||||
|
: "next-auth.session-token",
|
||||||
|
value: token,
|
||||||
|
domain: baseUrl.hostname,
|
||||||
|
path: "/",
|
||||||
|
expires: Math.floor(now / 1000) + SESSION_MAX_AGE_SECONDS,
|
||||||
|
httpOnly: true,
|
||||||
|
secure,
|
||||||
|
sameSite: "Lax",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
origins: [
|
||||||
|
{
|
||||||
|
origin: baseUrl.origin,
|
||||||
|
localStorage: [
|
||||||
|
{ name: "active_project", value: "playwright-project" },
|
||||||
|
{ name: "MAP_WORKSPACE", value: "tjwater_e2e" },
|
||||||
|
{ name: "NETWORK_NAME", value: "tjwater_e2e" },
|
||||||
|
{
|
||||||
|
name: "MAP_EXTENT",
|
||||||
|
value: "13508801.93,3608163.35,13555650.64,3633685.14",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
export const E2E_BASE_URL =
|
||||||
|
process.env.E2E_BASE_URL || "http://127.0.0.1:3100";
|
||||||
|
|
||||||
|
export const E2E_AUTH_SECRET =
|
||||||
|
process.env.E2E_NEXTAUTH_SECRET ||
|
||||||
|
"tjwater-playwright-local-secret-at-least-32-characters";
|
||||||
|
|
||||||
|
export const AUTH_STATE_PATH = path.join(
|
||||||
|
process.cwd(),
|
||||||
|
"e2e",
|
||||||
|
".auth",
|
||||||
|
"user.json",
|
||||||
|
);
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import type { Page, Route } from "@playwright/test";
|
||||||
|
|
||||||
|
const json = (route: Route, body: unknown) =>
|
||||||
|
route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: "application/json",
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const mockBackend = async (page: Page) => {
|
||||||
|
if (process.env.E2E_USE_REAL_SERVICES === "true") return;
|
||||||
|
|
||||||
|
await page.route("**/api/v1/**", async (route) => {
|
||||||
|
const url = new URL(route.request().url());
|
||||||
|
|
||||||
|
if (url.pathname === "/api/v1/access-context") {
|
||||||
|
return json(route, {
|
||||||
|
user_id: "playwright-user",
|
||||||
|
username: "playwright",
|
||||||
|
system_role: "system_admin",
|
||||||
|
is_system_admin: true,
|
||||||
|
project_id: "playwright-project",
|
||||||
|
project_role: "project_admin",
|
||||||
|
permissions: [
|
||||||
|
"webgis.view",
|
||||||
|
"simulation.view",
|
||||||
|
"simulation.run",
|
||||||
|
"scada.clean",
|
||||||
|
"risk.run",
|
||||||
|
"optimization.run",
|
||||||
|
"burst.run",
|
||||||
|
"audit.view",
|
||||||
|
"environment.manage",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.pathname === "/api/v1/projects/current") {
|
||||||
|
return json(route, {
|
||||||
|
project_id: "playwright-project",
|
||||||
|
code: "tjwater_e2e",
|
||||||
|
gs_workspace: "tjwater_e2e",
|
||||||
|
map_extent: {
|
||||||
|
bbox: [13508801.93, 3608163.35, 13555650.64, 3633685.14],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.pathname === "/api/v1/audit-logs/count") {
|
||||||
|
return json(route, { count: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
url.pathname === "/api/v1/audit-logs" ||
|
||||||
|
url.pathname === "/api/v1/admin/users" ||
|
||||||
|
url.pathname === "/api/v1/admin/projects"
|
||||||
|
) {
|
||||||
|
return json(route, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
return json(route, {});
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
test("未登录访问受保护页面时返回登录重定向", async ({ request }) => {
|
||||||
|
const sessionResponse = await request.get("/api/auth/session");
|
||||||
|
expect(await sessionResponse.json()).toEqual({});
|
||||||
|
|
||||||
|
const response = await request.get("/audit-logs", { maxRedirects: 0 });
|
||||||
|
const body = await response.text();
|
||||||
|
|
||||||
|
expect(response.status()).toBe(200);
|
||||||
|
expect(body).toContain("NEXT_REDIRECT;replace;/login;307;");
|
||||||
|
});
|
||||||
Generated
+46
@@ -48,6 +48,7 @@
|
|||||||
"zustand": "^5.0.11"
|
"zustand": "^5.0.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.63.0",
|
||||||
"@svgr/webpack": "^8.1.0",
|
"@svgr/webpack": "^8.1.0",
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
@@ -5710,6 +5711,22 @@
|
|||||||
"url": "https://opencollective.com/pkgr"
|
"url": "https://opencollective.com/pkgr"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@playwright/test": {
|
||||||
|
"version": "1.63.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz",
|
||||||
|
"integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright": "1.63.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@popperjs/core": {
|
"node_modules/@popperjs/core": {
|
||||||
"version": "2.11.8",
|
"version": "2.11.8",
|
||||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||||
@@ -19080,6 +19097,35 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/playwright": {
|
||||||
|
"version": "1.63.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz",
|
||||||
|
"integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright-core": "1.63.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright-core": {
|
||||||
|
"version": "1.63.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz",
|
||||||
|
"integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"playwright-core": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pluralize": {
|
"node_modules/pluralize": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
|
||||||
|
|||||||
@@ -14,6 +14,12 @@
|
|||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:coverage": "jest --coverage",
|
"test:coverage": "jest --coverage",
|
||||||
|
"test:e2e": "playwright test",
|
||||||
|
"test:e2e:ui": "playwright test --ui",
|
||||||
|
"test:e2e:debug": "playwright test --debug",
|
||||||
|
"test:e2e:report": "playwright show-report",
|
||||||
|
"e2e:install": "playwright install chromium",
|
||||||
|
"e2e:serve": "node scripts/startE2eServer.mjs",
|
||||||
"api:generate": "openapi-typescript contracts/server-v1.openapi.json -o src/generated/serverApi.ts && openapi-typescript contracts/agent-v1.openapi.json -o src/generated/agentApi.ts",
|
"api:generate": "openapi-typescript contracts/server-v1.openapi.json -o src/generated/serverApi.ts && openapi-typescript contracts/agent-v1.openapi.json -o src/generated/agentApi.ts",
|
||||||
"api:check": "node scripts/check-api-contracts.mjs",
|
"api:check": "node scripts/check-api-contracts.mjs",
|
||||||
"pipeline:trigger": "bash scripts/trigger-gitea-pipeline.sh"
|
"pipeline:trigger": "bash scripts/trigger-gitea-pipeline.sh"
|
||||||
@@ -64,6 +70,7 @@
|
|||||||
"sharp": "0.35.3"
|
"sharp": "0.35.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.63.0",
|
||||||
"@svgr/webpack": "^8.1.0",
|
"@svgr/webpack": "^8.1.0",
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AUTH_STATE_PATH,
|
||||||
|
E2E_AUTH_SECRET,
|
||||||
|
E2E_BASE_URL,
|
||||||
|
} from "./e2e/support/environment";
|
||||||
|
|
||||||
|
const externalServer = Boolean(process.env.E2E_BASE_URL);
|
||||||
|
const storageState = process.env.E2E_STORAGE_STATE || AUTH_STATE_PATH;
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: "./e2e",
|
||||||
|
fullyParallel: true,
|
||||||
|
forbidOnly: Boolean(process.env.CI),
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
workers: process.env.CI ? 1 : undefined,
|
||||||
|
expect: { timeout: 30_000 },
|
||||||
|
reporter: process.env.CI
|
||||||
|
? [["line"], ["html", { open: "never" }]]
|
||||||
|
: [["list"], ["html", { open: "never" }]],
|
||||||
|
outputDir: "test-results",
|
||||||
|
globalSetup: "./e2e/globalSetup.ts",
|
||||||
|
use: {
|
||||||
|
baseURL: E2E_BASE_URL,
|
||||||
|
locale: "zh-CN",
|
||||||
|
timezoneId: "Asia/Shanghai",
|
||||||
|
launchOptions: process.env.E2E_CHROMIUM_PATH
|
||||||
|
? { executablePath: process.env.E2E_CHROMIUM_PATH }
|
||||||
|
: undefined,
|
||||||
|
trace: "retain-on-failure",
|
||||||
|
screenshot: "only-on-failure",
|
||||||
|
video: "retain-on-failure",
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: "chromium-public",
|
||||||
|
testMatch: /unauthenticated\.spec\.ts/,
|
||||||
|
use: { ...devices["Desktop Chrome"] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "chromium-authenticated",
|
||||||
|
testIgnore: /unauthenticated\.spec\.ts/,
|
||||||
|
use: {
|
||||||
|
...devices["Desktop Chrome"],
|
||||||
|
storageState,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
webServer: externalServer
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
command:
|
||||||
|
"npm run runtime:config && npm run build && npm run e2e:serve",
|
||||||
|
url: E2E_BASE_URL,
|
||||||
|
reuseExistingServer: false,
|
||||||
|
timeout: 120_000,
|
||||||
|
env: {
|
||||||
|
NEXTAUTH_URL: E2E_BASE_URL,
|
||||||
|
NEXTAUTH_SECRET: E2E_AUTH_SECRET,
|
||||||
|
KEYCLOAK_CLIENT_ID: "tjwater-e2e",
|
||||||
|
KEYCLOAK_CLIENT_SECRET: "tjwater-e2e",
|
||||||
|
KEYCLOAK_ISSUER: "http://127.0.0.1:8180/realms/tjwater-e2e",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { spawn } from "node:child_process";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
const projectDir = process.cwd();
|
||||||
|
const standaloneDir = path.join(projectDir, ".next", "standalone");
|
||||||
|
const standalonePublicDir = path.join(standaloneDir, "public");
|
||||||
|
const standaloneStaticDir = path.join(standaloneDir, ".next", "static");
|
||||||
|
|
||||||
|
fs.rmSync(standalonePublicDir, { recursive: true, force: true });
|
||||||
|
fs.rmSync(standaloneStaticDir, { recursive: true, force: true });
|
||||||
|
fs.cpSync(path.join(projectDir, "public"), standalonePublicDir, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
fs.mkdirSync(path.dirname(standaloneStaticDir), { recursive: true });
|
||||||
|
fs.cpSync(path.join(projectDir, ".next", "static"), standaloneStaticDir, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const server = spawn(process.execPath, [path.join(standaloneDir, "server.js")], {
|
||||||
|
cwd: standaloneDir,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
HOSTNAME: "127.0.0.1",
|
||||||
|
PORT: "3100",
|
||||||
|
},
|
||||||
|
stdio: "inherit",
|
||||||
|
});
|
||||||
|
|
||||||
|
const stopServer = (signal) => {
|
||||||
|
if (!server.killed) server.kill(signal);
|
||||||
|
};
|
||||||
|
|
||||||
|
process.once("SIGINT", () => stopServer("SIGINT"));
|
||||||
|
process.once("SIGTERM", () => stopServer("SIGTERM"));
|
||||||
|
server.once("exit", (code, signal) => {
|
||||||
|
process.exitCode = signal ? 1 : (code ?? 1);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user