
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理由很充分开箱即用Jest内置了测试运行器、断言库和覆盖率报告无需像Mocha那样还要额外配置Chai和Karma对新手极其友好。出色的模拟能力小程序强依赖wx对象下的API如wx.request,wx.setStorage。Jest的jest.mock()功能可以非常轻松地模拟这些全局对象和方法让测试与微信客户端环境解耦。快照测试对于组件或配置对象快照测试能有效防止意外变更虽然在小程序视图层直接应用较少但对测试工具函数返回的复杂对象很有用。活跃的社区遇到问题容易找到解决方案很多小程序相关的测试工具也优先提供了Jest支持。对于可能用到的TypeScriptJest通过ts-jest或babel也能很好支持。所以我们的单元测试技术栈就定为Jest 小程序API模拟库。2.2 E2E测试工具选型跨端测试的挑战与方案E2E测试要模拟用户操作难点在于如何驱动小程序这个“黑盒”。目前主流有两条路方案A使用微信官方自动化SDKminiprogram-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-envjest测试框架本体。types/jest提供TypeScript类型定义如果使用TS。babel-jestbabel/corebabel/preset-env为了让Jest能理解ES6的模块语法如import/export和新的JavaScript特性需要使用Babel进行转译。小程序本身支持ES6但Node.js环境运行测试时需要此步骤。接着创建Jest配置文件jest.config.jsmodule.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.jsimport { 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.request和wx.showToast2. 在不同响应下是否返回了预期结果。// 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(); }); });实操心得对于模拟的异步函数一定要用mockResolvedValueOnce或mockRejectedValueOnce来模拟单次调用的返回值并用await等待异步函数执行完毕。beforeEach中调用jest.clearAllMocks()是个好习惯能防止不同测试用例间的模拟状态相互干扰。3.5 组件单元测试的特别考量对于小程序的自定义组件由于其运行在小程序特有的双线程架构中直接使用Jest进行完整的“渲染测试”比较困难。通常我们更侧重于测试组件的纯逻辑部分如properties,data,methods中的函数。如果组件逻辑复杂一个可行的策略是将核心业务逻辑抽离到独立的JavaScript函数或类中然后对这些纯JavaScript代码进行单元测试。组件本身只负责调用和视图渲染。这符合“关注点分离”的原则也让测试更容易。4. E2E测试环境配置基于Playwright如前所述我们选择Playwright进行本地快速的E2E测试。它的优势是速度快能直接与开发者工具内的页面交互。4.1 环境安装与初始化首先安装Playwrightnpm 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/cli或C:\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.jsconst { 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视图中会渲染成原生HTMLselect或自定义下拉框。定位这些元素需要一些技巧。技巧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 测试数据隔离与FixtureE2E测试不应该依赖线上数据库或特定的用户状态。理想情况是每个测试都能从一个干净的状态开始。对于小程序这通常意味着清理本地存储在测试开始前清除wx.setStorage的数据。可以通过Playwright执行一段小程序环境下的JavaScript代码如果环境支持或者更实际的做法是你的测试账号使用一个独立的、可重置的测试环境。使用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使用husky和lint-staged可以在提交代码前自动运行测试确保提交的代码质量。npm install --save-dev husky lint-staged在package.json中配置{ lint-staged: { src/**/*.js: [ npm run test:unit -- --findRelatedTests // 只运行与暂存文件相关的单元测试 ] } }然后初始化huskynpx husky install并添加一个pre-commit钩子npx husky add .husky/pre-commit npx lint-staged。这样每次git commit时都会自动对改动的文件运行单元测试。5.3 集成到持续集成CI流水线在CI中如GitHub Actions, GitLab CI, Jenkins你需要安装依赖npm ci比npm install更快更稳定。运行单元测试npm run test:unit。可以配置CI在测试失败时中止流程。运行E2E测试这步更复杂因为需要微信开发者工具环境。方案一推荐用于关键流程在CI服务器上安装微信开发者工具可能需要图形界面或使用无头模式取决于官方支持然后使用miniprogram-automator连接真机或模拟器进行测试。这更贴近真实环境但配置复杂、速度慢。方案二快速反馈在CI中只运行单元测试和静态类型检查。E2E测试作为提测后的一个独立验收环节在专用的测试机上执行。一个简化的GitHub Actions工作流示例仅单元测试name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Use Node.js uses: actions/setup-nodev3 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-actionv36. 常见问题与排查技巧实录在实际配置和编写测试的过程中我们遇到了不少坑。这里记录一些典型问题和解决方法。6.1 单元测试常见问题问题1Jest提示“SyntaxError: Cannot use import statement outside a module”原因Jest运行在Node.js环境默认不支持ES6模块语法import/export。解决确保已安装并正确配置了Babel。检查项目根目录是否有.babelrc或babel.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测试常见问题问题1Playwright无法连接到开发者工具页面ERR_CONNECTION_REFUSED原因基础URLBASE_URL错误或者开发者工具的服务未启动。排查手动在浏览器中打开开发者工具的预览地址确认能访问。检查端口号是否正确。开发者工具的端口可能每次启动都变化可以考虑写一个脚本动态获取。确保没有跨域问题。开发者工具本地服务通常已配置CORS但如果是复杂情况可能需要启动Playwright时添加--ignore-https-errors和--disable-web-security标志不推荐用于生产测试。问题2元素定位失败TimeoutError: locator.waitFor: Timeout原因页面元素尚未加载、选择器写错了、或者元素在iframe/Shadow DOM中。排查使用Playwright的--headed模式运行测试观察页面加载情况。使用page.screenshot({ path: debug.png })在失败时截图查看页面状态。使用开发者工具检查目标元素的实际HTML结构和属性调整选择器。增加等待时间或使用更稳定的等待条件如page.waitForLoadState(networkidle)等待网络空闲。问题3测试在CI环境中不稳定Flaky Tests原因网络延迟、动画未完成、动态内容加载时间不确定。解决使用确定性的等待避免使用固定的sleep改用waitForSelector、waitForFunction等待特定状态。重试机制Playwright Test内置了重试功能可以在配置文件中设置retries。禁用动画在测试前注入CSS或JavaScript来禁用CSS过渡和动画提升稳定性。隔离测试数据确保每个测试不依赖其他测试留下的状态使用beforeEach清理环境。问题4如何处理小程序登录等需要用户交互的流程策略对于E2E测试应尽量避免测试需要复杂手动交互如扫码登录的流程。有两个方法Mock登录态在测试开始前通过执行一段JavaScript代码直接向wx.setStorageSync写入一个模拟的登录态token。这要求你的小程序代码在开发环境下允许这种“后门”。测试专用账号准备一个测试账号其登录凭证如账号密码可以硬编码在测试环境中注意安全然后通过Playwright自动填写表单完成登录。这更贴近真实流程但更脆弱。6.3 测试维护心得测试命名要清晰测试用例的名称应该清晰地描述行为和预期结果例如“当用户未登录时访问个人中心应跳转到登录页”这比“test personal center”好得多。保持测试独立每个测试都应该能够独立运行不依赖其他测试产生的数据或状态。充分利用beforeEach和afterEach来设置和清理环境。测试重点在业务逻辑不要为了追求覆盖率而测试框架内部行为或第三方库。集中精力测试你自己写的业务代码。定期审查和重构测试当业务代码变更时测试也需要更新。将测试代码视为生产代码的一部分保持其简洁和可读性。平衡测试金字塔单元测试应该最多运行最快集成测试次之E2E测试最少但覆盖核心用户旅程。不要过度依赖缓慢的E2E测试。配置测试环境初期会花费一些时间但一旦步入正轨它带来的代码质量信心和重构勇气是巨大的。特别是当团队协作或项目复杂时一套可靠的自动化测试就是最坚实的安全网。从今天开始为你下一个小程序项目加上测试吧第一次可能会觉得麻烦但第一次用它提前捕获一个隐蔽的线上bug时你就会觉得一切投入都值了。