CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vitest--browser

Browser runner for Vitest enabling tests to run in actual browser environments with real DOM APIs and browser-specific features

Pending
Overview
Eval results
Files

assertions.mddocs/

Enhanced Assertions

DOM-specific matchers extending Vitest's expect API with browser-aware assertions and element polling for reliable test assertions in browser environments.

Capabilities

Element Polling

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();
});

DOM-Specific Matchers

Extended assertion methods specifically designed for DOM elements and browser testing.

Visibility Matchers

/**
 * 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();
});

Content Matchers

/**
 * 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"
});

State Matchers

/**
 * 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();
});

Accessibility Matchers

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");
});

Advanced Assertion Patterns

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();
  }
});

Configuration

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();
});

Types

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

docs

assertions.md

commands.md

context.md

index.md

interactions.md

locators.md

providers.md

server.md

utilities.md

tile.json