skills/condition-based-waiting/SKILL.md
Use when tests have race conditions, timing dependencies, or inconsistent pass/fail behavior - replaces arbitrary timeouts with condition polling
npx skillsauth add timequity/vibe-coder condition-based-waitingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
setTimeout, sleep)Don't use when:
// ❌ BEFORE: Guessing at timing
await new Promise(r => setTimeout(r, 50));
const result = getResult();
expect(result).toBeDefined();
// ✅ AFTER: Waiting for condition
await waitFor(() => getResult() !== undefined);
const result = getResult();
expect(result).toBeDefined();
| Scenario | Pattern |
|----------|---------|
| Wait for event | waitFor(() => events.find(e => e.type === 'DONE')) |
| Wait for state | waitFor(() => machine.state === 'ready') |
| Wait for count | waitFor(() => items.length >= 5) |
| Wait for file | waitFor(() => fs.existsSync(path)) |
| Complex condition | waitFor(() => obj.ready && obj.value > 10) |
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, 10)); // Poll every 10ms
}
}
❌ Polling too fast: setTimeout(check, 1) - wastes CPU
✅ Fix: Poll every 10ms
❌ No timeout: Loop forever if condition never met ✅ Fix: Always include timeout with clear error
❌ Stale data: Cache state before loop ✅ Fix: Call getter inside loop for fresh data
// 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:
For React tests, use built-in utilities:
import { waitFor, screen } from '@testing-library/react';
// ✅ Wait for element
await waitFor(() => {
expect(screen.getByText('Loaded')).toBeInTheDocument();
});
// ✅ Find (waits automatically)
const button = await screen.findByRole('button', { name: 'Submit' });
Complementary skills:
development
Hidden quality gate that runs before showing "Done!" to user - ensures all tests pass, build succeeds, and requirements met before claiming completion
data-ai
Use when about to claim work is complete or fixed - requires running verification commands and confirming output before making any success claims
tools
Generate UI components from natural language descriptions. Use when: user asks for a page, component, or UI element. Triggers: "create page", "add component", "show form", "make button", "страница", "компонент", "форма".
content-media
10 ready-to-use themes with colors and fonts for consistent styling. Use when: applying visual themes to pages, components, or design systems. Triggers: "theme", "color palette", "color scheme", "fonts", "branding", "visual identity", "design system colors".