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

utilities.mddocs/

Utilities

Development tools for debugging element queries, DOM inspection, and test development with pretty-printing and error formatting to improve the debugging experience.

Capabilities

Debug Function

Console logging utility for inspecting elements and locators during test development.

/**
 * Console debug utility for elements and locators. Logs element information
 * to the console with pretty-printed DOM structure.
 * @param element - Element(s) or locator(s) to debug, or null/undefined
 * @param maxLength - Maximum length of output (default based on environment)
 * @param options - Pretty-printing options
 */
function debug(
  element?: Element | Locator | null | (Element | Locator)[],
  maxLength?: number,
  options?: PrettyDOMOptions
): void;

Usage Examples:

import { debug } from "@vitest/browser/utils";
import { page } from "@vitest/browser/context";

// Debug single element
const button = page.getByRole("button").element();
debug(button);

// Debug locator
const form = page.getByRole("form");
debug(form);

// Debug multiple elements
const inputs = page.getByRole("textbox").elements();
debug(inputs);

// Debug with custom length limit
debug(page.getByTestId("complex-component"), 500);

// Debug with options
debug(button, 1000, {
  highlight: true,
  printFunctionNames: false
});

// Debug null/undefined (shows "null" or "undefined")
const maybeElement = page.getByText("Nonexistent").query();
debug(maybeElement); // Logs: "Unable to find element"

// Debug in test context
test("form validation", async () => {
  const form = page.getByRole("form");
  const submitButton = form.getByRole("button", { name: "Submit" });
  
  // Debug elements before interaction
  debug(form, undefined, { highlight: true });
  debug(submitButton);
  
  await userEvent.click(submitButton);
  
  // Debug state after interaction
  const errorMessages = page.getByRole("alert").elements();
  debug(errorMessages);
});

Pretty DOM Function

Format DOM elements as readable strings for logging and test output.

/**
 * Pretty-print DOM elements as formatted strings
 * @param dom - Element or locator to format, or null/undefined
 * @param maxLength - Maximum length of output string
 * @param options - Pretty-printing formatting options
 * @returns Formatted string representation of the DOM element
 */
function prettyDOM(
  dom?: Element | Locator | undefined | null,
  maxLength?: number,
  options?: PrettyDOMOptions
): string;

Usage Examples:

import { prettyDOM } from "@vitest/browser/utils";
import { page } from "@vitest/browser/context";

// Format element as string
const button = page.getByRole("button").element();
const buttonHTML = prettyDOM(button);
console.log("Button HTML:", buttonHTML);

// Format with length limit
const longContent = page.getByTestId("content").element();
const truncatedHTML = prettyDOM(longContent, 200);

// Format locator
const form = page.getByRole("form");
const formHTML = prettyDOM(form);

// Use in test assertions
test("element structure", () => {
  const menu = page.getByRole("menu").element();
  const menuHTML = prettyDOM(menu);
  
  expect(menuHTML).toContain('role="menuitem"');
  expect(menuHTML).toContain('aria-expanded="true"');
});

// Custom formatting options
const customHTML = prettyDOM(button, 500, {
  highlight: false,
  printFunctionNames: true
});

// Handle null/undefined
const missingElement = page.getByText("Does not exist").query();
const result = prettyDOM(missingElement); // Returns empty string or error message

Element Error Function

Create formatted error messages for missing elements with helpful debugging information.

/**
 * Create error for missing elements with helpful debugging context
 * @param selector - Selector string that was used to find the element
 * @param container - Container element that was searched (optional)
 * @returns Error object with formatted message and debugging information
 */
function getElementError(selector: string, container?: Element): Error;

Usage Examples:

import { getElementError } from "@vitest/browser/utils";
import { page } from "@vitest/browser/context";

// Create helpful error for missing elements
const findButtonSafely = (buttonText: string) => {
  const button = page.getByText(buttonText).query();
  
  if (!button) {
    throw getElementError(`text="${buttonText}"`);
  }
  
  return button;
};

// Use with custom container
const findInContainer = (selector: string, container: Element) => {
  const element = container.querySelector(selector);
  
  if (!element) {
    throw getElementError(selector, container);
  }
  
  return element;
};

// Enhanced error reporting in custom functions
const waitForElement = async (locator: Locator, timeout = 5000) => {
  const startTime = Date.now();
  
  while (Date.now() - startTime < timeout) {
    const element = locator.query();
    if (element) return element;
    
    await new Promise(resolve => setTimeout(resolve, 100));
  }
  
  // Create informative error
  throw getElementError(locator.selector);
};

// Integration with test helpers
const assertElementExists = (selector: string, message?: string) => {
  const element = document.querySelector(selector);
  
  if (!element) {
    const error = getElementError(selector);
    if (message) {
      error.message = `${message}\n\n${error.message}`;
    }
    throw error;
  }
  
  return element;
};

Element Locator Selectors

Create locator selector methods from existing DOM elements.

/**
 * Create locator selectors for an existing DOM element
 * @param element - DOM element to create selectors for
 * @returns LocatorSelectors object with all selector methods scoped to the element
 */
function getElementLocatorSelectors(element: Element): LocatorSelectors;

Usage Examples:

import { getElementLocatorSelectors } from "@vitest/browser/utils";

// Create selectors for existing element
const form = document.querySelector("#login-form");
const formSelectors = getElementLocatorSelectors(form);

// Use selector methods scoped to the element
const usernameInput = formSelectors.getByLabelText("Username");
const submitButton = formSelectors.getByRole("button", { name: "Submit" });
const errorAlert = formSelectors.getByRole("alert");

// Useful for component testing
test("login form component", async () => {
  // Mount component (framework-specific)
  const component = mountLoginForm();
  const formElement = component.container.querySelector("form");
  
  // Create scoped selectors
  const form = getElementLocatorSelectors(formElement);
  
  // Test interactions within the component
  await userEvent.fill(form.getByLabelText("Username"), "testuser");
  await userEvent.fill(form.getByLabelText("Password"), "password");
  await userEvent.click(form.getByRole("button", { name: "Login" }));
  
  // Assert results
  expect(form.getByText("Welcome testuser")).toBeVisible();
});

// Integration with page object pattern
class LoginPage {
  private selectors: LocatorSelectors;

  constructor(container: Element) {
    this.selectors = getElementLocatorSelectors(container);
  }

  async login(username: string, password: string) {
    await userEvent.fill(this.selectors.getByLabelText("Username"), username);
    await userEvent.fill(this.selectors.getByLabelText("Password"), password);
    await userEvent.click(this.selectors.getByRole("button", { name: "Login" }));
  }

  getErrorMessage() {
    return this.selectors.getByRole("alert");
  }
}

Debugging Workflows

Common debugging patterns and workflows using the utilities.

Element Discovery:

import { debug, prettyDOM } from "@vitest/browser/utils";
import { page } from "@vitest/browser/context";

// Debug element discovery process
test("find submit button", async () => {
  // Debug entire form structure
  debug(page.getByRole("form"));
  
  // Try different selector strategies
  console.log("By role:", prettyDOM(page.getByRole("button", { name: "Submit" })));
  console.log("By text:", prettyDOM(page.getByText("Submit")));
  console.log("By test ID:", prettyDOM(page.getByTestId("submit-btn")));
  
  // Debug what's actually available
  const allButtons = page.getByRole("button").elements();
  console.log(`Found ${allButtons.length} buttons:`);
  allButtons.forEach((btn, i) => {
    console.log(`Button ${i + 1}:`, prettyDOM(btn, 100));
  });
});

State Debugging:

// Debug element state changes
test("form validation states", async () => {
  const form = page.getByRole("form");
  const input = form.getByLabelText("Email");
  
  console.log("Initial state:");
  debug(input);
  
  await userEvent.fill(input, "invalid-email");
  
  console.log("After invalid input:");
  debug(input);
  debug(form.getByRole("alert")); // Error message
  
  await userEvent.clear(input);
  await userEvent.fill(input, "valid@example.com");
  
  console.log("After valid input:");
  debug(input);
});

Error Investigation:

import { getElementError, debug } from "@vitest/browser/utils";

// Enhanced error reporting
const findElementWithDebug = (selector: string) => {
  try {
    return page.locator(selector).element();
  } catch (error) {
    console.log("Element not found. Available elements:");
    debug(document.body, 1000);
    
    throw getElementError(selector);
  }
};

// Test debugging helper
const debugTest = async (testName: string, testFn: () => Promise<void>) => {
  console.log(`\n=== Debugging: ${testName} ===`);
  
  try {
    await testFn();
    console.log("✅ Test passed");
  } catch (error) {
    console.log("❌ Test failed:");
    console.log("Current page state:");
    debug(document.body, 2000);
    throw error;
  }
};

Performance Debugging

Use utilities to investigate performance issues.

import { prettyDOM } from "@vitest/browser/utils";

// Measure DOM complexity
const measureDOMComplexity = (element: Element) => {
  const html = prettyDOM(element);
  const elementCount = (html.match(/<[^>]+>/g) || []).length;
  const textLength = html.length;
  
  console.log(`DOM Complexity:
    - Elements: ${elementCount}
    - Text length: ${textLength}
    - Estimated complexity: ${elementCount * 10 + textLength}`);
};

// Debug slow selector performance
const debugSlowSelector = async (selector: string) => {
  const start = performance.now();
  const elements = document.querySelectorAll(selector);
  const end = performance.now();
  
  console.log(`Selector "${selector}":
    - Found: ${elements.length} elements
    - Time: ${(end - start).toFixed(2)}ms`);
    
  if (elements.length > 0) {
    console.log("First element:", prettyDOM(elements[0], 200));
  }
};

Types

Utility function types and options:

/**
 * Options for pretty-printing DOM elements
 */
interface PrettyDOMOptions {
  /** Whether to highlight the element in output */
  highlight?: boolean;
  /** Whether to include function names in output */
  printFunctionNames?: boolean;
  /** Additional formatting options (specific to implementation) */
  [key: string]: any;
}

/**
 * Locator selector methods interface
 */
interface LocatorSelectors {
  getByRole(role: string, options?: LocatorByRoleOptions): Locator;
  getByLabelText(text: string | RegExp, options?: LocatorOptions): Locator;
  getByAltText(text: string | RegExp, options?: LocatorOptions): Locator;
  getByPlaceholder(text: string | RegExp, options?: LocatorOptions): Locator;
  getByText(text: string | RegExp, options?: LocatorOptions): Locator;
  getByTitle(text: string | RegExp, options?: LocatorOptions): Locator;
  getByTestId(text: string | RegExp): Locator;
}

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