当前位置: 首页 > news >正文

微信小程序自动化测试实战:Jest单元测试与Playwright E2E环境搭建

1. 项目概述:为什么小程序测试环境如此重要?

最近在带一个新人团队做小程序项目,上线前出了个不大不小的线上问题:一个核心的优惠券计算函数,在特定边界条件下会返回错误金额。复盘时发现,这个bug在开发过程中完全没被发现,原因很简单——我们只做了功能的手动点击测试,没人去覆盖所有可能的输入组合。这件事让我下定决心,必须把单元测试和E2E测试的配置作为每个小程序项目的标准起手式。

你可能觉得,小程序页面简单、逻辑不复杂,手动测测就够了。但真实情况是,随着业务迭代,一个简单的工具类函数可能被十几个页面调用,一次逻辑改动引发的连锁反应,靠人工回归成本极高且极易遗漏。单元测试(Unit Test)就是给你的每一块“积木”(函数、组件)单独做质量检验,确保它本身是坚固的。而E2E测试(End-to-End Test)则是模拟真实用户操作,从点击按钮到页面跳转、数据展示,走完一整条流程,验证“积木”拼装成的“房子”是否稳固。

配置一套好用的测试环境,就像给开发流程上了双保险。它不仅能提前拦截bug,更是代码设计的“照妖镜”——难以测试的代码,往往意味着耦合度过高、职责不清。接下来,我会基于微信小程序原生开发框架(也适用于UniApp等跨端框架的核心思路),手把手带你搭建从单元测试到E2E测试的完整环境,并分享我们团队踩坑后总结的最佳实践。

2. 环境整体设计与工具选型考量

搭建测试环境不是简单安装几个库,首先要明确测试范围和工具链。小程序代码主要分三块:JavaScript逻辑层、WXML/WXSS视图层,以及云函数(如果有)。我们的策略是:逻辑层用单元测试,页面交互用E2E测试。

2.1 单元测试工具选型:Jest 为何是首选

市面上JavaScript单元测试框架很多,比如Mocha、Jasmine、AVA。我们最终选择Jest,理由很充分:

  1. 开箱即用:Jest内置了测试运行器、断言库和覆盖率报告,无需像Mocha那样还要额外配置ChaiKarma,对新手极其友好。
  2. 出色的模拟能力:小程序强依赖wx对象下的API(如wx.request,wx.setStorage)。Jest的jest.mock()功能可以非常轻松地模拟这些全局对象和方法,让测试与微信客户端环境解耦。
  3. 快照测试:对于组件或配置对象,快照测试能有效防止意外变更,虽然在小程序视图层直接应用较少,但对测试工具函数返回的复杂对象很有用。
  4. 活跃的社区:遇到问题容易找到解决方案,很多小程序相关的测试工具也优先提供了Jest支持。

对于可能用到的TypeScript,Jest通过ts-jestbabel也能很好支持。所以,我们的单元测试技术栈就定为:Jest + 小程序API模拟库

2.2 E2E测试工具选型:跨端测试的挑战与方案

E2E测试要模拟用户操作,难点在于如何驱动小程序这个“黑盒”。目前主流有两条路:

方案A:使用微信官方自动化SDK(miniprogram-automator)这是微信官方提供的库,可以与小程序开发者工具或真机通信,直接注入脚本控制页面。它的优势是官方支持、能力全面,能获取页面节点、触发事件、调用wx对象方法。但缺点也很明显:运行依赖开发者工具或手机,速度较慢,更适合在集成流水线中做关键流程的验收测试。

方案B:使用通用E2E框架(如Playwright)测试开发者工具Web视图微信开发者工具本质上是一个Chromium内核的桌面应用。我们可以用Playwright这类现代浏览器自动化工具,直接操作开发者工具内渲染出的Web视图。这种方法运行速度快,适合在本地频繁执行。但缺点是无法测试真机特有的API(如扫码、蓝牙),且可能因为开发者工具版本更新导致选择器失效。

我们的策略是两者结合,各有侧重:本地开发阶段,使用Playwright进行快速反馈的E2E测试;在持续集成(CI)环节,使用miniprogram-automator对提测包进行更贴近真机的自动化验收。本文会重点讲解本地快速反馈的Playwright方案配置,因为这是开发阶段效率提升的关键。

2.3 项目结构规划

一个清晰的结构能让测试维护更轻松。建议在项目根目录下创建专门的测试目录:

your-miniprogram/ ├── src/ # 小程序源码 │ ├── pages/ # 页面文件 │ ├── components/ # 自定义组件 │ ├── utils/ # 工具函数 │ └── app.js # 小程序入口 ├── tests/ # 测试目录 │ ├── unit/ # 单元测试 │ │ ├── utils/ # 测试工具函数 │ │ ├── components/ # 测试组件(如需要) │ │ └── __mocks__/ # Jest模拟文件目录 │ └── e2e/ # E2E测试 │ ├── specs/ # 测试用例文件 │ └── fixtures/ # 测试用的固定数据 ├── jest.config.js # Jest配置文件 ├── playwright.config.ts # Playwright配置文件 └── package.json

注意:小程序项目本身可能由开发者工具创建,没有package.json。你需要先在项目根目录执行npm init -y来初始化Node.js项目,这样才能管理测试相关的依赖。

3. 单元测试环境配置与核心实践

3.1 初始化与依赖安装

首先,在项目根目录下安装Jest及相关依赖:

npm install --save-dev jest @types/jest babel-jest @babel/core @babel/preset-env
  • jest:测试框架本体。
  • @types/jest:提供TypeScript类型定义(如果使用TS)。
  • babel-jest&@babel/core&@babel/preset-env:为了让Jest能理解ES6+的模块语法(如import/export)和新的JavaScript特性,需要使用Babel进行转译。小程序本身支持ES6,但Node.js环境运行测试时需要此步骤。

接着,创建Jest配置文件jest.config.js

module.exports = { // 测试文件匹配模式 testMatch: ['**/tests/unit/**/*.test.[jt]s?(x)'], // 模块文件扩展名 moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node'], // 收集测试覆盖率 collectCoverage: true, coverageDirectory: 'coverage', coverageReporters: ['html', 'text-summary'], // 覆盖率收集范围,通常针对业务逻辑目录 collectCoverageFrom: [ 'src/utils/**/*.js', 'src/components/**/*.js', '!**/node_modules/**', ], // 模拟小程序全局对象 setupFiles: ['<rootDir>/tests/unit/setup.js'], };

关键配置是setupFiles,它指定了一个初始化脚本,我们将在其中设置全局的模拟。

3.2 模拟微信小程序全局对象

这是小程序单元测试的核心。我们不能让测试代码直接调用真实的wx.request去发网络请求。在tests/unit/setup.js文件中,我们需要用Jest模拟wx对象:

// tests/unit/setup.js global.wx = { // 模拟异步API,返回Promise request: jest.fn(() => Promise.resolve({ data: {} })), setStorage: jest.fn(() => Promise.resolve({ errMsg: 'setStorage:ok' })), getStorage: jest.fn(() => Promise.resolve({ data: null })), showToast: jest.fn(), showModal: jest.fn(), navigateTo: jest.fn(), // ... 模拟其他你用到的API getSystemInfoSync: jest.fn(() => ({ model: 'iPhone', system: 'iOS 10.0.1', platform: 'ios', })), }; // 如果你的工具函数中使用了global(小程序中为全局对象),也需要模拟 global.getApp = jest.fn(() => ({ globalData: {}, }));

这个文件会在每个测试文件运行前执行,为全局环境注入一个模拟的wx对象。这样,在你的工具函数中调用wx.request时,实际上调用的是我们模拟的jest.fn(),可以在测试用例中对其调用情况进行断言。

3.3 编写第一个单元测试:工具函数

假设我们有一个计算价格的工具函数src/utils/price.js

// src/utils/price.js export function calculateDiscountPrice(originalPrice, discount) { if (typeof originalPrice !== 'number' || originalPrice < 0) { throw new Error('原始价格必须是非负数字'); } if (typeof discount !== 'number' || discount < 0 || discount > 1) { throw new Error('折扣必须是0到1之间的数字'); } // 保留两位小数,四舍五入 return Math.round(originalPrice * discount * 100) / 100; }

为它编写测试tests/unit/utils/price.test.js

import { calculateDiscountPrice } from '../../../src/utils/price.js'; describe('价格计算工具函数', () => { // 测试正常情况 test('计算正确的折扣价格', () => { expect(calculateDiscountPrice(100, 0.8)).toBe(80); expect(calculateDiscountPrice(55.5, 0.5)).toBe(27.75); expect(calculateDiscountPrice(0, 0.1)).toBe(0); }); // 测试边界情况 test('折扣为0或1时', () => { expect(calculateDiscountPrice(100, 0)).toBe(0); expect(calculateDiscountPrice(100, 1)).toBe(100); }); // 测试异常输入 - 错误的原始价格 test('传入非数字或负数的原始价格应抛出错误', () => { expect(() => calculateDiscountPrice('100', 0.8)).toThrow('原始价格必须是非负数字'); expect(() => calculateDiscountPrice(-10, 0.8)).toThrow('原始价格必须是非负数字'); }); // 测试异常输入 - 错误的折扣 test('传入无效折扣应抛出错误', () => { expect(() => calculateDiscountPrice(100, '0.8')).toThrow('折扣必须是0到1之间的数字'); expect(() => calculateDiscountPrice(100, 1.5)).toThrow('折扣必须是0到1之间的数字'); expect(() => calculateDiscountPrice(100, -0.1)).toThrow('折扣必须是0到1之间的数字'); }); });

运行测试:npx jest tests/unit/utils/price.test.js。你会看到所有测试通过。这个简单的例子覆盖了正常路径、边界值和异常处理,是单元测试的经典结构。

3.4 测试包含wx API调用的函数

现实中的函数常包含异步操作。例如,一个用户工具函数src/utils/user.js

// src/utils/user.js export async function fetchUserProfile(userId) { if (!userId) { wx.showToast({ title: '用户ID不能为空', icon: 'none' }); return null; } try { const res = await wx.request({ url: `https://api.example.com/user/${userId}`, method: 'GET', }); if (res.statusCode === 200) { return res.data; } else { wx.showToast({ title: '获取用户信息失败', icon: 'none' }); return null; } } catch (error) { wx.showToast({ title: '网络请求异常', icon: 'none' }); return null; } }

测试这个函数,我们需要验证两件事:1. 是否正确调用了wx.requestwx.showToast;2. 在不同响应下是否返回了预期结果。

// tests/unit/utils/user.test.js import { fetchUserProfile } from '../../../src/utils/user.js'; // 在每个测试用例前重置模拟函数的调用记录 beforeEach(() => { jest.clearAllMocks(); }); describe('获取用户资料函数', () => { const mockUserId = '123'; test('成功获取用户信息', async () => { // 1. 模拟一次成功的wx.request响应 const mockSuccessResponse = { statusCode: 200, data: { name: '张三', age: 25 } }; wx.request.mockResolvedValueOnce(mockSuccessResponse); // 2. 执行被测试函数 const result = await fetchUserProfile(mockUserId); // 3. 断言 // 3.1 验证wx.request被以正确的参数调用 expect(wx.request).toHaveBeenCalledWith({ url: `https://api.example.com/user/${mockUserId}`, method: 'GET', }); // 3.2 验证wx.showToast没有被调用(成功时不提示) expect(wx.showToast).not.toHaveBeenCalled(); // 3.3 验证函数返回了正确的数据 expect(result).toEqual(mockSuccessResponse.data); }); test('当API返回非200状态码时', async () => { const mockErrorResponse = { statusCode: 404 }; wx.request.mockResolvedValueOnce(mockErrorResponse); const result = await fetchUserProfile(mockUserId); expect(wx.request).toHaveBeenCalled(); // 验证显示了错误提示 expect(wx.showToast).toHaveBeenCalledWith({ title: '获取用户信息失败', icon: 'none', }); expect(result).toBeNull(); }); test('当网络请求异常时', async () => { wx.request.mockRejectedValueOnce(new Error('Network Error')); const result = await fetchUserProfile(mockUserId); expect(wx.showToast).toHaveBeenCalledWith({ title: '网络请求异常', icon: 'none', }); expect(result).toBeNull(); }); test('当用户ID为空时', async () => { const result = await fetchUserProfile(''); // 验证直接显示了Toast且未发起请求 expect(wx.showToast).toHaveBeenCalledWith({ title: '用户ID不能为空', icon: 'none', }); expect(wx.request).not.toHaveBeenCalled(); expect(result).toBeNull(); }); });

实操心得:对于模拟的异步函数,一定要用mockResolvedValueOncemockRejectedValueOnce来模拟单次调用的返回值,并用await等待异步函数执行完毕。beforeEach中调用jest.clearAllMocks()是个好习惯,能防止不同测试用例间的模拟状态相互干扰。

3.5 组件单元测试的特别考量

对于小程序的自定义组件,由于其运行在小程序特有的双线程架构中,直接使用Jest进行完整的“渲染测试”比较困难。通常,我们更侧重于测试组件的纯逻辑部分(如properties,data,methods中的函数)。

如果组件逻辑复杂,一个可行的策略是将核心业务逻辑抽离到独立的JavaScript函数或类中,然后对这些纯JavaScript代码进行单元测试。组件本身只负责调用和视图渲染。这符合“关注点分离”的原则,也让测试更容易。

4. E2E测试环境配置(基于Playwright)

如前所述,我们选择Playwright进行本地快速的E2E测试。它的优势是速度快,能直接与开发者工具内的页面交互。

4.1 环境安装与初始化

首先,安装Playwright:

npm install --save-dev @playwright/test # 安装Playwright自带的浏览器(Chromium, Firefox, WebKit) npx playwright install

然后,初始化Playwright配置文件。我们创建一个playwright.config.ts(或.js):

// playwright.config.js const { defineConfig, devices } = require('@playwright/test'); module.exports = defineConfig({ // 测试目录 testDir: './tests/e2e/specs', // 并行运行测试的最大工作进程数,本地开发可以设为1避免干扰 workers: 1, // 超时设置 timeout: 30 * 1000, // 每个测试最多30秒 expect: { timeout: 5000, // 断言超时5秒 }, // 测试报告 reporter: 'html', // 项目配置,这里我们只配置一个项目,使用Chromium projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'], // 设置视口大小模拟手机,例如iPhone 12 viewport: { width: 390, height: 844 }, // 忽略HTTPS错误,因为开发者工具本地服务可能是HTTP ignoreHTTPSErrors: true, // 设置慢速模拟,方便观察测试过程 // launchOptions: { // slowMo: 500, // }, }, }, ], // 全局配置,每个测试文件运行前执行 globalSetup: require.resolve('./tests/e2e/global-setup'), // 全局配置,每个测试文件运行后执行 // globalTeardown: require.resolve('./tests/e2e/global-teardown'), });

4.2 全局配置:启动与关闭开发者工具

E2E测试需要一个运行中的小程序。我们通过一个全局设置文件来启动开发者工具(命令行方式)并等待项目加载。这需要借助微信开发者工具的命令行调用能力。

首先,确保你的微信开发者工具安装了命令行调用插件,并且知道其cli工具的路径(通常在安装目录下,如/Applications/wechatwebdevtools.app/Contents/MacOS/cliC:\Program Files (x86)\Tencent\微信web开发者工具\cli.bat)。

创建tests/e2e/global-setup.js

// tests/e2e/global-setup.js const { exec } = require('child_process'); const path = require('path'); const { promisify } = require('util'); const execAsync = promisify(exec); module.exports = async (config) => { // 1. 构建小程序项目(如果需要) console.log('正在构建小程序项目...'); const projectPath = path.resolve(__dirname, '../../'); // 指向项目根目录 const cliPath = '/path/to/your/wechatdevtools/cli'; // 替换为你的cli实际路径 try { // 命令:打开项目并自动编译 // --auto-preview 参数可能因开发者工具版本而异,请查阅官方文档 const command = `"${cliPath}" open --project "${projectPath}" --auto-preview`; await execAsync(command); console.log('开发者工具启动命令已发送。'); } catch (error) { console.error('启动开发者工具失败:', error); // 这里不直接退出,因为可能工具已经打开 } // 2. 等待一段时间,确保小程序编译加载完成 console.log('等待小程序加载...'); await new Promise(resolve => setTimeout(resolve, 10000)); // 等待10秒 console.log('全局设置完成,开始执行测试。'); };

重要提示:微信开发者工具的命令行接口(CLI)及其参数可能随版本更新而变化。上述--auto-preview参数仅为示例,请务必查阅你当前使用版本的官方文档,确认正确的自动编译和预览命令。一个更稳定的替代方案是,在运行E2E测试前,手动在开发者工具中打开项目并点击“编译”,然后Playwright直接连接已打开的页面。

4.3 编写第一个E2E测试:页面导航与断言

假设我们有一个首页(pages/index/index),上面有一个按钮,点击后跳转到日志页(pages/logs/logs)。我们来测试这个流程。

首先,我们需要知道开发者工具中预览页面的URL。通常,编译后会在本地启动一个HTTP服务,地址类似http://127.0.0.1:端口号/。你需要先手动编译一次,从开发者工具的“调试器-网络”或地址栏复制这个基础URL。

创建测试文件tests/e2e/specs/navigation.spec.js

const { test, expect } = require('@playwright/test'); // 测试前设置,例如设置基础URL test.describe('小程序页面导航测试', () => { // 基础URL,需要替换为你本地开发者工具的实际地址和端口 const BASE_URL = 'http://127.0.0.1:5173'; // 示例端口,请务必修改! test.beforeEach(async ({ page }) => { // 每个测试用例开始前,都跳转到首页 await page.goto(BASE_URL + '/pages/index/index'); // 等待页面关键元素加载完成,增加测试稳定性 await page.waitForSelector('.container', { state: 'attached' }); }); test('应从首页成功跳转到日志页', async ({ page }) => { // 1. 在首页找到跳转按钮。选择器需要根据你的实际WXML来定。 // 假设按钮的文本是“查看日志”,或者有一个特定的class const gotoLogsButton = page.locator('text=查看日志').first(); // 使用文本定位 // 或者: const gotoLogsButton = page.locator('.goto-logs-btn'); // 使用类名定位 // 2. 点击按钮 await gotoLogsButton.click(); // 3. 等待页面跳转完成。可以通过等待新页面上的某个特定元素出现来判断。 // 假设日志页有一个标题元素 await page.waitForSelector('text=调试日志', { state: 'attached', timeout: 10000 }); // 4. 断言当前页面的URL路径包含 logs await expect(page).toHaveURL(/.*\/pages\/logs\/logs/); // 5. (可选) 断言日志页的特定内容,比如列表项的数量 const logItems = page.locator('.log-item'); await expect(logItems).toHaveCountGreaterThan(0); }); test('首页应包含预期的欢迎文本', async ({ page }) => { // 直接断言页面上的文本内容 await expect(page.locator('.user-motto')).toContainText('Hello World'); }); });

运行这个测试:npx playwright test tests/e2e/specs/navigation.spec.js。Playwright会自动打开Chromium浏览器,执行上述操作。

4.4 处理小程序特有的交互与API

小程序有一些特有组件和API,在Web视图中可能表现不同。例如,<picker>组件在Web视图中会渲染成原生HTML<select>或自定义下拉框。定位这些元素需要一些技巧。

技巧1:使用更稳定的选择器

  • 优先使用>test('列表页应成功加载数据', async ({ page }) => { await page.goto(BASE_URL + '/pages/list/list'); // 先等待“加载中”提示消失 await page.waitForSelector('.loading', { state: 'detached' }); // 再断言列表项 const listItems = page.locator('.list-item'); await expect(listItems).toHaveCount(10); // 假设一页加载10条 });

    技巧3:模拟用户输入对于<input><textarea>,使用page.fill()page.type()

    const input = page.locator('.search-input'); await input.fill('搜索关键词'); await page.keyboard.press('Enter'); // 模拟按下回车

    4.5 测试数据隔离与Fixture

    E2E测试不应该依赖线上数据库或特定的用户状态。理想情况是,每个测试都能从一个干净的状态开始。对于小程序,这通常意味着:

    1. 清理本地存储:在测试开始前,清除wx.setStorage的数据。可以通过Playwright执行一段小程序环境下的JavaScript代码(如果环境支持),或者更实际的做法是,你的测试账号使用一个独立的、可重置的测试环境。
    2. 使用Mock API:这是更推荐的方式。在运行E2E测试时,拦截小程序发出的网络请求,返回预设的测试数据(Fixture)。Playwright提供了强大的路由拦截功能page.route()
    test('我的页面应显示模拟的用户信息', async ({ page }) => { // 拦截特定的API请求 await page.route('**/api/user/profile', route => { // 返回模拟的JSON数据 route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ name: '测试用户', avatar: 'test.jpg' }), }); }); await page.goto(BASE_URL + '/pages/my/my'); // 现在页面显示的数据来自我们的模拟响应 await expect(page.locator('.user-name')).toContainText('测试用户'); });

    通过拦截API,我们完全控制了测试数据,使得测试结果可预测、可重复,且不污染线上数据。

    5. 集成到开发工作流与持续集成

    配置好测试是第一步,让测试真正跑起来并发挥作用是关键。

    5.1 配置 npm scripts

    package.json中配置便捷的命令:

    { "scripts": { "test:unit": "jest", "test:unit:watch": "jest --watch", "test:e2e": "playwright test", "test:e2e:ui": "playwright test --ui", "test:e2e:debug": "playwright test --debug", "test": "npm run test:unit && npm run test:e2e" } }
    • test:unit:watch:监听模式,文件保存后自动运行相关测试,非常适合开发。
    • test:e2e:ui:打开Playwright的GUI界面,可以可视化地运行和调试测试。
    • test:e2e:debug:调试模式,会暂停测试并打开开发者工具。

    5.2 在Git提交前运行测试(Git Hooks)

    使用huskylint-staged可以在提交代码前自动运行测试,确保提交的代码质量。

    npm install --save-dev husky lint-staged

    package.json中配置:

    { "lint-staged": { "src/**/*.js": [ "npm run test:unit -- --findRelatedTests" // 只运行与暂存文件相关的单元测试 ] } }

    然后初始化husky:npx husky install,并添加一个pre-commit钩子:npx husky add .husky/pre-commit "npx lint-staged"。这样,每次git commit时,都会自动对改动的文件运行单元测试。

    5.3 集成到持续集成(CI)流水线

    在CI中(如GitHub Actions, GitLab CI, Jenkins),你需要:

    1. 安装依赖npm ci(比npm install更快更稳定)。
    2. 运行单元测试npm run test:unit。可以配置CI在测试失败时中止流程。
    3. 运行E2E测试:这步更复杂,因为需要微信开发者工具环境。
      • 方案一(推荐用于关键流程):在CI服务器上安装微信开发者工具(可能需要图形界面或使用无头模式,取决于官方支持),然后使用miniprogram-automator连接真机或模拟器进行测试。这更贴近真实环境,但配置复杂、速度慢。
      • 方案二(快速反馈):在CI中只运行单元测试和静态类型检查。E2E测试作为提测后的一个独立验收环节,在专用的测试机上执行。

    一个简化的GitHub Actions工作流示例(仅单元测试):

    name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Use Node.js uses: actions/setup-node@v3 with: node-version: '18' - name: Install Dependencies run: npm ci - name: Run Unit Tests run: npm run test:unit -- --coverage # 可选:上传覆盖率报告到如Codecov等服务 - name: Upload coverage to Codecov uses: codecov/codecov-action@v3

    6. 常见问题与排查技巧实录

    在实际配置和编写测试的过程中,我们遇到了不少坑。这里记录一些典型问题和解决方法。

    6.1 单元测试常见问题

    问题1:Jest提示“SyntaxError: Cannot use import statement outside a module”

    • 原因:Jest运行在Node.js环境,默认不支持ES6模块语法(import/export)。
    • 解决:确保已安装并正确配置了Babel。检查项目根目录是否有.babelrcbabel.config.js文件,内容应包含@babel/preset-env。同时,在jest.config.js中确认transform配置正确(如果使用babel-jest,通常无需额外配置,Jest会自动检测)。

    问题2:测试文件中引用小程序路径报错“Cannot find module”

    • 原因:Jest默认的模块解析可能不识别小程序相对路径(如../../utils/price)。
    • 解决:在jest.config.js中配置moduleNameMapper,将路径别名映射到实际位置。或者,确保在测试文件中使用相对于项目根目录的绝对路径(可能需要配合moduleDirectories配置)。

    问题3:模拟的wx对象方法在测试中未被调用

    • 原因:可能是模拟没有生效。检查setupFiles配置的路径是否正确。另外,确保你的工具函数是通过全局的wx对象调用API,而不是通过其他方式引入的。

    问题4:异步测试超时或未等待完成

    • 原因:测试用例中包含了异步操作(如wx.request),但测试没有正确等待。
    • 解决:在测试函数前加上async关键字,并对所有异步调用使用await。确保模拟函数(如wx.request.mockResolvedValueOnce)返回的是Promise。

    6.2 E2E测试常见问题

    问题1:Playwright无法连接到开发者工具页面(ERR_CONNECTION_REFUSED)

    • 原因:基础URL(BASE_URL)错误,或者开发者工具的服务未启动。
    • 排查
      1. 手动在浏览器中打开开发者工具的预览地址,确认能访问。
      2. 检查端口号是否正确。开发者工具的端口可能每次启动都变化,可以考虑写一个脚本动态获取。
      3. 确保没有跨域问题。开发者工具本地服务通常已配置CORS,但如果是复杂情况,可能需要启动Playwright时添加--ignore-https-errors--disable-web-security标志(不推荐用于生产测试)。

    问题2:元素定位失败(TimeoutError: locator.waitFor: Timeout)

    • 原因:页面元素尚未加载、选择器写错了、或者元素在iframe/Shadow DOM中。
    • 排查
      1. 使用Playwright的--headed模式运行测试,观察页面加载情况。
      2. 使用page.screenshot({ path: 'debug.png' })在失败时截图,查看页面状态。
      3. 使用开发者工具检查目标元素的实际HTML结构和属性,调整选择器。
      4. 增加等待时间,或使用更稳定的等待条件,如page.waitForLoadState('networkidle')等待网络空闲。

    问题3:测试在CI环境中不稳定(Flaky Tests)

    • 原因:网络延迟、动画未完成、动态内容加载时间不确定。
    • 解决
      1. 使用确定性的等待:避免使用固定的sleep,改用waitForSelectorwaitForFunction等待特定状态。
      2. 重试机制:Playwright Test内置了重试功能,可以在配置文件中设置retries
      3. 禁用动画:在测试前注入CSS或JavaScript来禁用CSS过渡和动画,提升稳定性。
      4. 隔离测试数据:确保每个测试不依赖其他测试留下的状态,使用beforeEach清理环境。

    问题4:如何处理小程序登录等需要用户交互的流程?

    • 策略:对于E2E测试,应尽量避免测试需要复杂手动交互(如扫码登录)的流程。有两个方法:
      1. Mock登录态:在测试开始前,通过执行一段JavaScript代码,直接向wx.setStorageSync写入一个模拟的登录态token。这要求你的小程序代码在开发环境下允许这种“后门”。
      2. 测试专用账号:准备一个测试账号,其登录凭证(如账号密码)可以硬编码在测试环境中(注意安全!),然后通过Playwright自动填写表单完成登录。这更贴近真实流程,但更脆弱。

    6.3 测试维护心得

    1. 测试命名要清晰:测试用例的名称应该清晰地描述行为和预期结果,例如“当用户未登录时访问个人中心应跳转到登录页”,这比“test personal center”好得多。
    2. 保持测试独立:每个测试都应该能够独立运行,不依赖其他测试产生的数据或状态。充分利用beforeEachafterEach来设置和清理环境。
    3. 测试重点在业务逻辑:不要为了追求覆盖率而测试框架内部行为或第三方库。集中精力测试你自己写的业务代码。
    4. 定期审查和重构测试:当业务代码变更时,测试也需要更新。将测试代码视为生产代码的一部分,保持其简洁和可读性。
    5. 平衡测试金字塔:单元测试应该最多,运行最快;集成测试次之;E2E测试最少,但覆盖核心用户旅程。不要过度依赖缓慢的E2E测试。

    配置测试环境初期会花费一些时间,但一旦步入正轨,它带来的代码质量信心和重构勇气是巨大的。特别是当团队协作或项目复杂时,一套可靠的自动化测试就是最坚实的安全网。从今天开始,为你下一个小程序项目加上测试吧,第一次可能会觉得麻烦,但第一次用它提前捕获一个隐蔽的线上bug时,你就会觉得一切投入都值了。

http://www.jsqmd.com/news/1069393/

相关文章:

  • Python Selenium自动化问卷填写实战:从环境搭建到验证码处理
  • OWASP CRS自定义规则编写实战:从业务逻辑防护到精准WAF配置
  • 发布管理化技术中的发布流程发布测试发布部署
  • 出海中小企业如何监测竞品投放强度?高性价比广告分析工具选型指南
  • Appium自动化测试:滑动、拖拽、长按、单击四大交互操作实战指南
  • Playwright与Selenium集成NopeCHA:自动化脚本破解验证码实战
  • RPA自动化测试:Python+Playwright+Sure构建高可靠断言体系
  • Appium自动化测试实战:从原理到环境搭建与脚本编写
  • Jodit富文本编辑器安全配置实战:从XSS防御到全链路防护
  • 软件指标管理中的业务技术关联
  • 城市楼宇间无人机与地面站无线链路仿真工具(MATLAB一键运行版)
  • 一次由「操作系统线程数限制」导致的Cannot create native thread错误
  • AI视觉自动化测试:Midscene.js原理、实战与CI/CD集成指南
  • 使用Playwright实战爬取京东图书新书榜:动态价格与分页处理
  • Selenium Python自动化测试实战:从环境搭建到CI/CD集成
  • 前端组件测试策略详解
  • OWASP Top 10实战指南:从风险清单到安全开发生命周期
  • Java自动化测试新选择:Playwright核心优势与实战指南
  • 从零开发pytest插件:Hook机制、项目结构与发布全流程实战
  • Android 7下基于串口的GPS HAL层C语言实现,含硬件配置与NMEA解析框架
  • DeepSeek V4:开源大模型的协作基础设施与协议级工程实践
  • JMeter WebSocket压力测试实战:从工具链搭建到性能瓶颈定位
  • Selenium元素定位全解析:8种方式与实战避坑指南
  • Python电力短路计算器:带可视化界面和自由搭接节点的轻量级分析工具
  • 量子计算入门
  • Web渗透测试实战入门:从信息收集到漏洞利用的核心工具与命令详解
  • SpringBoot固定资产管理系统源码:含折旧计算、多环境部署与报表导出
  • 51单片机6位数码管计算器:带矩阵键盘输入与Proteus仿真演示
  • 从脚本小子到代码猎人:零基础掌握Web代码审计的核心思维与实战方法
  • 基于Playwright与Python构建数据驱动的测试度量体系实战指南