Commit 8e4e7b372bbbb6ede896cf0ab5c7858218c9486f

Authored by zichun
1 parent fbe53713

test(fe-login): 登录页 E2E 关键旅程 REQ-USR-004

.gitignore
... ... @@ -10,6 +10,9 @@ node_modules/
10 10 dist/
11 11 build/
12 12 coverage/
  13 +playwright-report/
  14 +test-results/
  15 +.playwright/
13 16  
14 17 # IDE
15 18 .idea/
... ...
frontend/playwright.config.ts 0 → 100644
  1 +import { defineConfig, devices } from '@playwright/test';
  2 +
  3 +// E2E:起 Vite dev server(端口 5173 = frontend.dev_port),用 page.route 桩后端,不依赖真实后端。
  4 +const PORT = 5173;
  5 +const BASE_URL = `http://localhost:${PORT}`;
  6 +
  7 +export default defineConfig({
  8 + testDir: './tests/e2e',
  9 + fullyParallel: true,
  10 + forbidOnly: !!process.env.CI,
  11 + retries: process.env.CI ? 2 : 0,
  12 + reporter: 'list',
  13 + use: {
  14 + baseURL: BASE_URL,
  15 + trace: 'on-first-retry',
  16 + },
  17 + projects: [
  18 + {
  19 + name: 'chromium',
  20 + use: { ...devices['Desktop Chrome'] },
  21 + },
  22 + ],
  23 + webServer: {
  24 + command: 'npm run dev',
  25 + url: BASE_URL,
  26 + reuseExistingServer: !process.env.CI,
  27 + timeout: 120_000,
  28 + },
  29 +});
... ...
frontend/tests/e2e/login.spec.ts 0 → 100644
  1 +import { test, expect, type Page } from '@playwright/test';
  2 +
  3 +// 桩后端:版本下拉与登录端点(page.route),不依赖真实后端起服。
  4 +async function stubCompanies(page: Page) {
  5 + await page.route('**/api/usr/companies', async (route) => {
  6 + await route.fulfill({
  7 + status: 200,
  8 + contentType: 'application/json',
  9 + body: JSON.stringify({
  10 + code: 0,
  11 + message: 'success',
  12 + data: [
  13 + { id: 1, sCompanyName: '甲公司', sVersion: '标准版' },
  14 + { id: 2, sCompanyName: '乙公司', sVersion: null },
  15 + ],
  16 + }),
  17 + });
  18 + });
  19 +}
  20 +
  21 +test.describe('登录页关键旅程', () => {
  22 + test('loads /login and shows version options', async ({ page }) => {
  23 + await stubCompanies(page);
  24 + await page.goto('/login');
  25 + await expect(page.getByText('用户登录')).toBeVisible();
  26 + // 打开版本下拉,应渲染桩返回项
  27 + await page.getByRole('combobox').click();
  28 + await expect(page.getByText('甲公司(标准版)')).toBeVisible();
  29 + await expect(page.getByText('乙公司', { exact: true })).toBeVisible();
  30 + });
  31 +
  32 + test('blocks submit with validation when empty', async ({ page }) => {
  33 + await stubCompanies(page);
  34 + let loginCalled = false;
  35 + await page.route('**/api/usr/login', async (route) => {
  36 + loginCalled = true;
  37 + await route.fulfill({
  38 + status: 200,
  39 + contentType: 'application/json',
  40 + body: JSON.stringify({ code: 0, message: 'success', data: {} }),
  41 + });
  42 + });
  43 + await page.goto('/login');
  44 + await page.getByRole('button', { name: /登\s*录/ }).click();
  45 + await expect(page.getByText('请输入用户名')).toBeVisible();
  46 + await expect(page.getByText('请输入密码')).toBeVisible();
  47 + expect(loginCalled).toBe(false);
  48 + });
  49 +
  50 + test('successful login navigates away from /login', async ({ page }) => {
  51 + await stubCompanies(page);
  52 + await page.route('**/api/usr/login', async (route) => {
  53 + await route.fulfill({
  54 + status: 200,
  55 + contentType: 'application/json',
  56 + body: JSON.stringify({
  57 + code: 0,
  58 + message: 'success',
  59 + data: {
  60 + token: 'tk-e2e',
  61 + user: { id: 1, sUserName: 'admin', sUserType: '超级管理员', sLanguage: '中文' },
  62 + },
  63 + }),
  64 + });
  65 + });
  66 + await page.goto('/login');
  67 + await page.getByPlaceholder('请输入你的用户名').fill('admin');
  68 + await page.getByPlaceholder('请输入你的密码').fill('secret');
  69 + // 选版本
  70 + await page.getByRole('combobox').click();
  71 + await page.getByText('甲公司(标准版)').click();
  72 + await page.getByRole('button', { name: /登\s*录/ }).click();
  73 + await expect(page.getByText('登录成功')).toBeVisible();
  74 + await expect(page).not.toHaveURL(/\/login$/);
  75 + });
  76 +
  77 + test('failed login stays on /login with error', async ({ page }) => {
  78 + await stubCompanies(page);
  79 + await page.route('**/api/usr/login', async (route) => {
  80 + await route.fulfill({
  81 + status: 200,
  82 + contentType: 'application/json',
  83 + body: JSON.stringify({ code: 40101, message: '认证失败', data: null }),
  84 + });
  85 + });
  86 + await page.goto('/login');
  87 + await page.getByPlaceholder('请输入你的用户名').fill('admin');
  88 + await page.getByPlaceholder('请输入你的密码').fill('wrong');
  89 + await page.getByRole('combobox').click();
  90 + await page.getByText('甲公司(标准版)').click();
  91 + await page.getByRole('button', { name: /登\s*录/ }).click();
  92 + await expect(page.getByText('用户名或密码错误')).toBeVisible();
  93 + await expect(page).toHaveURL(/\/login$/);
  94 + });
  95 +});
... ...