Browser runner for Vitest enabling tests to run in actual browser environments with real DOM APIs and browser-specific features
—
DOM-specific matchers extending Vitest's expect API with browser-aware assertions and element polling for reliable test assertions in browser environments.
Enhanced expectation API that polls for elements to handle asynchronous DOM changes.
/**
* Element polling expectation - shorthand for expect.poll(() => locator.element())
* Waits for element to exist and be available for assertions
* @param element - Element, locator, or null to poll for
* @param options - Polling options including timeout and interval
* @returns Promise-based assertion interface for DOM elements
*/
expect.element<T extends Element | Locator | null>(
element: T,
options?: ExpectPollOptions
): PromisifyDomAssertion<Awaited<Element | null>>;
interface ExpectPollOptions {
/** Maximum time to wait for condition in milliseconds */
timeout?: number;
/** Polling interval in milliseconds */
interval?: number;
/** Custom error message */
message?: string;
}Usage Examples:
import { expect } from "vitest";
import { page } from "@vitest/browser/context";
// Wait for element to appear and assert visibility
await expect.element(page.getByText("Loading complete")).toBeVisible();
// Poll with custom timeout
await expect.element(
page.getByRole("alert"),
{ timeout: 10000 }
).toBeInTheDocument();
// Poll for element that might not exist
await expect.element(
page.getByText("Optional message").query(),
{ timeout: 2000 }
).toBeNull();
// Poll with custom interval and message
await expect.element(
page.getByTestId("data-loaded"),
{
timeout: 5000,
interval: 100,
message: "Data should load within 5 seconds"
}
).toBeVisible();
// Complex polling scenarios
test("async form submission", async () => {
await userEvent.click(page.getByRole("button", { name: "Submit" }));
// Wait for loading spinner
await expect.element(page.getByTestId("spinner")).toBeVisible();
// Wait for spinner to disappear
await expect.element(page.getByTestId("spinner")).not.toBeVisible();
// Wait for success message
await expect.element(page.getByText("Form submitted successfully")).toBeVisible();
});Extended assertion methods specifically designed for DOM elements and browser testing.
/**
* Assert element visibility states
*/
interface DOMMatchers {
/** Element is visible to the user */
toBeVisible(): Promise<void>;
/** Element exists in DOM but may not be visible */
toBeInTheDocument(): Promise<void>;
/** Element is hidden from the user */
toBeHidden(): Promise<void>;
}Usage Examples:
// Basic visibility assertions
await expect.element(page.getByRole("button")).toBeVisible();
await expect.element(page.getByTestId("hidden-panel")).toBeHidden();
await expect.element(page.getByText("Success")).toBeInTheDocument();
// Negative assertions
await expect.element(page.getByRole("alert")).not.toBeVisible();
await expect.element(page.getByText("Error")).not.toBeInTheDocument();
// Conditional visibility
test("menu visibility", async () => {
const menu = page.getByRole("menu");
const menuButton = page.getByRole("button", { name: "Menu" });
// Menu initially hidden
await expect.element(menu).toBeHidden();
// Click to show menu
await userEvent.click(menuButton);
await expect.element(menu).toBeVisible();
// Click to hide menu
await userEvent.click(menuButton);
await expect.element(menu).toBeHidden();
});/**
* Assert element content and text
*/
interface ContentMatchers {
/** Element contains specific text content */
toHaveTextContent(text: string | RegExp): Promise<void>;
/** Input element has specific value */
toHaveValue(value: string | number): Promise<void>;
/** Element has specific attribute with value */
toHaveAttribute(name: string, value?: string | RegExp): Promise<void>;
/** Element has specific CSS class */
toHaveClass(className: string | RegExp | (string | RegExp)[]): Promise<void>;
/** Element has specific CSS styles */
toHaveStyle(style: string | object): Promise<void>;
/** Element is empty (has no content) */
toBeEmptyDOMElement(): Promise<void>;
/** Element contains another element */
toContainElement(element: Element): Promise<void>;
/** Element contains specific HTML content */
toContainHTML(html: string): Promise<void>;
}Usage Examples:
// Text content assertions
await expect.element(page.getByRole("heading")).toHaveTextContent("Welcome");
await expect.element(page.getByTestId("counter")).toHaveTextContent(/\d+/);
// Form value assertions
await expect.element(page.getByLabelText("Username")).toHaveValue("john");
await expect.element(page.getByRole("slider")).toHaveValue(50);
// Attribute assertions
await expect.element(page.getByRole("link")).toHaveAttribute("href", "/home");
await expect.element(page.getByRole("button")).toHaveAttribute("disabled");
await expect.element(page.getByTestId("image")).toHaveAttribute("alt", /profile/i);
// CSS class assertions
await expect.element(page.getByRole("alert")).toHaveClass("error");
await expect.element(page.getByTestId("card")).toHaveClass(["card", "shadow"]);
await expect.element(page.getByRole("button")).toHaveClass(/btn-primary/);
// Style assertions
await expect.element(page.getByTestId("modal")).toHaveStyle("display: block");
await expect.element(page.getByRole("progressbar")).toHaveStyle({
width: "75%",
backgroundColor: "green"
});/**
* Assert element states and properties
*/
interface StateMatchers {
/** Form control is disabled */
toBeDisabled(): Promise<void>;
/** Form control is enabled */
toBeEnabled(): Promise<void>;
/** Checkbox or radio is checked */
toBeChecked(): Promise<void>;
/** Checkbox is partially checked (indeterminate state) */
toBePartiallyChecked(): Promise<void>;
/** Element has focus */
toHaveFocus(): Promise<void>;
/** Select element has specific option selected */
toHaveDisplayValue(value: string | RegExp | (string | RegExp)[]): Promise<void>;
/** Form element is required */
toBeRequired(): Promise<void>;
/** Form element is invalid */
toBeInvalid(): Promise<void>;
/** Form element is valid */
toBeValid(): Promise<void>;
/** Form has specific values for all fields */
toHaveFormValues(values: Record<string, any>): Promise<void>;
/** Input has specific text selection */
toHaveSelection(selection: { start: number; end: number }): Promise<void>;
}Usage Examples:
// Form state assertions
await expect.element(page.getByLabelText("Submit")).toBeDisabled();
await expect.element(page.getByLabelText("Email")).toBeEnabled();
await expect.element(page.getByLabelText("Terms")).toBeChecked();
await expect.element(page.getByRole("textbox")).toHaveFocus();
// Validation state assertions
await expect.element(page.getByLabelText("Email")).toBeRequired();
await expect.element(page.getByLabelText("Invalid Email")).toBeInvalid();
await expect.element(page.getByLabelText("Valid Email")).toBeValid();
// Select assertions
await expect.element(page.getByLabelText("Country")).toHaveDisplayValue("United States");
await expect.element(page.getByLabelText("Multiple Select")).toHaveDisplayValue(["Option 1", "Option 2"]);
// Interactive state testing
test("form interaction states", async () => {
const input = page.getByLabelText("Username");
const button = page.getByRole("button", { name: "Submit" });
// Initially disabled
await expect.element(button).toBeDisabled();
// Enable after input
await userEvent.fill(input, "testuser");
await expect.element(button).toBeEnabled();
// Focus management
await userEvent.click(input);
await expect.element(input).toHaveFocus();
await userEvent.tab();
await expect.element(button).toHaveFocus();
});Specialized matchers for testing accessibility properties and ARIA attributes.
/**
* Accessibility-focused assertions
*/
interface AccessibilityMatchers {
/** Element has specific accessible name */
toHaveAccessibleName(name: string | RegExp): Promise<void>;
/** Element has specific accessible description */
toHaveAccessibleDescription(description: string | RegExp): Promise<void>;
/** Element has specific accessible error message */
toHaveAccessibleErrorMessage(message: string | RegExp): Promise<void>;
/** Element has specific ARIA role */
toHaveRole(role: string): Promise<void>;
/** Element has specific ARIA attribute */
toHaveAriaAttribute(name: string, value?: string | RegExp): Promise<void>;
}Usage Examples:
// Accessible name assertions
await expect.element(page.getByRole("button")).toHaveAccessibleName("Submit Form");
await expect.element(page.getByRole("img")).toHaveAccessibleName(/profile picture/i);
// Accessible description
await expect.element(page.getByRole("textbox")).toHaveAccessibleDescription("Enter your email address");
// ARIA role assertions
await expect.element(page.getByTestId("alert")).toHaveRole("alert");
await expect.element(page.getByTestId("navigation")).toHaveRole("navigation");
// ARIA attribute assertions
await expect.element(page.getByRole("tab")).toHaveAriaAttribute("selected", "true");
await expect.element(page.getByRole("menu")).toHaveAriaAttribute("expanded", "false");
await expect.element(page.getByRole("progressbar")).toHaveAriaAttribute("valuenow", "75");
// Comprehensive accessibility testing
test("accessible form", async () => {
const form = page.getByRole("form");
const emailInput = form.getByLabelText("Email");
const submitButton = form.getByRole("button", { name: "Submit" });
// Form structure
await expect.element(form).toHaveRole("form");
await expect.element(form).toHaveAccessibleName("Contact Form");
// Input accessibility
await expect.element(emailInput).toHaveRole("textbox");
await expect.element(emailInput).toBeRequired();
await expect.element(emailInput).toHaveAccessibleDescription("We'll never share your email");
// Button accessibility
await expect.element(submitButton).toHaveRole("button");
await expect.element(submitButton).toHaveAccessibleName("Submit Contact Form");
});Complex assertion scenarios for comprehensive browser testing.
Waiting for Multiple Elements:
test("page load completion", async () => {
// Wait for multiple elements to appear
await Promise.all([
expect.element(page.getByRole("navigation")).toBeVisible(),
expect.element(page.getByRole("main")).toBeVisible(),
expect.element(page.getByRole("contentinfo")).toBeVisible()
]);
// Ensure loading indicators are gone
await Promise.all([
expect.element(page.getByTestId("loading-spinner")).not.toBeVisible(),
expect.element(page.getByText("Loading...")).not.toBeInTheDocument()
]);
});Conditional Assertions:
test("conditional error display", async () => {
await userEvent.click(page.getByRole("button", { name: "Submit" }));
// Wait for either success or error
const successMessage = page.getByText("Success").query();
const errorMessage = page.getByRole("alert").query();
if (successMessage) {
await expect.element(successMessage).toBeVisible();
await expect.element(errorMessage).not.toBeInTheDocument();
} else {
await expect.element(errorMessage).toBeVisible();
await expect.element(errorMessage).toHaveTextContent(/error/i);
}
});List and Collection Assertions:
test("dynamic list updates", async () => {
const list = page.getByRole("list");
// Initial state
await expect.element(list).toBeVisible();
// Add item
await userEvent.click(page.getByRole("button", { name: "Add Item" }));
// Wait for new item to appear
await expect.element(page.getByText("New Item")).toBeVisible();
// Count items
const items = page.getByRole("listitem").elements();
expect(items).toHaveLength(4);
// Check each item
for (let i = 0; i < items.length; i++) {
await expect.element(page.getByRole("listitem").nth(i)).toBeVisible();
}
});Configure polling behavior globally or per-test:
// In test setup or vitest.config.ts
expect.poll.timeout = 10000; // Default timeout for expect.element
expect.poll.interval = 200; // Default polling interval
// Per-test configuration
test("slow loading test", async () => {
// Override for this test
await expect.element(
page.getByText("Slow content"),
{ timeout: 30000, interval: 500 }
).toBeVisible();
});Enhanced assertion type definitions:
/**
* Options for element polling expectations
*/
interface ExpectPollOptions {
timeout?: number;
interval?: number;
message?: string;
}
/**
* Promise-based DOM assertion interface
*/
type PromisifyDomAssertion<T> = {
[K in keyof Assertion<T>]: Assertion<T>[K] extends (...args: infer A) => infer R
? (...args: A) => Promise<R>
: Assertion<T>[K];
};
/**
* Extended expect interface with element method
*/
interface ExpectStatic {
element<T extends Element | Locator | null>(
element: T,
options?: ExpectPollOptions
): PromisifyDomAssertion<Awaited<Element | null>>;
}Install with Tessl CLI
npx tessl i tessl/npm-vitest--browser