condition-based-waiting
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCondition-Based Waiting
基于条件的等待
Use with: skill for overall test guidance. This skill focuses on timing-based flakiness.
writing-testsRelated: If tests pass alone but fail concurrently, the problem may be shared state, not timing. See skill for diagnosis.
fixing-flaky-tests搭配使用: 技能可提供整体测试指导,本技能专注于基于时序的不稳定问题。
writing-tests相关提示: 如果测试单独运行通过但并发运行失败,问题可能出在共享状态而非时序。请查看 技能进行诊断。
fixing-flaky-testsOverview
概述
Flaky tests often guess at timing with arbitrary delays. This creates race conditions where tests pass on fast machines but fail under load or in CI.
Core principle: Wait for the actual condition you care about, not a guess about how long it takes.
不稳定的测试通常会通过任意延迟来猜测时序,这会导致竞态条件,使得测试在快速机器上能通过,但在负载下或CI环境中失败。
核心原则: 等待你真正关心的实际条件,而非猜测所需时间。
When to use
适用场景
Test has arbitrary delay (setTimeout/sleep)?
│
├─ Testing actual timing (debounce, throttle)?
│ └─ Yes → Keep timeout, document WHY
│
└─ No → Replace with condition-based waitingUse when:
- Tests have arbitrary delays (,
setTimeout,sleep)time.sleep() - Tests are flaky with timing-related errors
- Waiting for async operations to complete
Don't use when:
- Testing actual timing behavior (debounce, throttle, intervals)
- Problem is shared state between tests (use )
fixing-flaky-tests
测试中包含任意延迟(setTimeout/sleep)?
│
├─ 是否在测试实际时序行为(防抖、节流)?
│ └─ 是 → 保留超时,并注明原因
│
└─ 否 → 替换为基于条件的等待适用情况:
- 测试中包含任意延迟(、
setTimeout、sleep)time.sleep() - 测试因时序相关错误而不稳定
- 需要等待异步操作完成
不适用情况:
- 测试实际时序行为(防抖、节流、间隔执行)
- 问题源于测试间的共享状态(使用 技能)
fixing-flaky-tests
Core pattern
核心模式
typescript
// Bad: Guessing at timing
await new Promise((r) => setTimeout(r, 50));
const result = getResult();
expect(result).toBeDefined();
// Good: Waiting for condition (returns the result)
const result = await waitFor(() => getResult(), 'result to be available');
expect(result).toBeDefined();typescript
// 不良写法:猜测时序
await new Promise((r) => setTimeout(r, 50));
const result = getResult();
expect(result).toBeDefined();
// 良好写法:等待条件(返回结果)
const result = await waitFor(() => getResult(), 'result to be available');
expect(result).toBeDefined();Implementation
实现方式
Prefer framework built-ins when available:
- Testing Library: queries,
findBywaitFor - Playwright: auto-waiting,
expect(locator).toBeVisible() - pytest: , tenacity
asyncio.wait_for
Custom polling fallback when built-ins aren't enough:
typescript
async function waitFor<T>(
condition: () => T | undefined | null | false,
description: string,
timeoutMs = 5000
): Promise<T> {
const startTime = Date.now();
while (true) {
const result = condition();
if (result) return result;
if (Date.now() - startTime > timeoutMs) {
throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`);
}
await new Promise((r) => setTimeout(r, 50)); // Poll interval
}
}Common use cases:
waitFor(() => events.find(e => e.type === 'DONE'), 'done event')waitFor(() => machine.state === 'ready', 'ready state')waitFor(() => items.length >= 5, '5+ items')
优先使用框架内置方法(如果有):
- Testing Library:查询、
findBywaitFor - Playwright:自动等待、
expect(locator).toBeVisible() - pytest:、tenacity
asyncio.wait_for
当内置方法不足时,使用自定义轮询作为备选:
typescript
async function waitFor<T>(
condition: () => T | undefined | null | false,
description: string,
timeoutMs = 5000
): Promise<T> {
const startTime = Date.now();
while (true) {
const result = condition();
if (result) return result;
if (Date.now() - startTime > timeoutMs) {
throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`);
}
await new Promise((r) => setTimeout(r, 50)); // 轮询间隔
}
}常见使用场景:
waitFor(() => events.find(e => e.type === 'DONE'), 'done event')waitFor(() => machine.state === 'ready', 'ready state')waitFor(() => items.length >= 5, '5+ items')
Language-specific patterns
特定语言模式
| Stack | Reference |
|---|---|
| Python (pytest, asyncio, tenacity) | references/python.md |
| TypeScript (Jest, Testing Library, Playwright) | references/typescript.md |
| 技术栈 | 参考文档 |
|---|---|
| Python (pytest, asyncio, tenacity) | references/python.md |
| TypeScript (Jest, Testing Library, Playwright) | references/typescript.md |
Common mistakes
常见错误
| Mistake | Problem | Fix |
|---|---|---|
| Polling too fast | | Poll every 50ms |
| No timeout | Loop forever if condition never met | Always include timeout |
| Stale data | Caching state before loop | Call getter inside loop |
| No description | "Timeout" with no context | Include what you waited for |
| 错误 | 问题 | 修复方案 |
|---|---|---|
| 轮询过于频繁 | | 每50ms轮询一次 |
| 未设置超时 | 如果条件永远不满足,会进入无限循环 | 始终设置超时时间 |
| 数据过期 | 在循环前缓存状态 | 在循环内部调用获取数据的方法 |
| 无描述信息 | 仅显示“超时”无上下文 | 注明等待的具体内容 |
When arbitrary timeout IS correct
何时可以保留任意超时
typescript
// Tool ticks every 100ms - need 2 ticks to verify partial output
await waitForEvent(manager, "TOOL_STARTED"); // First: wait for condition
await new Promise((r) => setTimeout(r, 200)); // Then: wait for timed behavior
// 200ms = 2 ticks at 100ms intervals - documented and justifiedRequirements:
- First wait for triggering condition
- Based on known timing (not guessing)
- Comment explaining WHY
typescript
// 工具每100ms执行一次 - 需要等待2个周期来验证部分输出
await waitForEvent(manager, "TOOL_STARTED"); // 第一步:等待触发条件
await new Promise((r) => setTimeout(r, 200)); // 第二步:等待时序行为
// 200ms = 100ms间隔的2个周期 - 已记录并说明原因要求:
- 先等待触发条件
- 基于已知时序(而非猜测)
- 添加注释说明原因