Production-ready Playwright testing utilities for AI applications
Battle-tested helpers from Vex - A production AI platform with 6,813+ commits in 2025
Waffles provides a collection of battle-tested Playwright utilities that make E2E testing delightful. Born from real-world production testing at Vex, these helpers solve common testing challenges like simulating user input, waiting for elements, and generating test data.
- π Playwright-first - Built specifically for Playwright
- π§ͺ Production-tested - Used in Vex's extensive test suite
- π¦ Zero config - Works out of the box
- π― TypeScript - Full type safety
- π Lightweight - Minimal dependencies
npm install @chrryai/waffles @playwright/testimport { test, expect } from "@playwright/test"
import {
wait,
simulateInputPaste,
waitForElement,
generateTestEmail,
} from "@chrryai/waffles"
test("chat interaction", async ({ page }) => {
await page.goto("https://yourapp.com")
// Wait for chat to load
await waitForElement(page, '[data-testid="chat-textarea"]')
// Simulate pasting text
await simulateInputPaste(page, "Hello, AI!")
// Wait for response
await wait(1000)
// Assert
await expect(page.locator(".message")).toBeVisible()
})Wait for a specified number of milliseconds.
await wait(1000) // Wait 1 secondWait for an element to be visible.
await waitForElement(page, ".loading-spinner", 5000)Wait for an element to disappear.
await waitForElementToDisappear(page, ".loading-spinner")Simulate pasting text into a textarea.
await simulateInputPaste(page, "Pasted content")Simulate pasting using clipboard API and clicking paste button.
await simulatePaste(page, "Clipboard content")Generate a URL with optional fingerprint for testing.
const url = getURL({
baseURL: "https://app.com",
path: "/chat",
isMember: true,
memberFingerprint: "abc-123",
})Scroll to the bottom of the page.
await scrollToBottom(page)Capitalize the first letter of a string.
capitalizeFirstLetter("hello") // "Hello"Generate a unique test email.
const email = generateTestEmail("user") // user-1234567890-abc123@test.comGenerate a random password for testing.
const password = generateTestPassword(16)Clear browser local storage.
await clearLocalStorage(page)Clear browser cookies.
await clearCookies(page)Take a screenshot with a custom name.
await takeScreenshot(page, "error-state", true)import { test } from "@playwright/test"
import { simulateInputPaste, waitForElement, wait } from "@chrryai/waffles"
test("complete chat interaction", async ({ page }) => {
await page.goto("https://app.com/chat")
// Wait for chat to be ready
await waitForElement(page, '[data-testid="chat-textarea"]')
// Send message
await simulateInputPaste(page, "What's the weather?")
await page.click('[data-testid="send-button"]')
// Wait for AI response
await wait(2000)
await waitForElement(page, ".ai-message")
})import { test } from "@playwright/test"
import { generateTestEmail, generateTestPassword, wait } from "@chrryai/waffles"
test("user registration", async ({ page }) => {
const email = generateTestEmail("newuser")
const password = generateTestPassword()
await page.goto("https://app.com/signup")
await page.fill('[name="email"]', email)
await page.fill('[name="password"]', password)
await page.click('button[type="submit"]')
await wait(1000)
await expect(page).toHaveURL(/dashboard/)
})We welcome contributions! Waffles is extracted from Vex's production test suite, and we're always improving it.
Built with β€οΈ by the Vex team