web/docs/test.md
This document is the single source of truth for automated frontend tests under web/. Tests should protect product behavior and make refactoring safer. They are not a file-by-file completion exercise. Dify UI owns its package-specific test boundary in the Dify UI testing contract.
Write or update a test when a change affects a stable, observable contract:
Do not add a test only because:
useState, useEffect, useMemo, or useCallback.For visual-only changes, verify the real UI at representative widths and states. Use browser, screenshot, Storybook, or end-to-end coverage when the risk justifies automation.
Coverage is a diagnostic signal, not a quality target. This guide defines no required percentage and reviewers should not request tests solely to increase coverage. Use a report to find suspicious gaps, then decide whether each gap represents a product risk worth protecting.
Use the smallest boundary that includes the behavior owner and proves the product contract without coupling the test to implementation:
happy-dom cannot represent faithfully.Test the behavior owner. Barrel exports, pass-through wrappers, and purely presentational children do not need separate tests when the owning feature already proves their contract. Do not repeat generic behavior already owned by Base UI, React Aria, or the browser; test Dify's integration, overrides, and known regressions.
happy-dom is the default choice for tests under web/. Use the unit project for pure logic, hooks, and DOM-observable component or feature behavior that does not depend on a browser's rendering engine. This split follows Vitest test projects and Why Browser Mode.
Use the browser project only when the asserted contract depends on browser-owned behavior that happy-dom cannot represent faithfully, such as:
Rendering UI, reducing mocks, increasing confidence, or raising coverage is not enough reason to use Browser Mode. Each *.browser.spec.{ts,tsx} test under web/app/ must name the browser-owned behavior and why happy-dom is insufficient, exercise the smallest owner through semantic locators, and justify its additional runtime. Do not use forced interaction, fixed sleeps, private DOM or CSS assertions, or real network requests.
Browser Mode remains a focused component or feature test and currently proves Chromium only. Use the end-to-end suite for a running application, authentication, real routing, backend APIs, persistence, or complete journeys.
null, undefined, or extreme values without a reachable scenario.Prefer selectors in this order:
getByRole with an accessible name.getByLabelText for labeled form controls.getByText, getByPlaceholderText, or other user-visible queries when appropriate.getByTestId only for boundaries with no useful DOM semantics, such as canvas output, editor shims, or mocked non-visual integrations.When repeated content creates ambiguity, narrow to a semantic container, then query within it with within in React Testing Library or locator chaining in Browser Mode.
If an interactive control cannot be found semantically, first check whether the production markup needs a real button, link, label, landmark, or accessible name.
userEvent.setup() instance inside the test. Use fireEvent only when the low-level event itself is the contract..element() only for DOM APIs that locators do not expose.queryBy* for synchronous absence, findBy* for asynchronous appearance, and waitForElementToBeRemoved or waitFor for asynchronous disappearance. In Browser Mode, use expect.element for eventual assertions.Keep the production code that owns or transforms the asserted behavior real. Mock only dependencies outside the target contract, where isolation improves the signal:
Mocks must preserve the public contract needed by the test. Do not mock interactive Dify UI primitives or feature-owned wrappers around them. Keep their semantic roles, state attributes, portals, focus behavior, and render(props, state) contract real; mock only service or external-data boundaries needed to reach the scenario.
web/__mocks__/ only when multiple suites genuinely share it.findBy*, and waitFor.findBy* for an element that appears asynchronously and waitFor for an eventually true external assertion.web/vitest.setup.ts already runs Testing Library cleanup and resets Zustand stores after each test.vi.clearAllMocks() in beforeEach when a suite relies on mock call history. Do not use afterEach to prepare the next test.web/ use two explicit projects in web/vite.config.ts. Supported commands and CI select one project explicitly: unit runs in happy-dom and loads web/vitest.setup.ts, while browser runs matching app/**/*.browser.spec.{ts,tsx} files in Playwright Chromium and loads web/vitest.browser.setup.ts. Bare vp test runs both registered projects.web/.vitest-browser/. CI uploads that directory only when failure artifacts exist; Browser Mode does not own coverage or report merging.__tests__/ directory. Existing colocated utility and hook specs may follow their owning module's convention. Cross-feature integration specs belong in web/__tests__/.react-i18next mock is loaded globally. Use createReactI18nextMock from web/test/i18n-mock only when a test needs custom translations.nuqs behavior, use the helpers in web/test/nuqs-testing.tsx and assert URL updates. Mock nuqs only when URL synchronization is explicitly outside the test contract.When working across several files, order the work by dependency and verify each coherent slice before continuing. Do not create one test file per source file by default.
Run from web/:
# happy-dom; omit the path to run the full unit project
vp test run --project unit path/to/spec-or-directory
# Browser Mode; omit the path to run the full browser project
vp test run --project browser path/to/spec.browser.spec.tsx
# Watch mode; select browser instead for Browser Mode
vp test watch --project unit path/to/spec
# Diagnostic coverage report for the unit project; not an acceptance target
vp test run --project unit --coverage path/to/spec-or-directory
Always pass --project unit or --project browser. Bare vp test runs both registered projects and is not the standard Web test command.
happy-dom, and worth the additional runtime?