userlist.spec.ts 5.92 KB
import { test, expect, type Page } from '@playwright/test';

// 桩用户列表响应工厂
function usersBody(records: unknown[], total: number, pageNum = 1, pageSize = 10) {
  return JSON.stringify({
    code: 0,
    message: 'success',
    data: { records, total, pageNum, pageSize },
  });
}

function makeUser(id: number, name: string, over: Record<string, unknown> = {}) {
  return {
    id,
    sUserName: name,
    员工名: null,
    sUserNo: `U00${id}`,
    部门: '技术部',
    sUserType: '普通用户',
    sLanguage: '中文',
    iIsVoid: 0,
    tLastLoginDate: null,
    sCreator: 'admin',
    tCreateDate: '2024-01-01T00:00:00',
    ...over,
  };
}

// 桩登录 / 版本下拉(沿用 shell.spec.ts 模式)。用户列表由各用例单独桩。
async function stubAuth(page: Page) {
  await page.route('**/api/usr/companies', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        code: 0,
        message: 'success',
        data: [{ id: 1, sCompanyName: '甲公司', sVersion: '标准版' }],
      }),
    });
  });
  await page.route('**/api/usr/login', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        code: 0,
        message: 'success',
        data: {
          token: 'tk-e2e',
          user: { id: 1, sUserName: '朱子纯', sUserType: '超级管理员', sLanguage: '中文' },
        },
      }),
    });
  });
}

async function login(page: Page) {
  await page.goto('/login');
  await page.getByPlaceholder('请输入你的用户名').fill('admin');
  await page.getByPlaceholder('请输入你的密码').fill('secret');
  await expect(page.getByText('甲公司(标准版)')).toBeVisible();
  await page.getByRole('button', { name: /登\s*录/ }).click();
  await expect(page).toHaveURL(/\/$/);
}

async function gotoUserList(page: Page) {
  await page.getByRole('button', { name: '用户列表' }).click();
  await expect(page).toHaveURL(/\/usr\/users$/);
}

test.describe('用户列表关键旅程', () => {
  test('enter user list renders rows', async ({ page }) => {
    await stubAuth(page);
    await page.route('**/api/usr/users**', async (route) => {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: usersBody([makeUser(1, '李雷'), makeUser(2, '韩梅梅')], 2),
      });
    });
    await login(page);
    await gotoUserList(page);
    await expect(page.getByText('李雷')).toBeVisible();
    await expect(page.getByText('共 2 条记录')).toBeVisible();
  });

  test('empty result shows 暂无匹配的用户', async ({ page }) => {
    await stubAuth(page);
    await page.route('**/api/usr/users**', async (route) => {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: usersBody([], 0),
      });
    });
    await login(page);
    await gotoUserList(page);
    await expect(page.getByText('暂无匹配的用户')).toBeVisible();
  });

  test('search by value triggers query', async ({ page }) => {
    await stubAuth(page);
    await page.route('**/api/usr/users**', async (route) => {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: usersBody([makeUser(3, '王搜索')], 1),
      });
    });
    await login(page);
    await gotoUserList(page);
    const input = page.getByTestId('filter-query-value').locator('input');
    await input.fill('王');
    const reqPromise = page.waitForRequest((req) =>
      req.url().includes('/api/usr/users') && req.url().includes('queryValue'),
    );
    await page.getByTestId('btn-search').click();
    const req = await reqPromise;
    expect(decodeURIComponent(req.url())).toContain('queryValue=王');
    await expect(page.getByText('王搜索')).toBeVisible();
  });

  test('pagination next page refetches with pageNum=2', async ({ page }) => {
    await stubAuth(page);
    await page.route('**/api/usr/users**', async (route) => {
      const url = route.request().url();
      const pageNum = url.includes('pageNum=2') ? 2 : 1;
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: usersBody([makeUser(pageNum, `行${pageNum}`)], 30, pageNum, 10),
      });
    });
    await login(page);
    await gotoUserList(page);
    await expect(page.getByText('行1')).toBeVisible();
    const reqPromise = page.waitForRequest((req) =>
      req.url().includes('/api/usr/users') && req.url().includes('pageNum=2'),
    );
    await page.getByTitle('下一页').click();
    await reqPromise;
    await expect(page.getByText('行2')).toBeVisible();
  });

  test('double click row navigates to user detail', async ({ page }) => {
    await stubAuth(page);
    await page.route('**/api/usr/users**', async (route) => {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: usersBody([makeUser(88, '详情用户')], 1),
      });
    });
    await login(page);
    await gotoUserList(page);
    await page.getByText('详情用户').dblclick();
    await expect(page).toHaveURL(/\/usr\/users\/88$/);
  });

  test('error response shows retry then recovers', async ({ page }) => {
    await stubAuth(page);
    let fail = true;
    await page.route('**/api/usr/users**', async (route) => {
      if (fail) {
        await route.fulfill({ status: 500, contentType: 'application/json', body: '{}' });
      } else {
        await route.fulfill({
          status: 200,
          contentType: 'application/json',
          body: usersBody([makeUser(1, '恢复用户')], 1),
        });
      }
    });
    await login(page);
    await gotoUserList(page);
    await expect(page.getByText('加载失败,点击重试')).toBeVisible();
    fail = false;
    await page.getByTestId('userlist-error').getByRole('button', { name: '点击重试' }).click();
    await expect(page.getByText('恢复用户')).toBeVisible();
  });
});