Skip to content

前端自动化测试

现状

技术选型

技术选择:jest+@testing-library/react+playwright

bash
yarn add -D jest @playwright/test babel-jest jest-environment-jsdom react-test-renderer @testing-library/react @types/jest

cypress 使用中的问题

流程与规范

目录与命名规范

  • 单元测试一般放在对应目录的 __tests__文件夹下,命名为测试代码的文件名中加 .test
  • e2e 测试放在根目录 e2e 下,命名 xxxx.test.ts

组件测试

typescript
import { render } from '@testing-library/react';
import IncPrice from '.';

jest.mock('antd-mobile/es/utils/native-props', () => {
  return {
    withNativeProps: (props, el) => el,
  };
});

describe('价格组件', () => {
  it('1+100=>1积分+1.00元', () => {
    const { container } = render(<IncPrice integral={1} currency={100} />);
    expect(container.textContent).toBe('1积分+1.00元');
  });
  it('0+100=>1.00元', () => {
    const { container } = render(<IncPrice integral={0} currency={100} />);
    expect(container.textContent).toBe('1.00元');
  });
  it('1=>1积分', () => {
    const { container } = render(<IncPrice integral={1} currency={0} />);
    expect(container.textContent).toBe('1积分');
  });
  it('0+0=>免费', () => {
    const { container } = render(<IncPrice integral={0} currency={0} />);
    expect(container.textContent).toBe('免费');
  });
});

可以使用 chatGPT 生成基础测试代码

e2e 测试

typescript
import { test } from '@playwright/test';

test.beforeEach(async ({ page }) => {
  await page.goto('https://127.0.0.1:10086');
});

test.describe('下单', () => {
  test('券码下单流程', async ({ page }) => {
    const goodsName = '券码';
    await page.getByPlaceholder('请输入关键词').fill(goodsName);
    await page.keyboard.press('Enter');
    await page.getByText(goodsName).last().click();
    // 商品详情页
    await page.getByRole('button', { name: '立即兑换' }).click();
    // 确认订单页
    await page.getByRole('button', { name: '提交订单' }).click();
    // 支付结果页
    await page.waitForSelector('text=支付成功');
  });
});

GitLab 工作流集成

yml
stages:
  - test
  - deploy

.testTpl:
  rules:
    # 提交默认分支时触发
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: always

test:
  extends: .testTpl
  image: mcr.microsoft.com/playwright:v1.31.0-focal
  stage: test
  script:
    - 'yarn'
    # jest --ci --coverage --runInBand --reporters=default --reporters=jest-junit
    - 'yarn test:ci'
    # playwright test ; node ./post-message.js  用于发消息到企微
    - 'yarn test:e2e:ci'
  artifacts:
    reports:
      cobertura: coverage/cobertura-coverage.xml
      junit:
        # 单测报告
        - junit.xml
        # e2e报告
        - je2e.xml
    paths:
      - coverage
      - playwright-report
      - test

pages:
  extends: .testTpl
  stage: deploy
  dependencies:
    - test
  script:
    # 部署单测报告到 GitLab Pages
    - mv coverage/ public/
  artifacts:
    paths:
      - public

post-message.js 部分代码

js
async function main() {
  // 读取e2e报告文件
  if (fs.existsSync('je2e.xml')) {
    const xml = fs.readFileSync('je2e.xml', 'utf8').toString();
    const result = await parser.parseStringPromise(xml);
    const { testsuites } = result;
    const { time, tests, failures } = testsuites.$;
    // 拼接消息并发送到企微
    const msg = `总数: ${tests}  失败: ${failures}  耗时: ${Math.floor(time)}s\n\n
   ${testsuites.testsuite
     .map(
       (m) => `${m.$.name}\n
       ${m.testcase
         .map(
           (c) =>
             `>${c.failure ? '<font color="warning">✕</font>' : '<font color="info">✓</font>'} ${
               c.$.name
             } (${c.$.time * 1000} ms)`,
         )
         .join('\n')}`,
     )
     .join('\n')}\n\n流水线地址:${process.env.CI_PIPELINE_URL}`;

    postMsgToQw(msg);
  }
}

完整例子:https://github.com/dobble11/daydayup-playground/tree/main/vite-react-playwright