Pruebas en TypeScript/JavaScript
Este archivo extiende common/testing.md con contenido específico de TypeScript/JavaScript.
Testing E2E
Usar Playwright como framework de testing E2E para flujos de usuario críticos.
Soporte de Agentes
- e2e-runner - Especialista en testing E2E con Playwright
其 YAML frontmatter 的 `paths` 决定了规则的自动触发范围(所有 TS/TSX/JS/JSX 文件):
```yaml
paths:
- "**/*.ts"
- "**/*.tsx"
- "**/*.js"
- "**/*.jsx"
按 rules/README.md 中「语言特定规则优先于通用规则」的约定(类似 CSS 特异性),本文件是对通用测试要求的唯一 TypeScript 覆盖点;它本身没有对通用层的覆盖率门槛或 TDD 做任何放宽,因此必须与 rules/common/testing.md 一起阅读生效。安装时应整目录复制(避免 /* 展开破坏 ../common/ 相对引用),参见 rules/README.md。
二、底层红线:80% 覆盖率与三类测试全量必选
该语言规则没有降低通用层的强制要求。rules/common/testing.md 明确规定了每个 TypeScript/JavaScript 项目都必须同时具备:
| 测试类型 | 对象 | 典型落点 |
|---|---|---|
| Unit Tests(单元测试) | 单个函数、工具、组件 | *.test.ts 与源码同目录 |
| Integration Tests(集成测试) | API 端点、数据库操作 | tests/integration/ |
| E2E Tests(端到端测试) | 关键用户流程 | tests/e2e/*.spec.ts |
其中 E2E 层的框架选择,正是本语言规则唯一明确指定的事项:Playwright。这条规则同时出现在 rules/typescript/testing.md 及其西班牙语镜像 docs/es/rules/typescript/testing.md 中,说明它是跨翻译版本被一致执行的语言级决策。
三、强制 TDD:先写测试(RED → GREEN → IMPROVE)
通用规则要求所有 TypeScript 功能开发遵循 TDD 强制流程(rules/common/testing.md):
- 先写测试(RED)——测试应先失败;
- 运行测试,确认失败;
- 编写最小实现(GREEN)——让测试通过;
- 重构(IMPROVE);
- 校验覆盖率 ≥ 80%。
配套的 agent 是 tdd-guide(见 agents/tdd-guide.md),其职责是在新功能开发时主动(PROACTIVELY)强制执行「先写测试」。规则还特别强调排障纪律:优先调用 tdd-guide、检查测试隔离性、核对 mock 是否正确,并且在测试本身无误时修改实现而非修改测试。
四、测试结构规范:AAA 模式与行为化命名
4.1 AAA(Arrange-Act-Assert)三段式
通用规则以 TypeScript 示例约定了测试的标准结构(rules/common/testing.md):
test('calculates similarity correctly', () => {
// Arrange
const vector1 = [1, 0, 0]
const vector2 = [0, 1, 0]
// Act
const similarity = calculateCosineSimilarity(vector1, vector2)
// Assert
expect(similarity).toBe(0)
})
4.2 行为化命名
测试名必须描述被测行为而非实现细节:
test('returns empty array when no markets match query', () => {})
test('throws error when API key is missing', () => {})
test('falls back to substring search when Redis is unavailable', () => {})
这种命名的价值在 E2E 场景被进一步放大:行为化描述天然映射到用户旅程(user journey),便于 e2e-runner 在生成用例时直接翻译为「用户动作 + 断言」。
五、E2E 层深挖:Playwright 与专职 agent e2e-runner
语言规则在 E2E 上的落地不止是选型,还绑定了执行主体 e2e-runner(完整定义见 agents/e2e-runner.md),其核心职责包括:
- Test Journey Creation——为关键用户流程编写测试(优先 Agent Browser,回退 Playwright);
- Test Maintenance——随 UI 变更维护用例;
- Flaky Test Management——识别并隔离不稳定测试;
- Artifact Management——采集截图、视频、trace;
- CI/CD Integration——保证流水线内稳定运行;
- Test Reporting——产出 HTML 报告与 JUnit XML。
在 ECC 的命令体系中,这一能力通过 /e2e 命令暴露给用户(见 docs/es/commands/e2e.md),典型触发场景包括登录、交易、支付等高风险关键流程,以及在发布前验证前后端集成。
5.1 工具优先级:Agent Browser 优先,Playwright 兜底
e2e-runner 的默认偏好是 Agent Browser(语义化选择器、AI 优化、内置自动等待,底层构建于 Playwright):
npm install -g agent-browser && agent-browser install
agent-browser open https://example.com
agent-browser snapshot -i # 获取带 [ref=e1] 引用的元素
agent-browser click @e1 # 按引用点击
agent-browser fill @e2 "text" # 填充输入框
agent-browser wait visible @e5 # 等待元素可见
agent-browser screenshot result.png
当 Agent Browser 不可用时,回退到原生 Playwright:
npx playwright test # 运行全部 E2E 用例
npx playwright test tests/auth.spec.ts # 只跑指定文件
npx playwright test --headed # 有头模式查看浏览器
npx playwright test --debug # 调试器逐行运行
npx playwright test --trace on # 带 trace 运行
npx playwright show-report # 打开 HTML 报告
5.2 Playwright 配置基线
sdks 中提供了可直接落地的 playwright.config.ts,覆盖多浏览器项目、CI 差异化重试与多种报告器:
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html', { outputFolder: 'playwright-report' }],
['junit', { outputFile: 'playwright-results.xml' }],
['json', { outputFile: 'playwright-results.json' }]
],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
actionTimeout: 10000,
navigationTimeout: 30000,
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120000,
},
})
关键参数解读:
trace: 'on-first-retry':首次失败自动重试并捕获 trace,是最省存储的失败调试策略;screenshot: 'only-on-failure'+video: 'retain-on-failure':只在失败时保留证据,对应 e2e-runner 的制品管理职责;retries/workers在 CI 与本地差异化:CI 上收紧并发(workers: 1)换取稳定性;- 三个
reporter并行输出:HTML 给人看,JUnit/JSON 喂给 CI 与脚本。
5.3 Page Object Model(POM)范式
e2e-runner 的用例生成与 skill 均强调 POM,将页面选择器与交互封装为类:
import { Page, Locator } from '@playwright/test'
export class ItemsPage {
readonly page: Page
readonly searchInput: Locator
readonly itemCards: Locator
readonly createButton: Locator
constructor(page: Page) {
this.page = page
this.searchInput = page.locator('[data-testid="search-input"]')
this.itemCards = page.locator('[data-testid="item-card"]')
this.createButton = page.locator('[data-testid="create-btn"]')
}
async goto() {
await this.page.goto('/items')
await this.page.waitForLoadState('networkidle')
}
async search(query: string) {
await this.searchInput.fill(query)
await this.page.waitForResponse(resp => resp.url().includes('/api/search'))
await this.page.waitForLoadState('networkidle')
}
async getItemCount() {
return await this.itemCards.count()
}
}
配套的测试骨架遵循 test.describe + beforeEach 组织用例,并在关键步骤放置 expect 断言(skills/e2e-testing/SKILL.md):
import { test, expect } from '@playwright/test'
import { ItemsPage } from '../../pages/ItemsPage'
test.describe('Item Search', () => {
let itemsPage: ItemsPage
test.beforeEach(async ({ page }) => {
itemsPage = new ItemsPage(page)
await itemsPage.goto()
})
test('should search by keyword', async ({ page }) => {
await itemsPage.search('test')
const count = await itemsPage.getItemCount()
expect(count).toBeGreaterThan(0)
await expect(itemsPage.itemCards.first()).toContainText(/test/i)
await page.screenshot({ path: 'artifacts/search-results.png' })
})
test('should handle no results', async ({ page }) => {
await itemsPage.search('xyznonexistent123')
await expect(page.locator('[data-testid="no-results"]')).toBeVisible()
expect(await itemsPage.getItemCount()).toBe(0)
})
})
六、关键用户流程的端到端验证实战
docs/es/commands/e2e.md 给出了 e2e-runner 的完整工作样例:将「搜索市场 → 查看结果 → 点击进入 → 查看详情」的用户旅程翻译为多场景用例,并在每个关键步骤验证 API 响应、页面状态与截图取证。
6.1 场景设计三要素
| 场景类型 | 示例 | 断言重点 |
|---|---|---|
| Happy path(主路径) | 搜索关键词并进入详情页 | 标题匹配、URL 形态 /markets/[id]、图表渲染 |
| 边界情形(empty state) | 搜索不存在的市场 | no-results 可见、卡片数为 0 |
| 反向操作 | 清空搜索恢复全部 | 计数回到初始值 |
E2E 中等待的真实形态是等待条件而非固定时长,这正是 unstable 测试的主要来源之一:
// 错误:任意超时
await page.waitForTimeout(5000)
// 正确:等待具体网络条件
await page.waitForResponse(resp => resp.url().includes('/api/data'))
// 正确:使用自动等待的 locator
await page.locator('[data-testid="button"]').click()
6.2 高风险流程:金融/交易与 Web3
对于金融与支付类关键流程,skill 强调三个原则:绝不针对生产环境执行真实资金操作、通过 mock 注入链上/provider 行为、对交易确认使用带 { timeout } 的响应等待:
test('trade execution', async ({ page }) => {
// Skip on production — real money
test.skip(process.env.NODE_ENV === 'production', 'Skip on production')
await page.goto('/markets/test-market')
await page.locator('[data-testid="position-yes"]').click()
await page.locator('[data-testid="trade-amount"]').fill('1.0')
const preview = page.locator('[data-testid="trade-preview"]')
await expect(preview).toContainText('1.0')
await page.locator('[data-testid="confirm-trade"]').click()
await page.waitForResponse(
resp => resp.url().includes('/api/trade') && resp.status() === 200,
{ timeout: 30000 }
)
await expect(page.locator('[data-testid="trade-success"]')).toBeVisible()
})
七、不稳定测试(Flaky)治理:隔离、重跑与指标
e2e-runner 与 skill 共同定义了 flaky 治理闭环:
定位问题(本地重跑放大):
npx playwright test tests/search.spec.ts --repeat-each=10
npx playwright test tests/search.spec.ts --retries=3
隔离问题用例(quarantine):
test('flaky: complex search', async ({ page }) => {
test.fixme(true, 'Flaky - Issue #123')
})
test('conditional skip', async ({ page }) => {
test.skip(process.env.CI, 'Flaky in CI - Issue #123')
})
三类高频成因与修法(来自 skills/e2e-testing/SKILL.md):
| 成因 | 反模式 | 修法 |
|---|---|---|
| 竞态条件 | 假设元素就绪后裸 page.click() |
用自动等待 locator page.locator(...).click() |
| 网络时序 | waitForTimeout(5000) |
waitForResponse(url 匹配) |
| 动画时序 | 动画播放期间点击 | 先 waitFor({ state: 'visible' }) + networkidle |
成功指标(agents/e2e-runner.md):关键流程通过率 100%、整体通过率 > 95%、flaky 率 < 5%、单次测试时长 < 10 分钟、制品可访问。
八、CI/CD 集成与报告产物
规则要求 E2E 在合入主干(merge to main)前执行。Playwright 官方 GitHub Actions 流程可直接对接:
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
env:
BASE_URL: ${{ vars.STAGING_URL }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
产物规范:每次运行生成 HTML 报告与 JUnit XML(供 CI 消费);仅失败时保留截图、视频、trace 压缩包、网络与控制台日志。本地排查命令:
npx playwright show-report # HTML 报告
npx playwright show-trace artifacts/trace.zip # 单步回放 trace
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00