Browser runner for Vitest enabling tests to run in actual browser environments with real DOM APIs and browser-specific features
—
Development tools for debugging element queries, DOM inspection, and test development with pretty-printing and error formatting to improve the debugging experience.
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);
});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 messageCreate 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;
};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");
}
}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;
}
};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));
}
};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