Skip to content

3 min read

Smart Waits

The Cdat utility class wraps Playwright’s locator API with helpers that wait for the right condition instead of an arbitrary number of milliseconds. It is the operational backbone of zero-waitForTimeout.

Install

pnpm add -D @cdat/utils

Or copy packages/cdat-utils/src/Cdat.ts directly into your repo if you prefer not to depend on a package.

Core API

waitAndClick(locator, options?)

Waits for an element to be visible + enabled, then clicks. Replaces 90% of await page.click() calls.

await Cdat.waitAndClick(this.components.submitButton);

// with options
await Cdat.waitAndClick(this.components.submitButton, { timeout: 10_000 });

waitAndFill(locator, value, options?)

Waits for visible + clears + fills + verifies. Handles the “input not yet rendered” race.

await Cdat.waitAndFill(this.components.username, 'testuser');

waitForState(locator, state, options?)

Waits for a LocatorState enum value: Visible, Hidden, Attached, Detached. Returns void.

import { Cdat, LocatorState } from '@cdat/utils';

await Cdat.waitForState(this.components.loader, LocatorState.Hidden);
await Cdat.waitForState(this.components.dialog, LocatorState.Visible);

waitForText(locator, options?)

Waits for visible, returns textContent. Returns Promise<string>.

const message = await Cdat.waitForText(this.components.notification);
expect(message).toContain('saved successfully');

checkState(locator, state, options?)

Like waitForState but non-throwing - returns Promise<boolean>. Use for conditional flows where the element may or may not be present.

if (await Cdat.checkState(this.components.cookieBanner, LocatorState.Visible)) {
await Cdat.waitAndClick(this.components.acceptCookiesButton);
}

Method reference

MethodReturnsUse when
waitAndClickvoidclicking a button/link
waitAndFillvoidfilling an input
waitAndSelectvoidchoosing a <select> option
waitAndCheckvoidcheckbox toggle
waitForStatevoidelement should reach a specific state
checkStatebooleanelement may or may not be present
waitForTextstringreading visible text
waitForCountnumberelement list reaches a target count
waitForUrlvoidURL matches a pattern

Each method takes an optional { timeout, force, retry } config object. Defaults are conservative (10s timeout, no force, 1 retry on assertion-style failures).

Default timeouts

The utility exposes timeout constants:

Cdat.SHORT_TIMEOUT  // 2_000ms - quick UI state changes
Cdat.DEFAULT_TIMEOUT // 10_000ms - most actions
Cdat.LONG_TIMEOUT   // 30_000ms - page navigation, async data loads

Use named constants over magic numbers. If you find yourself reaching for a 60s timeout, you probably have a real bug, not a slow page.

Common patterns

Wait, click, wait for the result

async addToCart(): Promise<void> {
await Cdat.waitAndClick(this.components.addToCartButton);
await Cdat.waitForState(this.components.cartBadge, LocatorState.Visible);
}

Conditional dismissal

async dismissOnboardingIfPresent(): Promise<void> {
if (await Cdat.checkState(this.components.onboardingModal, LocatorState.Visible)) {
  await Cdat.waitAndClick(this.components.onboardingDismiss);
}
}

Reading and asserting separately

// actions.ts - gets the message
async getOrderConfirmationNumber(): Promise<string> {
return Cdat.waitForText(this.components.orderConfirmation);
}

// test.ts - asserts on it
test('order created', async ({ page }) => {
const actions = new CheckoutActions(page);
await actions.placeOrder(VALID_ORDER);
const orderNumber = await actions.getOrderConfirmationNumber();
expect(orderNumber).toMatch(/^ORD-d{6}$/);
});

Next steps