Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Frontend Testing with Vitest and Playwright

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.

Frontend Testing PyramidPlaywright E2EFew, slow, high valueIntegration TestsAPI + component wiringVitest Unit TestsMany, fast, isolatedPure functionsFormatters, validatorsVue composablesStores, hooksCritical pathsLogin, checkout
Frontend Testing with Vitest and Playwright mapped to the classic testing pyramid — many fast unit tests, fewer browser E2E specs

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.

Vitest Execution FlowSource File*.test.jsVite TransformESM + aliasesjsdomDOM environmentAssertionsexpect()Watch Mode Feedback LoopFile changeRe-run specPass / failTypical unit test completes in under 50 msFull suite runs in seconds, not minutes
Vitest transforms source through Vite, runs assertions in jsdom, and re-runs changed specs instantly in watch mode

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.

LayerToolWhat to testWhat to skipTypical count
UnitVitestFormatters, validators, cart math, date helpersDOM layout pixel checks100–300 specs
ComponentVitest + Testing LibraryConditional rendering, event handlers, prop edge casesFull page navigation30–80 specs
IntegrationVitest or Playwright APIForm submit + API mock, state store updatesThird-party payment iframes10–30 specs
E2EPlaywrightLogin, checkout, booking, document uploadEvery button hover state5–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.

Playwright E2E FlowLaunchBrowserNavigatebaseURLInteractClick, typeAssertVisible textFailure Artifacts on RetryScreenshotPNG captureTrace fileTimeline replayVideoOptional recordDebug flaky tests with npx playwright show-trace trace.zipCodegen speeds up locator discovery: npx playwright codegen
Playwright launches a real browser, interacts with the page, and captures traces plus screenshots when assertions fail

Priority journeys worth E2E coverage

  1. Authentication and password reset flows
  2. Payment or booking checkout with gateway sandbox credentials
  3. File upload and document download on client portals
  4. Multi-step wizards with session state
  5. 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.

CI Pipeline StagesGit pushEvery commitVitestUnit + coverageBuildVite assetsPlaywrightE2E specsDeployOn greenGate Strategy by BranchFeature branchVitest only — fast feedbackMain / releaseVitest + Playwright requiredParallel shards cut Playwright time: --shard=1/4Cache node_modules and Playwright browsers between runs
Run Vitest on every push for fast feedback; gate merges to main with Playwright E2E plus asset build verification

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.js files 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

Vitest runs fast unit and component tests inside Vite; Playwright drives real browsers for end-to-end flows. Together they cover logic bugs in milliseconds and verify clicks, forms, and navigation across Chromium, Firefox, and WebKit.

Start from a Vite 8.x project on Node.js 26 LTS with npm 12. Install vitest, @vitest/coverage-v8, and jsdom as dev dependencies, plus @testing-library/dom and @testing-library/user-event for DOM interaction. Add @vue/test-utils if you test Vue 3 components. Add a test block to your existing vite.config.js with environment set to jsdom, globals off, and include patterns pointing at resources/js test files. Co-locate specs beside source modules, for example formatNpr.test.js next to formatNpr.js. Run npx vitest for watch mode or npx vitest run for a single CI-style pass.

Run npm init playwright@latest at the project root. The wizard creates playwright.config.ts, an e2e folder, and example specs. Point baseURL at your local app, commonly http://127.0.0.1:8000 for Laravel. Use the webServer option to start php artisan serve before tests, with reuseExistingServer enabled locally but disabled in CI. Enable fullyParallel, set retries to two in CI, and configure trace on-first-retry plus screenshot only-on-failure. Define browser projects for Chromium and Firefox at minimum. Playwright works with Laravel apps, WordPress themes, and standalone Vite SPAs because it only needs HTML served over HTTP.

Yes. Vitest exposes a Jest-compatible expect API and mocking helpers like vi.mock(). Migration means swapping the runner, removing separate Babel transform config, and pointing tests at your shared Vite config so aliases and plugins stay consistent.

Yes. Playwright tests any site over HTTP and does not require React or Vue. Point baseURL at your Laravel dev server, use role-based locators on Blade forms, and seed predictable records with factories before each spec.

Most business apps need five to twenty Playwright specs for revenue and compliance paths. If E2E count exceeds fifty, move detailed checks down to Vitest where the pyramid stays wide at the bottom.

Vitest handles formatters, validators, cart math, date helpers, conditional rendering, event handlers, and prop edge cases. Aim for roughly one hundred to three hundred unit specs and thirty to eighty component specs. Playwright covers login, checkout, booking, document upload, multi-step wizards, and critical SEO landing pages. Reserve five to twenty E2E specs for journeys that span pages and network calls. Skip DOM pixel checks and every button hover in E2E. On legal-tech portals, Vitest covers NPR fee calculators while Playwright covers lead capture from landing page to confirmation.

Run Vitest on every push for fast feedback. Run Playwright on pull requests to main and on nightly schedules because full cross-browser E2E on every commit burns CI minutes quickly. A GitLab CI pattern gates merges with unit tests and gates releases with E2E. Use node:26 for Vitest jobs and the mcr.microsoft.com/playwright:v1.55.0-noble image for E2E, running npm ci, npx vitest run --coverage, then npx playwright install --with-deps followed by npx playwright test. Publish coverage and failure artifacts including playwright-report and test-results directories.

Run Vitest in parallel with backend unit tests because they share no running application dependency. Run Playwright only after the application server and database seed step succeed, since E2E specs need a live HTTP server and predictable test data. This ordering keeps fast checks early and expensive browser suites last.

Teams often over-index on E2E because browser tests feel realistic, producing slow pipelines and brittle suites within months. Testing implementation details instead of visible text and ARIA roles breaks specs during harmless refactors. Skipping data-testid planning early forces brittle CSS selectors later. Keeping a separate Jest config on Vite projects duplicates transform pipelines when Vitest already reuses vite.config.js. Ignoring coverage thresholds gives false confidence when assertions never hit edge cases. High line coverage alone does not prove modal rendering or checkout flows work after deploy.

Prefer role-based locators such as getByRole over CSS classes because classes change during refactors while roles reflect accessibility. Use getByTestId only when roles are ambiguous, adding data-testid in Blade or Vue templates during feature work. Avoid page.waitForTimeout fixed sleeps. Use expect locator toBeVisible or page.waitForResponse so suites stay fast and stable under load. Capture traces on first retry in CI so flaky failures become debuggable without local reproduction.

Use vi.mock and vi.spyOn to isolate modules at the boundary rather than duplicating inline mocks in every spec file. Create a shared mock factory in tests/mocks/api.js and reuse it across component tests. On a custom Laravel eCommerce cart project, mocking the delivery-zone API once kept every component spec readable. Duplicated inline mocks drift within weeks and hide regressions in API contract assumptions.

For Playwright, run a single spec with npx playwright test e2e/checkout.spec.ts --headed, or open the inspector with PWDEBUG=1 npx playwright test. For Vitest, re-run one file with npx vitest run formatNpr.test.js. If imports fail only in CI, compare Vite config between dev and test environments because path aliases and plugins must merge identically. Align with vite.dev config merging documentation when aliases break in CI but work locally.

Co-locate test files next to the code they cover inside resources/js. Configure include patterns such as resources/js/*/.{test,spec}.{js,ts} in the Vite test block. A formatter at resources/js/utils/formatNpr.js gets formatNpr.test.js beside it so refactors move source and tests together. This co-location makes ownership obvious and reduces the chance of orphaned specs after renames.

Seed a dedicated test database for E2E and never point Playwright at production. Use Laravel factories or WordPress test fixtures to create predictable records before each spec. A test.beforeEach hook can call an API route that resets state, but keep that route behind an environment check so it never ships in production configuration. Shared fixtures prevent specs from depending on stale manual data and stop accidental writes to live customer records during CI runs.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: