condition-based-waiting

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Condition-Based Waiting

基于条件的等待

Use with:
writing-tests
skill for overall test guidance. This skill focuses on timing-based flakiness.
Related: If tests pass alone but fail concurrently, the problem may be shared state, not timing. See
fixing-flaky-tests
skill for diagnosis.
搭配使用:
writing-tests
技能可提供整体测试指导,本技能专注于基于时序的不稳定问题。
相关提示: 如果测试单独运行通过但并发运行失败,问题可能出在共享状态而非时序。请查看
fixing-flaky-tests
技能进行诊断。

Overview

概述

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 waiting
Use 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:
    findBy
    queries,
    waitFor
  • Playwright: auto-waiting,
    expect(locator).toBeVisible()
  • pytest:
    asyncio.wait_for
    , tenacity
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:
    findBy
    查询、
    waitFor
  • Playwright:自动等待、
    expect(locator).toBeVisible()
  • pytest:
    asyncio.wait_for
    、tenacity
当内置方法不足时,使用自定义轮询作为备选:
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

特定语言模式

StackReference
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

常见错误

MistakeProblemFix
Polling too fast
setTimeout(check, 1)
wastes CPU
Poll every 50ms
No timeoutLoop forever if condition never metAlways include timeout
Stale dataCaching state before loopCall getter inside loop
No description"Timeout" with no contextInclude what you waited for
错误问题修复方案
轮询过于频繁
setTimeout(check, 1)
浪费CPU资源
每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 justified
Requirements:
  1. First wait for triggering condition
  2. Based on known timing (not guessing)
  3. Comment explaining WHY
typescript
// 工具每100ms执行一次 - 需要等待2个周期来验证部分输出
await waitForEvent(manager, "TOOL_STARTED");  // 第一步:等待触发条件
await new Promise((r) => setTimeout(r, 200)); // 第二步:等待时序行为
// 200ms = 100ms间隔的2个周期 - 已记录并说明原因
要求:
  1. 先等待触发条件
  2. 基于已知时序(而非猜测)
  3. 添加注释说明原因