
September 12, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
Shipping a checkout form or booking widget without tests is how regressions reach production on Friday evening. Frontend Testing with Vitest and Playwright pairs a fast Vite-native unit runner with a cross-browser end-to-end engine, so you catch broken logic early and broken UI flows before deploy. On Laravel + Livewire booking apps and WooCommerce storefronts I maintain, that split mirrors how real users interact with the page. This guide walks through setup, config, and CI wiring you can copy into a production project today.
What is Frontend Testing with Vitest and Playwright?
Vitest is a test runner built for the Vite ecosystem. It reuses your Vite config, supports ES modules natively, and runs unit tests in Node with optional browser mode. Playwright drives real browsers through the DevTools protocol. It clicks buttons, fills forms, waits for network idle, and asserts what users actually see.
Together they cover two layers of the testing pyramid. Vitest sits at the base for pure functions, composables, and component logic. Playwright sits near the top for critical user journeys. You do not replace one with the other. A passing Vitest suite does not prove your modal renders correctly after a deploy.
In my experience working on production Laravel applications with Vue or Alpine frontends, this pairing reduces the gap between "tests pass" and "the site works." Backend Laravel feature tests validate HTTP responses. Vitest validates client-side calculations. Playwright validates the full stack through the browser.
How do you set up Vitest for frontend unit tests?
Start from a Vite 8.x project. Vitest ships as a dev dependency and reads the same vite.config.js your app already uses. That matters because path aliases, plugins, and environment variables stay consistent between dev and test.
Install dependencies
Use Node.js 26 LTS and npm 12. Run these commands at the project root:
npm install -D vitest @vitest/coverage-v8 jsdom
npm install -D @testing-library/dom @testing-library/user-event For Vue 3 component tests, add @vue/test-utils. For DOM assertions, @testing-library/jest-dom works well with Vitest's expect API.
Configure vite.config.js
Add a test block inside your existing Vite config. Keep globals off unless your team prefers the Jest-style API:
import { defineConfig } from 'vite'
export default defineConfig({
test: {
environment: 'jsdom',
globals: false,
include: ['resources/js/**/*.{test,spec}.{js,ts}'],
coverage: {
provider: 'v8',
reporter: ['text', 'lcov'],
thresholds: { lines: 70, functions: 70 },
},
},
}) Place test files next to the code they cover. A formatter utility at resources/js/utils/formatNpr.js gets formatNpr.test.js beside it. That co-location makes refactors safer because the test moves with the source.
Write your first Vitest spec
Test pure logic first. These run in under 10 ms each and give immediate feedback in watch mode:
import { describe, it, expect } from 'vitest'
import { formatNpr } from './formatNpr'
describe('formatNpr', () => {
it('formats whole rupees with comma separators', () => {
expect(formatNpr(150000)).toBe('Rs 1,50,000')
})
it('returns zero string for null input', () => {
expect(formatNpr(null)).toBe('Rs 0')
})
}) Run tests locally with npx vitest for watch mode or npx vitest run for a single CI-style pass. Use the JSON formatter tool to inspect API fixture payloads while you write integration specs.
Mock fetch and module boundaries
Vitest provides vi.mock() and vi.spyOn() for isolating modules. Mock external API calls at the module boundary, not inside every test file. A shared mock factory in tests/mocks/api.js keeps specs readable.
On a client project with a custom Laravel eCommerce cart, I mocked the delivery-zone API once. Every component test reused that mock. Duplicated inline mocks drift within weeks.
How do you configure Playwright for end-to-end testing?
Playwright installs browser binaries and a test runner in one package. It works with any frontend stack that serves HTML over HTTP. Laravel apps, WordPress themes, and standalone Vite SPAs all fit the same pattern.
Initialize Playwright
npm init playwright@latest The wizard creates playwright.config.ts, an e2e/ folder, and example specs. Choose TypeScript if your team already uses it. Otherwise JavaScript is fine.
Point tests at your local dev server
Playwright can start the app before tests via the webServer option. For a Vite + Laravel setup, start both processes or use a pre-built preview server:
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
use: {
baseURL: 'http://127.0.0.1:8000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
webServer: {
command: 'php artisan serve',
url: 'http://127.0.0.1:8000',
reuseExistingServer: !process.env.CI,
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
],
}) Official Playwright docs at playwright.dev cover trace viewer, codegen, and fixture patterns. Vitest documentation lives at vitest.dev.
Write resilient Playwright specs
Prefer role-based locators over CSS classes. Classes change during refactors. Roles reflect accessibility and survive markup rewrites.
import { test, expect } from '@playwright/test'
test('guest can submit contact form', async ({ page }) => {
await page.goto('/contact-us')
await page.getByRole('textbox', { name: 'Email' }).fill('dev@example.com')
await page.getByRole('button', { name: 'Send message' }).click()
await expect(page.getByText('Thank you')).toBeVisible()
}) Use page.getByTestId() only when roles are ambiguous. Add data-testid attributes in Blade or Vue templates for elements that lack semantic roles.
Avoid hard-coded page.waitForTimeout() calls. Use expect(locator).toBeVisible() or page.waitForResponse() instead. Fixed sleeps make suites slow and flaky under load.
What should you test at each layer of the frontend stack?
Not every line of UI code deserves an E2E spec. The cost of browser tests is real. A ten-spec Playwright suite can take two minutes. Two hundred Vitest specs often finish in under fifteen seconds.
| Layer | Tool | What to test | What to skip | Typical count |
|---|---|---|---|---|
| Unit | Vitest | Formatters, validators, cart math, date helpers | DOM layout pixel checks | 100–300 specs |
| Component | Vitest + Testing Library | Conditional rendering, event handlers, prop edge cases | Full page navigation | 30–80 specs |
| Integration | Vitest or Playwright API | Form submit + API mock, state store updates | Third-party payment iframes | 10–30 specs |
| E2E | Playwright | Login, checkout, booking, document upload | Every button hover state | 5–20 specs |
On legal-tech portals I have built, Vitest covers NPR fee calculators and date validators. Playwright covers the lead-capture flow from landing page to confirmation email. That split matches how QA and optimisation work should be budgeted on small teams.
Use the regex tester while building Vitest cases for input validation. Regex bugs are easier to catch in unit tests than in a flaky E2E run.
Priority journeys worth E2E coverage
- Authentication and password reset flows
- Payment or booking checkout with gateway sandbox credentials
- File upload and document download on client portals
- Multi-step wizards with session state
- Critical SEO landing pages that must render key content without JavaScript errors
Everything else belongs in Vitest unless a bug proved otherwise. That rule keeps CI fast and maintenance sane. Read regression testing automation for how to schedule these suites against staging before production deploys.
How do you run Vitest and Playwright together in CI?
Run Vitest on every push. Run Playwright on pull requests to main and nightly schedules. Full cross-browser E2E on every commit burns CI minutes fast. A pattern I've seen repeatedly on GitLab CI pipelines: unit tests gate merges, E2E gates releases.
GitLab CI example
stages:
- test
unit_tests:
stage: test
image: node:26
script:
- npm ci
- npx vitest run --coverage
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
e2e_tests:
stage: test
image: mcr.microsoft.com/playwright:v1.55.0-noble
script:
- npm ci
- npx playwright install --with-deps
- npx playwright test
artifacts:
when: on_failure
paths:
- playwright-report/
- test-results/ See GitHub Actions for Laravel testing and deploy for an alternate runner layout. The stage ordering is the same regardless of platform. See also integration testing in CI pipelines for broader pipeline design.
Commit frontend assets built with Vite 8.x if your production server lacks Node. Run Vitest in CI on the same Node version you use locally. Version mismatches cause subtle ESM resolution failures that waste hours.
Shared fixtures and test data
Seed a dedicated test database for E2E. Never point Playwright at production. Use Laravel factories or WordPress test fixtures to create predictable records before each spec.
Playwright's test.beforeEach hook can call an API route that resets state. Keep that route behind an environment check so it never ships to production configs.
Debugging failures locally
- Run a single spec:
npx playwright test e2e/checkout.spec.ts --headed - Open the inspector:
PWDEBUG=1 npx playwright test - Re-run Vitest for one file:
npx vitest run formatNpr.test.js - Compare Vite config between dev and test if imports fail only in CI
The Vite team documents config merging at vite.dev. Align your test environment with that reference when aliases break in CI only.
What are common mistakes with Vitest and Playwright?
Teams new to frontend testing often over-index on E2E because browser tests feel closest to real usage. That choice produces slow pipelines and brittle suites within months.
Another mistake is testing implementation details. Assert on visible text and ARIA roles, not internal component state. Refactors should not break specs when behaviour stays the same.
Skipping data-testid planning early forces brittle CSS selectors later. Add test hooks during feature work, not as a cleanup sprint nobody budgets.
On projects using Vite instead of Webpack, developers sometimes keep a separate Jest config. Drop it. Vitest replaces Jest for Vite projects and removes duplicate transform pipelines.
Finally, ignoring mutation testing and coverage limits gives false confidence. Eighty percent line coverage means little if assertions never check edge cases.
Key Takeaways
- Run Vitest for fast unit and component tests; reserve Playwright for critical user journeys that span pages and network calls.
- Share Vite config between dev and test so path aliases and plugins behave identically in CI.
- Use role-based Playwright locators and auto-waiting assertions instead of fixed timeouts and CSS class selectors.
- Gate every push with Vitest; run Playwright on main-branch merges and nightly schedules to control CI cost.
- Capture Playwright traces on retry so flaky failures become debuggable without reproducing locally.
- Co-locate
*.test.jsfiles with source modules and mock external APIs at module boundaries, not per spec.
People Also Ask
Can Vitest replace Jest for a Vite project?
Yes. Vitest uses a Jest-compatible API for most matchers and mocking helpers. Migration usually means swapping the runner, removing Babel transform config, and pointing tests at the shared Vite config. Teams on Vite 8.x typically finish migration in a day for medium-sized codebases.
Does Playwright work with Laravel Blade apps?
Playwright tests any site served over HTTP. It does not require React or Vue. Point baseURL at your Laravel dev server, use role-based locators for Blade-rendered forms, and seed test data with factories before each spec.
How many E2E tests are enough?
Most business apps need five to twenty Playwright specs covering revenue and compliance paths. If your E2E count exceeds fifty, move detailed checks down to Vitest. The pyramid should be wide at the bottom, narrow at the top.
Should frontend tests run before or after backend tests in CI?
Run Vitest in parallel with backend unit tests since they share no dependencies. Run Playwright after the application server and database seed step succeed. Playwright needs a running app; Vitest does not.
Ship tested frontends with confidence
Frontend Testing with Vitest and Playwright is the most practical JavaScript testing stack in 2026 for Vite-based apps backed by Laravel, WordPress, or standalone SPAs. Start with ten Vitest specs for your highest-risk utilities. Add five Playwright journeys for flows that generate revenue or legal liability. Expand from real bugs, not from coverage targets alone.
If you want help wiring tests into an existing pipeline or recovering a flaky suite, review the web development services and custom software development offerings, browse the project portfolio, or read end-to-end testing with Playwright in CI. For ongoing test maintenance after launch, see support and maintenance services. When you are ready to scope work for your stack, contact us with your repo layout and CI provider.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

