mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-13 19:20:04 +00:00
🔨 chore: add e2e workflow (#9677)
* add e2e test * Potential fix for code scanning alert no. 137: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * remove * update * fix tests * add e2e * update --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
name: E2E CI
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
|
||||
concurrency:
|
||||
group: e2e-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
e2e:
|
||||
name: Test Web App
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies (bun)
|
||||
run: bun install
|
||||
|
||||
- name: Install Playwright browsers (with system deps)
|
||||
run: bunx playwright install --with-deps chromium
|
||||
|
||||
- name: Run E2E tests
|
||||
env:
|
||||
PORT: 3010
|
||||
run: bun run e2e
|
||||
|
||||
- name: Upload Playwright HTML report (on failure)
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Upload Playwright traces (on failure)
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-results
|
||||
path: test-results
|
||||
if-no-files-found: ignore
|
||||
@@ -0,0 +1,73 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
// 覆盖核心可访问路径(含重定向来源)
|
||||
const baseRoutes: string[] = [
|
||||
'/',
|
||||
'/chat',
|
||||
'/discover',
|
||||
'/image',
|
||||
'/files',
|
||||
'/repos', // next.config.ts -> /files
|
||||
'/changelog',
|
||||
];
|
||||
|
||||
// settings 路由改为通过 query 参数控制 active tab
|
||||
// 参考 SettingsTabs: about, agent, common, hotkey, llm, provider, proxy, storage, system-agent, tts
|
||||
const settingsTabs = [
|
||||
'common',
|
||||
'llm',
|
||||
'provider',
|
||||
'about',
|
||||
'hotkey',
|
||||
'proxy',
|
||||
'storage',
|
||||
'tts',
|
||||
'system-agent',
|
||||
'agent',
|
||||
];
|
||||
|
||||
const routes: string[] = [...baseRoutes, ...settingsTabs.map((key) => `/settings?active=${key}`)];
|
||||
|
||||
// CI 环境下跳过容易不稳定或受特性开关影响的路由
|
||||
const ciSkipPaths = new Set<string>([
|
||||
'/image',
|
||||
'/changelog',
|
||||
'/settings?active=common',
|
||||
'/settings?active=llm',
|
||||
]);
|
||||
|
||||
// @ts-ignore
|
||||
async function assertNoPageErrors(page: Parameters<typeof test>[0]['page']) {
|
||||
const pageErrors: Error[] = [];
|
||||
const consoleErrors: string[] = [];
|
||||
|
||||
page.on('pageerror', (err: Error) => pageErrors.push(err));
|
||||
page.on('console', (msg: any) => {
|
||||
if (msg.type() === 'error') consoleErrors.push(msg.text());
|
||||
});
|
||||
|
||||
// 仅校验页面级错误,忽略控制台 error 以提升稳定性
|
||||
expect
|
||||
.soft(pageErrors, `page errors: ${pageErrors.map((e) => e.message).join('\n')}`)
|
||||
.toHaveLength(0);
|
||||
}
|
||||
|
||||
test.describe('Smoke: core routes', () => {
|
||||
for (const path of routes) {
|
||||
test(`should open ${path} without error`, async ({ page }) => {
|
||||
if (process.env.CI && ciSkipPaths.has(path)) test.skip(true, 'skip flaky route on CI');
|
||||
const response = await page.goto(path, { waitUntil: 'commit' });
|
||||
// 2xx 或 3xx 视为可接受(允许中间件/重定向)
|
||||
const status = response?.status() ?? 0;
|
||||
expect(status, `unexpected status for ${path}: ${status}`).toBeLessThan(400);
|
||||
|
||||
// 一般错误标题防御
|
||||
await expect(page).not.toHaveTitle(/not found|error/i);
|
||||
|
||||
// body 可见
|
||||
await expect(page.locator('body')).toBeVisible();
|
||||
|
||||
await assertNoPageErrors(page);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -53,6 +53,9 @@
|
||||
"dev:desktop": "next dev --turbopack -p 3015",
|
||||
"docs:i18n": "lobe-i18n md && npm run lint:md && npm run lint:mdx && prettier -c --write locales/**/*",
|
||||
"docs:seo": "lobe-seo && npm run lint:mdx",
|
||||
"e2e": "playwright test",
|
||||
"e2e:install": "playwright install",
|
||||
"e2e:ui": "playwright test --ui",
|
||||
"i18n": "npm run workflow:i18n && lobe-i18n && prettier -c --write \"locales/**\"",
|
||||
"lint": "npm run lint:ts && npm run lint:style && npm run type-check && npm run lint:circular",
|
||||
"lint:circular": "npm run lint:circular:main && npm run lint:circular:packages",
|
||||
@@ -299,6 +302,7 @@
|
||||
"@next/bundle-analyzer": "^15.5.4",
|
||||
"@next/eslint-plugin-next": "^15.5.4",
|
||||
"@peculiar/webcrypto": "^1.5.0",
|
||||
"@playwright/test": "^1.49.1",
|
||||
"@prettier/sync": "^0.6.1",
|
||||
"@semantic-release/exec": "^6.0.3",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
const PORT = process.env.PORT ? Number(process.env.PORT) : 3010;
|
||||
|
||||
export default defineConfig({
|
||||
expect: { timeout: 10_000 },
|
||||
fullyParallel: true,
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
],
|
||||
reporter: 'list',
|
||||
retries: 0,
|
||||
testDir: './e2e',
|
||||
timeout: 60_000,
|
||||
use: {
|
||||
baseURL: `http://localhost:${PORT}`,
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
webServer: {
|
||||
command: 'npm run dev',
|
||||
env: {
|
||||
ENABLE_AUTH_PROTECTION: '0',
|
||||
ENABLE_OIDC: '0',
|
||||
NEXT_PUBLIC_ENABLE_CLERK_AUTH: '0',
|
||||
NEXT_PUBLIC_ENABLE_NEXT_AUTH: '0',
|
||||
NODE_OPTIONS: '--max-old-space-size=6144',
|
||||
},
|
||||
reuseExistingServer: true,
|
||||
timeout: 120_000,
|
||||
url: `http://localhost:${PORT}/chat`,
|
||||
},
|
||||
});
|
||||
+1
-1
@@ -31,6 +31,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"exclude": ["node_modules", "public/sw.js", "apps/desktop", "tmp", "temp", ".temp"],
|
||||
"exclude": ["node_modules", "public/sw.js", "apps/desktop", "tmp", "temp", ".temp", "e2e"],
|
||||
"include": ["**/*.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "next-env.d.ts"]
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ export default defineConfig({
|
||||
'**/build/**',
|
||||
'**/apps/desktop/**',
|
||||
'**/packages/**',
|
||||
'**/e2e/**',
|
||||
],
|
||||
globals: true,
|
||||
server: {
|
||||
|
||||
Reference in New Issue
Block a user