Anti-flaky test patterns: wait on conditions, not timeouts. Trigger: flaky tests, waitForTimeout, race conditions.
76
95%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Anti-flaky test patterns using condition-based waiting instead of fixed timeouts.
| Pattern | Use When |
|---|---|
locator.waitFor() | Element state changes |
expect(locator).toBeVisible() | Visibility assertions |
page.waitForFunction() | Custom JS conditions |
expect.poll() | Repeated value checks |
page.waitForLoadState() | Page lifecycle events |
Wait for conditions, not time.
// ❌ BAD: Fixed timeout
await page.waitForTimeout(2000);
// ✅ GOOD: Condition-based
await page.locator('.result').waitFor({ state: 'visible' });
await expect(page.locator('.result')).toBeVisible();// Wait for element to be attached to DOM
await page.locator('.content').waitFor({ state: 'attached' });
// Wait for element to be visible
await page.locator('.modal').waitFor({ state: 'visible' });
// Wait for element to be hidden
await page.locator('.loading').waitFor({ state: 'hidden' });
// With custom timeout
await page.locator('.slow-element').waitFor({
state: 'visible',
timeout: 10000
});// expect() auto-waits for condition
await expect(page.locator('.status')).toHaveText('Complete');
await expect(page.locator('.count')).toHaveText('5');
await expect(page.locator('.item')).toHaveCount(3);
// Negation assertions (wait for NOT)
await expect(page.locator('.loading')).not.toBeVisible();
await expect(page.locator('.error')).not.toBeAttached();
// Custom timeout on assertions
await expect(page.locator('.result')).toBeVisible({ timeout: 10000 });// Wait for arbitrary JavaScript condition
await page.waitForFunction(() => {
return document.querySelectorAll('.item').length >= 5;
});
// Wait with arguments
const minCount = 5;
await page.waitForFunction(
(count) => document.querySelectorAll('.item').length >= count,
minCount
);
// Wait for element state via JS
await page.waitForFunction(
(selector) => document.querySelector(selector)?.textContent?.includes('Done'),
'.status'
);// Poll for value changes
await expect.poll(async () => {
const response = await page.request.get('/api/status');
return await response.text();
}).toBe('ready');
// Poll with options
await expect.poll(async () => {
return await page.locator('.count').textContent();
}, {
message: 'Item count should increase',
timeout: 15000,
intervals: [500, 1000, 2000] // Custom retry intervals
}).toBe('10');// Wait for specific API response
const responsePromise = page.waitForResponse('**/api/data');
await page.locator('.load-button').click();
const response = await responsePromise;
// Wait for response and verify
await Promise.all([
page.waitForResponse(resp =>
resp.url().includes('/api/data') && resp.status() === 200
),
page.locator('.submit').click()
]);
// Wait for network idle (use sparingly)
await page.waitForLoadState('networkidle');// Wait for page load
await page.goto('/dashboard');
await page.waitForLoadState('domcontentloaded');
// Wait for specific load state
await page.waitForLoadState('load'); // All resources loaded
await page.waitForLoadState('networkidle'); // No network for 500ms
// Wait for URL change
await page.waitForURL('**/dashboard');
await page.waitForURL(/\/dashboard/);// Wait for download
const downloadPromise = page.waitForEvent('download');
await page.locator('.download-btn').click();
const download = await downloadPromise;
// Wait for file input
const fileChooserPromise = page.waitForEvent('filechooser');
await page.locator('.upload-btn').click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles('document.pdf');// Wait for dialog
page.on('dialog', async dialog => {
expect(dialog.message()).toBe('Are you sure?');
await dialog.accept();
});
await page.locator('.delete-btn').click();
// Wait for popup
const popupPromise = page.waitForEvent('popup');
await page.locator('.open-window').click();
const popup = await popupPromise;
await popup.waitForLoadState();// BAD: Arbitrary wait
await page.waitForTimeout(2000);
await page.waitForTimeout(5000);
// GOOD: Wait for condition
await expect(page.locator('.result')).toBeVisible();// BAD: Manual polling loop
while (await page.locator('.loading').isVisible()) {
await page.waitForTimeout(100);
}
// GOOD: Built-in waiting
await page.locator('.loading').waitFor({ state: 'hidden' });
await expect(page.locator('.loading')).not.toBeVisible();// BAD: Missing await (race condition)
page.locator('.submit').click();
expect(page.locator('.result')).toBeVisible();
// GOOD: Properly awaited
await page.locator('.submit').click();
await expect(page.locator('.result')).toBeVisible();// BAD: Waiting for container when content matters
await page.locator('.container').waitFor();
// GOOD: Wait for actual content
await page.locator('.container .item').first().waitFor();Need to wait for... → Use this pattern
─────────────────────────────────────────────────
Element visible/hidden → locator.waitFor({ state })
Element text/value → expect(locator).toHaveText()
Element count → expect(locator).toHaveCount()
JS condition → page.waitForFunction()
API response → page.waitForResponse()
Page navigation → page.waitForURL() / waitForLoadState()
Download/popup → page.waitForEvent()
Repeated value check → expect.poll()
Element NOT present → expect(locator).not.toBeAttached()import { test, expect } from '@playwright/test';
test.describe('Feature with proper waiting', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/app');
await page.waitForLoadState('domcontentloaded');
});
test('loads data and displays results', async ({ page }) => {
// Trigger action
await page.locator('.load-data').click();
// Wait for loading to finish
await expect(page.locator('.loading')).not.toBeVisible();
// Wait for results
await expect(page.locator('.result-item')).toHaveCount(5);
// Verify content
await expect(page.locator('.result-item').first()).toHaveText(/expected/);
});
test('handles async operations', async ({ page }) => {
// Setup response listener
const responsePromise = page.waitForResponse('**/api/data');
// Trigger action
await page.locator('.fetch-btn').click();
// Wait for and verify response
const response = await responsePromise;
expect(response.status()).toBe(200);
// Wait for UI update
await expect(page.locator('.data-display')).toBeVisible();
});
});YoizenSA/dev-ai-workflow
36f5af5
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.