Simple and complete Vue DOM testing utilities that encourage good testing practices
—
Utilities for waiting for asynchronous operations and DOM changes in Vue components.
Wait for a condition to be true by repeatedly executing a callback function until it succeeds or times out.
/**
* Wait for a condition to be true
* @param callback - Function to execute repeatedly
* @param options - Wait configuration options
* @returns Promise resolving to callback return value
*/
function waitFor<T>(
callback: () => T | Promise<T>,
options?: {
/** Element to observe for mutations */
container?: Element;
/** Maximum time to wait in milliseconds */
timeout?: number;
/** Interval between attempts in milliseconds */
interval?: number;
/** Custom timeout error handler */
onTimeout?: (error: Error) => Error;
/** MutationObserver configuration */
mutationObserverOptions?: MutationObserverInit;
}
): Promise<T>;Usage Examples:
import { render, screen, waitFor, fireEvent } from "@testing-library/vue";
import AsyncComponent from "./AsyncComponent.vue";
render(AsyncComponent);
// Wait for element to appear
await waitFor(() => {
expect(screen.getByText("Loading complete")).toBeInTheDocument();
});
// Wait for API call to complete
const button = screen.getByRole("button", { name: "Load Data" });
await fireEvent.click(button);
await waitFor(() => {
expect(screen.getByText("Data loaded successfully")).toBeInTheDocument();
}, { timeout: 5000 });
// Wait for element to have specific attribute
await waitFor(() => {
const element = screen.getByTestId("status");
expect(element).toHaveAttribute("data-status", "complete");
});Wait for one or more elements to be removed from the DOM.
/**
* Wait for element(s) to be removed from DOM
* @param element - Element, elements array, or callback returning elements to wait for removal
* @param options - Wait configuration options
* @returns Promise that resolves when elements are removed
*/
function waitForElementToBeRemoved<T>(
element: Element | Element[] | (() => Element | Element[] | null),
options?: {
/** Element to observe for mutations */
container?: Element;
/** Maximum time to wait in milliseconds */
timeout?: number;
/** Interval between attempts in milliseconds */
interval?: number;
/** Custom timeout error handler */
onTimeout?: (error: Error) => Error;
/** MutationObserver configuration */
mutationObserverOptions?: MutationObserverInit;
}
): Promise<void>;Usage Examples:
// Wait for loading spinner to disappear
const spinner = screen.getByTestId("loading-spinner");
await waitForElementToBeRemoved(spinner);
// Wait for multiple elements to be removed
const modals = screen.getAllByRole("dialog");
await waitForElementToBeRemoved(modals);
// Wait using a callback (handles cases where element might not exist initially)
await waitForElementToBeRemoved(() =>
screen.queryByText("Temporary message")
);
// With custom timeout
await waitForElementToBeRemoved(
screen.getByText("Processing..."),
{ timeout: 10000 }
);interface WaitForOptions {
/** Default container for mutation observation (document.body) */
container?: Element;
/** Default timeout in milliseconds (1000ms) */
timeout?: number;
/** Default interval between checks in milliseconds (50ms) */
interval?: number;
/** Custom error handler for timeouts */
onTimeout?: (error: Error) => Error;
/** MutationObserver options for DOM change detection */
mutationObserverOptions?: MutationObserverInit;
}interface MutationObserverInit {
/** Observe attribute changes */
attributes?: boolean;
/** List of attribute names to observe */
attributeFilter?: string[];
/** Include old attribute values in mutations */
attributeOldValue?: boolean;
/** Observe character data changes */
characterData?: boolean;
/** Include old character data in mutations */
characterDataOldValue?: boolean;
/** Observe child list changes */
childList?: boolean;
/** Observe all descendant mutations */
subtree?: boolean;
}import { render, screen, waitFor, fireEvent } from "@testing-library/vue";
// Test loading → success flow
const loadButton = screen.getByRole("button", { name: "Load Data" });
await fireEvent.click(loadButton);
// Wait for loading to start
await waitFor(() => {
expect(screen.getByText("Loading...")).toBeInTheDocument();
});
// Wait for loading to finish
await waitForElementToBeRemoved(screen.getByText("Loading..."));
// Verify success state
expect(screen.getByText("Data loaded")).toBeInTheDocument();// Test async form validation
const emailInput = screen.getByLabelText("Email");
const submitButton = screen.getByRole("button", { name: "Submit" });
await fireEvent.update(emailInput, "invalid-email");
await fireEvent.click(submitButton);
// Wait for validation error
await waitFor(() => {
expect(screen.getByText("Please enter a valid email")).toBeInTheDocument();
});// Test reactive state updates
const toggleButton = screen.getByRole("button", { name: "Toggle Theme" });
await fireEvent.click(toggleButton);
// Wait for theme to change
await waitFor(() => {
const root = document.documentElement;
expect(root).toHaveAttribute("data-theme", "dark");
});// Test Vue Router navigation
const navLink = screen.getByRole("link", { name: "About" });
await fireEvent.click(navLink);
// Wait for new page content
await waitFor(() => {
expect(screen.getByText("About Page")).toBeInTheDocument();
});When waitFor times out, it throws a detailed error with the last error from the callback:
try {
await waitFor(() => {
expect(screen.getByText("Never appears")).toBeInTheDocument();
}, { timeout: 1000 });
} catch (error) {
// Error includes timeout info and last expectation failure
console.log(error.message);
}await waitFor(() => {
expect(screen.getByText("Content")).toBeInTheDocument();
}, {
timeout: 5000,
onTimeout: (error) => {
return new Error(`Custom timeout message: ${error.message}`);
}
});Configure default timeouts and intervals using DOM Testing Library's configure function:
/**
* Configure global library behavior
* @param options - Configuration options
*/
function configure(options: {
/** Default timeout for async utilities in milliseconds */
asyncUtilTimeout?: number;
/** Attribute name used for getByTestId queries */
testIdAttribute?: string;
/** Whether to include hidden elements by default */
defaultHidden?: boolean;
/** CSS selector for elements to ignore */
defaultIgnore?: string;
/** Show original stack traces in errors */
showOriginalStackTrace?: boolean;
/** Enable query suggestions in error messages */
throwSuggestions?: boolean;
}): void;
/**
* Get current configuration
* @returns Current configuration object
*/
function getConfig(): {
asyncUtilTimeout: number;
testIdAttribute: string;
defaultHidden: boolean;
defaultIgnore: string;
showOriginalStackTrace: boolean;
throwSuggestions: boolean;
};Usage Examples:
import { configure } from "@testing-library/vue";
// Configure timeout for all async utilities
configure({
asyncUtilTimeout: 2000,
});
// Configure test ID attribute
configure({
testIdAttribute: 'data-cy',
});
// Enable query suggestions for better error messages
configure({
throwSuggestions: true,
});Install with Tessl CLI
npx tessl i tessl/npm-testing-library--vue