Render Angular components in VItest Browser Mode.
pnpm add -D vitest-browser-angularTo set up your test environment (with Zone.js or Zoneless), use @analogjs/vitest-angular's setupTestBed() function.
Important: Make sure to use { browserMode: true } when calling setupTestBed() to enable Vitest browser mode's visual test preview functionality.
For detailed setup instructions for both Zone.js and Zoneless configurations, please refer to the Analog Vitest documentation.
The render function supports two query patterns:
import { test, expect } from 'vitest';
import { render } from 'vitest-browser-angular';
@Component({
template: ` <h1>Welcome</h1> `,
})
export class MyComponent {}
test('query elements', async () => {
// Pattern 1: Use locator to query within the component element
const { locator } = await render(MyComponent);
await expect.element(locator.getByText('Welcome')).toBeVisible();
// Pattern 2: Use screen to query from document.body (useful for portals/overlays)
const screen = await render(MyComponent);
await expect.element(screen.getByText('Welcome')).toBeVisible();
await expect.element(screen.getByText('Some Popover Content')).toBeVisible();
});Both locator and screen provide the following query methods:
getByRole- Locate by ARIA role and accessible namegetByText- Locate by text contentgetByLabelText- Locate by associated label textgetByPlaceholder- Locate by placeholder textgetByAltText- Locate by alt text (images)getByTitle- Locate by title attributegetByTestId- Locate by data-testid attribute
When to use which pattern:
locator: (full name: "Component Locator") - queries are scoped to the component's host element. Best for most component tests.screen: Queries start frombaseElement(defaults todocument.body). Use when testing components that render content outside their host element (modals, tooltips, portals).
Access the component's host element directly via container (shortcut for fixture.nativeElement):
const { container, locator } = await render(MyComponent);
expect(container).toBe(locator.element());Customize the root element for screen queries (useful for portal/overlay testing):
const customContainer = document.querySelector('#modal-root');
const screen = await render(ModalComponent, {
baseElement: customContainer,
});
// screen queries now start from customContainer instead of document.bodyPass input values to components using the inputs option:
import { Component, input } from '@angular/core';
@Component({
template: '<h2>{{ name() }}</h2><p>Price: ${{ price() }}</p>',
standalone: true,
})
export class ProductComponent {
name = input('Unknown Product');
price = input(0);
}
test('render with inputs', async () => {
const screen = await render(ProductComponent, {
inputs: {
name: 'Laptop',
price: 1299.99,
},
});
await expect.element(screen.getByText('Laptop')).toBeVisible();
await expect.element(screen.getByText(/Price: \$1299\.99/)).toBeVisible();
});Works with both signal-based inputs (input()) and @Input() decorators.
Enable routing with withRouting: true for components that use routing features but don't require specific route configuration:
import { test, expect } from 'vitest';
import { render } from 'vitest-browser-angular';
import { Component } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
@Component({
template: `
<nav>
<a routerLink="/home">Home</a>
<a routerLink="/about">About</a>
</nav>
<router-outlet></router-outlet>
`,
imports: [RouterLink, RouterOutlet],
})
export class RoutedComponent {}
test('render with simple routing', async () => {
const screen = await render(RoutedComponent, {
withRouting: true,
});
await expect.element(screen.getByText('Home')).toBeVisible();
await expect.element(screen.getByText('About')).toBeVisible();
});Configure specific routes and optionally set an initial route:
import { test, expect } from 'vitest';
import { render } from 'vitest-browser-angular';
import { Component, inject } from '@angular/core';
import { Router, RouterLink, RouterOutlet, Routes } from '@angular/router';
@Component({
template: '<h1>Home Page</h1>',
})
export class HomeComponent {}
@Component({
template: '<h1>About Page</h1>',
standalone: true,
})
export class AboutComponent {}
@Component({
template: `
<nav>
<a routerLink="/home">Home</a>
<a routerLink="/about">About</a>
</nav>
<router-outlet></router-outlet>
`,
imports: [RouterLink, RouterOutlet],
standalone: true,
})
export class AppComponent {
router = inject(Router);
}
const routes: Routes = [
{ path: 'home', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: '', redirectTo: '/home', pathMatch: 'full' },
];
test('render with route configuration', async () => {
const { locator, router } = await render(AppComponent, {
withRouting: {
routes,
initialRoute: '/home',
},
});
await expect.element(locator).toHaveTextContent('Home Page');
// Navigate programmatically
await router.navigate(['/about']);
await expect.element(locator).toHaveTextContent('About Page');
});If you need to add or override component providers, you can use the componentProviders option.
@Component({
template: '<h1>{{ title }}</h1>',
providers: [GreetingService],
})
export class HelloWorldComponent {
title = 'Hello World';
}
test('renders component with service provider', async () => {
const screen = await render(ServiceConsumerComponent, {
componentProviders: [
{ provide: GreetingService, useClass: FakeGreetingService },
],
});
await expect.element(screen.getByText('Fake Greeting')).toBeVisible();
});Want to contribute? Yayy! π
Please read and follow our Contributing Guidelines to learn what are the right steps to take before contributing your time, effort and code.
Thanks π
Be kind to each other and please read our code of conduct.
This project is inspired by the following projects:
vitest-browser-vue angular-testing-library
MIT