Playwright 从零到 CI 实战:基于 TypeScript 的端到端测试完整指南

Playwright 从零到 CI 实战:基于 TypeScript 的端到端测试完整指南

本文基于一个可运行的 playwright-demo 项目,从环境搭建、配置拆解、用例编写、定位器与断言、调试排障,一路讲到 GitHub Actions 持续集成。适合有一定前端/测试基础、想系统掌握 Playwright 的同学。


一、为什么选择 Playwright?

Playwright 是微软开源的现代化端到端(E2E)测试框架,与 Selenium、Cypress 相比,它在工程化体验上有几个突出优势:

能力 说明
多浏览器同源 API 一套 API 覆盖 Chromium、Firefox、WebKit,真正做到跨引擎一致
自动等待(Auto-waiting) 操作前自动等待元素可交互,大幅减少 sleep / 脆弱的显式等待
隔离性强 每个测试默认使用独立 Browser Context,状态不互相污染
开箱即用的工具链 Codegen、UI Mode、Trace Viewer、HTML Report 一体
对现代前端友好 原生支持 SPA、Shadow DOM、多页签、文件下载/上传等场景
TypeScript 一等公民 官方脚手架默认 TS,类型提示完善

简单理解:Playwright = 浏览器自动化引擎 + 测试运行器 + 调试/报告工具链

本项目使用:

  • @playwright/test^1.62.0
  • @types/node^26.1.1
  • 测试目录:e2e/
  • 配置文件:playwright.config.ts
  • CI:GitHub Actions(.github/workflows/playwright.yml

二、环境准备与项目初始化

2.1 前置条件

  • Node.js(建议 LTS,例如 18/20/22)
  • npm / pnpm / yarn 任一包管理器
  • 能访问外网(首次需要下载浏览器二进制)

2.2 初始化方式一:官方脚手架(推荐)

bash 复制代码
npm init playwright@latest

交互过程中通常会问:

  1. 使用 TypeScript 还是 JavaScript?
  2. 测试目录叫什么(本项目为 e2e)?
  3. 是否添加 GitHub Actions 工作流?
  4. 是否立即安装浏览器?

2.3 初始化方式二:在已有项目中手动安装

bash 复制代码
npm init -y
npm install -D @playwright/test @types/node
npx playwright install

说明:

  • npm install -D @playwright/test:安装测试框架(含断言、fixture、CLI)
  • npx playwright install:下载 Chromium / Firefox / WebKit 浏览器内核
  • CI 环境常用:npx playwright install --with-deps(同时安装系统依赖,尤其是 Linux)

本仓库 package.json 核心依赖如下:

json 复制代码
{
  "name": "playwright-demo",
  "version": "1.0.0",
  "type": "commonjs",
  "devDependencies": {
    "@playwright/test": "^1.62.0",
    "@types/node": "^26.1.1"
  }
}

注意:Playwright 测试文件可以是 .ts,即使 package.json"type": "commonjs",Playwright 测试运行器也会自行处理 TypeScript 转译,无需你再单独配 ts-node


三、项目结构拆解

一个典型 Playwright 项目大致如下:

text 复制代码
playwright-demo/
├── e2e/                          # 正式测试目录(testDir)
│   └── example.spec.ts           # 示例用例
├── tests/                        # 脚手架可能残留的旧目录(本项目配置未指向它)
│   └── example.spec.ts
├── playwright.config.ts          # 核心配置
├── package.json
├── package-lock.json
├── README.md
├── .gitignore
├── .github/workflows/
│   └── playwright.yml            # CI 流水线
├── playwright-report/            # HTML 报告输出(运行后生成,通常 gitignore)
└── test-results/                 # 失败产物:截图、trace、error-context(gitignore)

3.1 为什么有 e2e/tests/ 两个目录?

本项目 playwright.config.ts 中明确写了:

ts 复制代码
testDir: './e2e',

因此 真正会被执行的是 e2e/tests/ 若存在,只是历史残留或对照样例,不会被默认扫描。

建议:团队统一只保留一个测试根目录,避免"改了 tests,CI 却跑 e2e"的困惑。

3.2 .gitignore 该忽略什么?

gitignore 复制代码
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth/
目录 作用
test-results/ 失败时的截图、视频、trace、error-context
playwright-report/ npx playwright show-report 用的 HTML 报告
blob-report/ 分布式/分片合并报告时的中间产物
.auth/ 常用于存储登录态(storageState),勿提交敏感信息

四、核心配置详解:playwright.config.ts

配置是 Playwright 工程化的灵魂。下面按本项目真实配置逐项讲解。

ts 复制代码
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    // baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox',  use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit',   use: { ...devices['Desktop Safari'] } },
  ],
  // webServer: { ... }
});

4.1 testDir

指定测试文件根目录。默认会匹配 **/*.(spec|test).(js|ts|mjs)

4.2 fullyParallel

  • true:同一文件内的多个 test() 也可并行
  • false:文件内串行,文件间仍可并行(取决于 workers)

适合:用例互相独立、无共享状态 的场景。若用例依赖"上一条创建的数据",不要开这个,或改用 test.describe.serial

4.3 forbidOnly

ts 复制代码
forbidOnly: !!process.env.CI,

本地调试时你可能会写 test.only(...)。若把 only 提交到仓库,CI 会只跑这一条,其他全跳过------非常危险。

开启后:CI 环境一旦发现 test.only / describe.only直接失败,强制你改回来。

4.4 retriesworkers(本地 vs CI 差异)

ts 复制代码
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
配置 本地 CI 原因
retries 0 2 CI 偶发抖动(网络、机器负载)更常见,失败可重试;本地失败应立刻暴露
workers 自动(通常按 CPU) 1 CI 资源有限,串行更稳;本地并行更快

重试不是万能药。若用例本身不稳定(竞态、硬编码等待),应优先修用例,而不是靠 retries 掩盖。

4.5 reporter

ts 复制代码
reporter: 'html',

运行结束后生成 HTML 报告。常用组合:

ts 复制代码
reporter: [
  ['list'],
  ['html', { open: 'never' }],
  ['junit', { outputFile: 'results.xml' }], // 给 Jenkins / 质量门禁
],

查看报告:

bash 复制代码
npx playwright show-report

4.6 use:所有用例共享的浏览器选项

ts 复制代码
use: {
  // baseURL: 'http://localhost:3000',
  trace: 'on-first-retry',
},
baseURL

设置后,page.goto('/login') 会自动拼成 http://localhost:3000/login,便于环境切换(dev/staging)。

trace

Trace 是 Playwright 最强调试手段之一,会记录:

  • DOM 快照
  • 网络请求
  • 控制台日志
  • 每一步 action 的前后状态

常用取值:

含义
off 不记录
on 每次都记(体积大)
retain-on-failure 失败保留
on-first-retry 仅第一次重试时记录(本项目配置,兼顾体积与可观测性)

查看:

bash 复制代码
npx playwright show-trace trace.zip

4.7 projects:多浏览器矩阵

ts 复制代码
projects: [
  { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
  { name: 'firefox',  use: { ...devices['Desktop Firefox'] } },
  { name: 'webkit',   use: { ...devices['Desktop Safari'] } },
]

devices[...] 会预设视口、User-Agent、是否 touch 等。一条用例会被跑 3 遍(三浏览器)。

只跑某一浏览器:

bash 复制代码
npx playwright test --project=chromium

还可扩展移动端 / 品牌浏览器(配置里已有注释模板):

ts 复制代码
{ name: 'Mobile Chrome', use: { ...devices['Pixel 5'] } },
{ name: 'Mobile Safari', use: { ...devices['iPhone 12'] } },
{
  name: 'Microsoft Edge',
  use: { ...devices['Desktop Edge'], channel: 'msedge' },
},

4.8 webServer(测本地应用时强烈建议开启)

本项目测的是公网 https://playwright.dev/,所以没开。若测自己的前端:

ts 复制代码
webServer: {
  command: 'npm run start',
  url: 'http://localhost:3000',
  reuseExistingServer: !process.env.CI,
  timeout: 120 * 1000,
},

含义:

  1. 测试开始前自动启动本地服务
  2. 等到 url 可访问再跑用例
  3. 本地可复用已启动的服务;CI 强制重新拉起,保证干净环境

4.9 环境变量与 dotenv(可选)

配置顶部预留了 dotenv 写法:

ts 复制代码
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });

适合把账号、baseURL、密钥放到 .env(切记加入 .gitignore)。


五、第一条用例:逐行精读

本项目 e2e/example.spec.ts

ts 复制代码
import { test, expect } from '@playwright/test';

test('has title', async ({ page }) => {
  await page.goto('https://playwright.dev/');

  // Expect a title "to contain" a substring.
  await expect(page).toHaveTitle(/Playwright/);
});

test('get started link', async ({ page }) => {
  await page.goto('https://playwright.dev/');

  // Click the get started link.
  await page.getByRole('link', { name: 'Get started' }).click();

  // Expects page to have a heading with the name of Installation.
  await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});

5.1 test 与 Fixture 注入

ts 复制代码
test('has title', async ({ page }) => { ... })

{ page } 不是普通参数,而是 Playwright Fixture。框架会为每个测试:

  1. 创建 Browser(或复用)
  2. 创建独立 BrowserContext
  3. 创建 Page
  4. 测试结束后自动关闭,保证隔离

常见内置 fixture:pagecontextbrowserrequest(API 测试)、baseURL 等。

5.2 page.goto

ts 复制代码
await page.goto('https://playwright.dev/');

默认等待事件是 load。可选:

ts 复制代码
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.goto(url, { waitUntil: 'networkidle' }); // SPA 慎用,可能一直等
await page.goto(url, { timeout: 60_000 });

5.3 断言:expect 是 Web-First 的

ts 复制代码
await expect(page).toHaveTitle(/Playwright/);

注意这里的 awaitPlaywright 的断言会自动重试,直到超时或条件满足。这与普通 Jest 同步断言不同。

正则 /Playwright/ 表示标题"包含"即可,不必全等。

5.4 定位器:优先用角色定位(Role-based)

ts 复制代码
await page.getByRole('link', { name: 'Get started' }).click();
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();

这是官方推荐的 Accessibility Tree(无障碍树)定位

  • 更贴近真实用户感知
  • 对 CSS 类名、DOM 结构变动更不敏感
  • 顺带倒逼产品可访问性更好
定位器优先级建议(从高到低)
  1. getByRole / getByLabel / getByPlaceholder / getByText
  2. getByTestId(专门为测试加的 data-testid
  3. CSS / XPath(最后手段)

示例对比:

ts 复制代码
// ✅ 推荐
page.getByRole('button', { name: '提交' })

// ✅ 测试专用属性
page.getByTestId('submit-btn')

// ⚠️ 脆弱:样式类一变就挂
page.locator('.btn-primary.ml-2')

5.5 自动等待到底等什么?

对 Locator 执行 click() 时,Playwright 会等待元素:

  • 出现在 DOM
  • 可见
  • 稳定(不在动画中乱跳)
  • 可接收事件(不被遮挡)
  • 已启用(非 disabled)

因此多数场景 不需要 waitForTimeout(3000)。硬编码 sleep 通常是坏味道。


六、常用命令速查

bash 复制代码
# 跑全部测试(按 projects 矩阵展开)
npx playwright test

# UI 模式:可视化挑选用例、看时间线、定位器探索
npx playwright test --ui

# 只跑 Chromium
npx playwright test --project=chromium

# 按文件名过滤
npx playwright test example

# 调试模式(步进、Inspector)
npx playwright test --debug

# 录制脚本(Codegen)
npx playwright codegen https://playwright.dev/

# 看 HTML 报告
npx playwright show-report

# 有头模式(看见浏览器)
npx playwright test --headed

# 更新快照(若使用 toHaveScreenshot / toMatchSnapshot)
npx playwright test --update-snapshots

6.1 Codegen 怎么用?

bash 复制代码
npx playwright codegen https://playwright.dev/

会打开浏览器 + Playwright Inspector。你在页面上点击、输入,右侧自动生成定位器代码。适合:

  • 快速摸清页面结构
  • 生成初稿后再人工改成更稳的 Role/TestId 定位

不要把 Codegen 产物原样当最终代码------它有时会生成偏脆的选择器。

6.2 UI Mode 适合做什么?

npx playwright test --ui 适合:

  • 本地开发时反复跑单条用例
  • 看每一步的 DOM / 网络
  • 用 pick locator 验证定位是否唯一

比纯 --debug 更适合日常迭代。


七、进阶技术点(写真实业务必会)

下面这些在官方 demo 里未必直接出现,但落地项目几乎都会用到。

7.1 自定义 Fixture(登录态复用)

ts 复制代码
// e2e/fixtures.ts
import { test as base, expect } from '@playwright/test';

type MyFixtures = {
  loggedInPage: import('@playwright/test').Page;
};

export const test = base.extend<MyFixtures>({
  loggedInPage: async ({ page }, use) => {
    await page.goto('/login');
    await page.getByLabel('用户名').fill('demo');
    await page.getByLabel('密码').fill('secret');
    await page.getByRole('button', { name: '登录' }).click();
    await expect(page.getByText('工作台')).toBeVisible();
    await use(page);
  },
});

export { expect };

用例中:

ts 复制代码
import { test, expect } from './fixtures';

test('进入订单页', async ({ loggedInPage: page }) => {
  await page.goto('/orders');
  await expect(page.getByRole('heading', { name: '订单列表' })).toBeVisible();
});

7.2 storageState:一次登录,多处复用

ts 复制代码
// e2e/auth.setup.ts
import { test as setup } from '@playwright/test';

setup('authenticate', async ({ page }) => {
  await page.goto('https://example.com/login');
  // ... 登录步骤
  await page.context().storageState({ path: 'playwright/.auth/user.json' });
});

配置:

ts 复制代码
projects: [
  { name: 'setup', testMatch: /.*\.setup\.ts/ },
  {
    name: 'chromium',
    use: {
      ...devices['Desktop Chrome'],
      storageState: 'playwright/.auth/user.json',
    },
    dependencies: ['setup'],
  },
],

这样业务用例不再重复登录,速度更快、更稳。

7.3 Page Object Model(POM)

当页面交互变复杂时,把定位与操作封装起来:

ts 复制代码
// e2e/pages/HomePage.ts
import { type Page, type Locator, expect } from '@playwright/test';

export class HomePage {
  readonly getStarted: Locator;

  constructor(private readonly page: Page) {
    this.getStarted = page.getByRole('link', { name: 'Get started' });
  }

  async goto() {
    await this.page.goto('https://playwright.dev/');
  }

  async openInstallation() {
    await this.getStarted.click();
    await expect(this.page.getByRole('heading', { name: 'Installation' })).toBeVisible();
  }
}

用例变薄:

ts 复制代码
test('get started link', async ({ page }) => {
  const home = new HomePage(page);
  await home.goto();
  await home.openInstallation();
});

POM 的价值不是"多写类",而是 定位器集中维护、用例可读性提升

7.4 API 与 UI 混合测试

ts 复制代码
test('先用 API 造数,再用 UI 验证', async ({ page, request }) => {
  const res = await request.post('/api/orders', {
    data: { sku: 'A001', qty: 1 },
  });
  expect(res.ok()).toBeTruthy();
  const { id } = await res.json();

  await page.goto(`/orders/${id}`);
  await expect(page.getByText('A001')).toBeVisible();
});

UI 负责验证交互与展示;数据准备尽量走 API,减少 UI 前置步骤的脆弱性。

7.5 网络拦截与 Mock

ts 复制代码
await page.route('**/api/user', async (route) => {
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify({ name: 'Mock User' }),
  });
});

适用:后端未就绪、第三方不稳定、要测异常态(500 / 超时)。

7.6 截图与视觉对比

ts 复制代码
await expect(page).toHaveScreenshot('home.png');
await expect(page.locator('.hero')).toHaveScreenshot('hero.png');

首次运行会生成基线图;之后对比像素差异。CI 中注意字体/抗锯齿导致的跨平台差异,可固定 viewport、用同一 OS 跑视觉测试。

7.7 软断言

ts 复制代码
await expect.soft(page.getByText('A')).toBeVisible();
await expect.soft(page.getByText('B')).toBeVisible();
// 继续执行,最后统一失败

适合收集同一页面上的多个校验点,避免"挂在第一条就中断"。


八、失败排障实战(结合本项目现象)

本项目跑外网用例时,可能出现类似错误:

text 复制代码
Test timeout of 30000ms exceeded.
Error: page.goto: Test timeout of 30000ms exceeded.
Call log:
  - navigating to "https://playwright.dev/", waiting until "load"

8.1 这是什么意思?

默认单测超时 30s。page.goto 一直等不到 load(网络慢、被墙、DNS、代理、目标站抖动),最终超时失败。

8.2 排查路径

  1. 本机浏览器能否打开目标站?
  2. 是否需要代理?(公司网络常见)
  3. 是否只在某个浏览器失败? (对比 --project=chromium
  4. test-results/**/error-context.md:Playwright 会保存失败时的可访问性树快照,帮助判断"页面是否其实已经打开了一部分"
  5. 开 Trace / 有头模式复现

8.3 常见缓解手段(按推荐顺序)

ts 复制代码
// 1)提高单测超时(治标)
test.setTimeout(60_000);

// 2)goto 单独加超时,并放宽等待条件
await page.goto(url, {
  timeout: 60_000,
  waitUntil: 'domcontentloaded',
});

// 3)配置层统一调整
// playwright.config.ts
export default defineConfig({
  timeout: 60_000,
  use: {
    navigationTimeout: 60_000,
    actionTimeout: 15_000,
  },
});

更稳妥的工程做法:

  • 测自己的应用,用 webServer + baseURL,不要依赖公网
  • CI 里对外部依赖做健康检查,或改为 Mock
  • 对已知不稳定步骤做有限 retries,并监控 flaky 率

8.4 HTML Report / Trace 怎么读?

失败后:

bash 复制代码
npx playwright show-report

报告里可以看到:

  • 哪个 project(chromium/firefox/webkit)失败
  • 失败步骤与调用栈
  • 附件:截图、视频、trace、error context

有 trace 时:

bash 复制代码
npx playwright show-trace path/to/trace.zip

在时间轴上点每一步,看当时 DOM 与网络,比"猜"高效一个数量级。


九、GitHub Actions CI 完整拆解

本项目工作流:.github/workflows/playwright.yml

yaml 复制代码
name: Playwright Tests
on:
  push:
    branches: [ main, master ]
  pull_request:
    branches: [ main, master ]
jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: lts/*
    - name: Install dependencies
      run: npm ci
    - name: Install Playwright Browsers
      run: npx playwright install --with-deps
    - name: Run Playwright tests
      run: npx playwright test
    - uses: actions/upload-artifact@v4
      if: ${{ !cancelled() }}
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 30

9.1 触发时机

  • pushmain/master
  • 针对 main/masterpull_request

保证主干与合入前都有 E2E 门禁。

9.2 为什么用 npm ci 而不是 npm install

npm ci

  • 严格按 package-lock.json 安装
  • 先清空 node_modules 再装
  • 更适合 CI 的可复现构建

9.3 --with-deps 为什么重要?

Linux Runner 上跑浏览器需要系统级依赖(字体、图形库等)。

npx playwright install --with-deps = 装浏览器 + 装系统依赖,少一步就容易出现"本地能跑、CI 起不来浏览器"。

9.4 报告产物上传

yaml 复制代码
if: ${{ !cancelled() }}

即使测试失败(job failed),只要没被取消,也会上传 playwright-report/,方便在 Actions 页面下载 HTML 报告排查。

retention-days: 30 控制产物保留时长,避免 Artifact 无限堆积。

9.5 与 playwright.config.ts 的联动

CI 通常会设置环境变量 CI=true(GitHub Actions 默认就有)。于是配置中的:

ts 复制代码
forbidOnly: !!process.env.CI,   // true
retries: process.env.CI ? 2 : 0, // 2
workers: process.env.CI ? 1 : undefined, // 1

自动切换到"更严格、更稳"的 CI 模式。

9.6 可继续增强的 CI 技巧

  1. 缓存浏览器 :用 actions/cache 缓存 ~/.cache/ms-playwright,加速安装
  2. 分片并行(Sharding)
bash 复制代码
npx playwright test --shard=1/3
npx playwright test --shard=2/3
npx playwright test --shard=3/3
  1. 只在关键路径变更时跑 E2Epaths / paths-filter
  2. 合并报告 :使用 blob reporter + npx playwright merge-reports

十、最佳实践清单(可直接当团队规范)

  1. 定位器getByRole / getByLabel / getByTestId 优先,少写 CSS/XPath
  2. 等待 :相信 Auto-waiting;禁止随意 waitForTimeout
  3. 隔离:一测一 context;不要依赖执行顺序
  4. 稳定性:数据准备走 API;UI 只验证必要路径
  5. 登录setup project + storageState,不要每条用例都点登录
  6. 超时 :区分 test timeout / navigationTimeout / actionTimeout,按场景设置
  7. CIforbidOnly、有限 retries、上传 report/trace
  8. 密钥 :账号密码放环境变量 / Secret,勿提交 .auth 到公开仓库
  9. 目录 :单一 testDir,POM / fixtures / 工具函数分层清晰
  10. 失败即资产:把 flaky 用例登记治理,而不是无限加大 retries

十一、动手实验建议(跟着本仓库练)

  1. 安装依赖与浏览器:
bash 复制代码
npm ci
npx playwright install
  1. 只跑 Chromium,确认环境通:
bash 复制代码
npx playwright test --project=chromium
  1. 打开 UI Mode 改一条断言,观察失败报告:
bash 复制代码
npx playwright test --ui
  1. 用 Codegen 录一条新流程,整理成 Role 定位后放进 e2e/
  2. 给配置加上 baseURL + webServer,改成测自己的本地应用
  3. Push 到 GitHub,观察 Actions 是否产出可下载的 HTML 报告

十二、总结

Playwright 的学习曲线可以拆成三层:

  1. 会跑 :安装、npx playwright test、看报告
  2. 会写:Locator、Web-First 断言、Fixture、POM
  3. 会工程化:config 分环境、trace/重试策略、登录态复用、CI Artifact、flaky 治理

本仓库已经覆盖了第 1 层和第 3 层的骨架(多浏览器 projects、HTML reporter、GitHub Actions)。你要做的是把 e2e/example.spec.ts 从"官方演示站冒烟",演进成"你们业务关键路径的回归套件"。

如果你接下来要扩展,优先顺序建议:

  1. baseURL + webServer 切到真实被测系统
  2. auth.setup.ts + storageState 解决登录
  3. 关键路径 POM 化
  4. CI 缓存与分片提速
  5. 引入 API 造数与必要 Mock

参考资料


本文示例代码基于 @playwright/test ^1.62.0 与仓库内 playwright.config.ts / e2e/example.spec.ts / .github/workflows/playwright.yml 整理。版本升级时请以官方文档为准。

相关推荐
柚yuzumi2 小时前
变量明明定义了,为什么访问不到?深入理解 JavaScript 作用域
javascript
何时梦醒3 小时前
React + TypeScript + Vite 实战:从零构建 Color Picker 应用
前端·javascript·架构
Highcharts.js3 小时前
基于Highcharts开发的OHLC 股票K线图完全指南
javascript·信息可视化·echarts·数据可视化·highcharts·ohlc
默_笙3 小时前
😋 我让 DeepSeek-R1 在浏览器本地跑了起来,后端同事说"你认真的?"
前端·javascript
JasonMa1534 小时前
从零搭建一个微信小程序活动管理平台(一):架构设计与技术选型
javascript·微信小程序·node.js
ji_shuke5 小时前
从零深入:基于 Playwright + Pytest + Allure 的企业级 Web 端到端自动化测试框架实战
前端·自动化测试·docker·jenkins·pytest·allure·playwright
古韵5 小时前
你的 uni-app 分页,一个 hook 搞定竞态和加载
javascript·vue.js·uni-app
成都渲染101云渲染66667 小时前
Blender渲染时,纯CPU渲染的设置教程
前端·javascript·blender
多加点辣也没关系7 小时前
JavaScript|第30章:事件机制
开发语言·javascript
YHHLAI8 小时前
Vue 3 流式输出实战:从零掌握 LLM Streaming 与 SSE 协议
前端·javascript·vue.js