[lexical-extension][lexical-react][lexical-playground] Chore: Add Vitest browser-mode tests via the Playwright runner#8614
Merged
Conversation
Adds a Vitest "browser" project that runs in a real browser through the
Playwright provider, so unit tests that lean heavily on jsdom mocks (real
layout, Selection/Range APIs, contentEditable) can be ported to a more
realistic environment.
- Upgrade vitest 4.1.5 -> 4.1.8 and add @vitest/browser-playwright and
playwright so the whole vitest stack stays on one consistent version.
- New "browser" project in vitest.config.mts: Playwright provider, headless,
collects packages/**/__tests__/browser/**/*.test.{ts,tsx}. The browser set
is driven by the comma-separated VITEST_BROWSER env var (default chromium),
with react()/optimizeDeps tuned so React renders share one React instance.
- New root scripts: test-browser and test-browser-watch.
- CI: default run is linux + chromium (call-browser-tests.yml wired into
call-core-tests.yml); the extended suite fans out across
ubuntu/macos/windows x chromium/firefox/webkit.
- Example tests use the extension APIs (buildEditorFromExtensions and
LexicalExtensionComposer) rather than createEditor.
- Ignore vitest browser artifact dirs (__screenshots__, .vitest-attachments)
and document the workflow in AGENTS.md.
https://claude.ai/code/session_01C9r2wFA2WU5gsjFHRDmeCz
Keeps the vite side of the test/build stack on the latest releases now that
vitest and the playwright runner are current.
The newer vite type definitions overflow TypeScript's structural comparison
("Excessive stack depth") on the playground's defineConfig call, so annotate
the config function's return as UserConfig and type the plugins array as
PluginOption[]. This is type-only; the resolved config is unchanged.
https://claude.ai/code/session_01C9r2wFA2WU5gsjFHRDmeCz
Replace the os x browser cartesian product with an explicit include list so the matrix only contains combinations we actually run: - Drop ubuntu + chromium; it is already covered by the default browser job in call-core-tests.yml. - List WebKit only on macOS instead of generating webkit + linux/windows jobs that were always skipped. With the policy now encoded in the matrix, the webkit-on-macOS guard in call-browser-tests.yml is redundant, so remove it. https://claude.ai/code/session_01C9r2wFA2WU5gsjFHRDmeCz
Drop the cleanups array / afterEach teardown in favor of the same `using` Disposable pattern the unit tests use: - buildEditorFromExtensions returns a Disposable editor, so each test does `using editor = setUpEditor()` and reads the contentEditable back via editor.getRootElement() rather than returning it from the helper. document .body is reset between tests, so the root element needs no explicit removal, and everything stays synchronous (no editor.update/dispatchCommand). - The React composer test mounts via a small `using view = renderReact(...)` Disposable helper that unmounts on scope exit instead of in an afterEach. https://claude.ai/code/session_01C9r2wFA2WU5gsjFHRDmeCz
The "Excessive stack depth" overflow on the newer vite types comes from the single array-valued plugins entry (react() returns Plugin[]). Binding it to a PluginOption[] via a checked annotation — `const reactPlugins: PluginOption[] = react()` — and spreading it lets the plugins array infer as PluginOption[] without an `as` cast on the array (every other entry is a Plugin, which is a PluginOption). This replaces the broad `as PluginOption[]` assertion with a type-checked widening scoped to the one entry that needs it. https://claude.ai/code/session_01C9r2wFA2WU5gsjFHRDmeCz
`using`/`Disposable` (Explicit Resource Management) isn't supported in WebKit/Safari yet — the syntax throws a SyntaxError there, and Vite serves source untransformed in dev/serve mode, so the browser suite couldn't run on webkit. Rather than maintain a syntax-lowering plugin plus a Symbol.dispose polyfill just for the tests, clean up with onTestFinished(() => editor.dispose()) — editor.dispose() is a plain method on the buildEditorFromExtensions result and works in every engine. Also document in AGENTS.md that `using`/`Disposable` must not be relied on in browser tests or any browser-facing code (it stays fine in jsdom/Node unit tests). https://claude.ai/code/session_01C9r2wFA2WU5gsjFHRDmeCz
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
|
Review from Navi (on behalf of @potatowagon) This is a well-structured infrastructure PR that adds Vitest browser-mode testing via the Playwright runner. Here's what I verified: What this PR does:
What I checked:
Verdict: Safe to approve. Clean infrastructure work from a maintainer with comprehensive CI coverage already green. No behavioral changes to existing code. — via Navi on behalf of @potatowagon |
potatowagon
approved these changes
Jun 3, 2026
mayrang
added a commit
to mayrang/lexical
that referenced
this pull request
Jun 21, 2026
…anup convention
Convert 10 inline `if (!SUPPORTS_COMPOSED_RANGES) { return }` guards to
test.skipIf so unsupported browsers show "skipped" instead of false "passed"
and avoid unnecessary setUpShadowEditor setup/teardown.
Switch useDynamicPositioning regression test from manual removeChild cleanup
to onTestFinished, matching the pattern used in ShadowRootSelection tests
and LexicalExtensionComposer (PR facebook#8614).
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Our unit tests run under jsdom and lean heavily on hand-written polyfills in
vitest.setup.mts(PointerEvent, DataTransfer, ClipboardEvent, execCommand,Range.getBoundingClientRect,contentEditable/Selection behavior, …) to stand in for functionality jsdom doesn't implement. That mocking is fragile and can drift from real browser behavior.This PR adds a Vitest browser project that runs in a real browser through the Playwright runner, so tests that depend on a real layout/selection engine can be ported out of jsdom. Existing unit tests are untouched.
What's included:
Vitest browser project (
vitest.config.mts): Playwright provider, headless,collecting
packages/**/__tests__/browser/**/*.test.{ts,tsx}. The browser set isdriven by the comma-separated
VITEST_BROWSERenv var (defaultchromium), andReact is deduped via
optimizeDepsso browser tests share a single React instancewith the source-aliased
@lexical/react.Scripts:
pnpm run test-browserandpnpm run test-browser-watch.CI:
call-core-tests.ymlvia anew reusable
call-browser-tests.yml.tests-extended.yml): a fully-specifiedincludematrix — ubuntu+firefox,macOS+{chromium,firefox,webkit}, windows+{chromium,firefox}. ubuntu+chromium is
already the default job and WebKit only runs on macOS, so no combination is
generated just to be skipped.
Example tests using the extension APIs (not
createEditor):lexical-extension/…/BuildEditorFromExtensions.test.ts— builds an editor whosecontentEditable is created in the extension's
afterRegistrationphase, andasserts real
Range.getBoundingClientRectand the Selection API.lexical-react/…/LexicalExtensionComposer.test.tsx— renders a realcontentEditableand checks real layout.Both clean up with
onTestFinished(() => editor.dispose())rather thanusing:Explicit Resource Management (
using/Symbol.dispose/Disposable) isn'tsupported in WebKit/Safari yet, so the syntax can't be relied on in browser tests
(it stays fine in jsdom/Node unit tests). This is documented in
AGENTS.md.Dependency bumps:
vitest4.1.5 → 4.1.8 (+@vitest/browser-playwright,playwright);vite8.0.10 → 8.0.16;@vitejs/plugin-react6.0.1 → 6.0.2.Playground vite config: the newer vite types overflow TypeScript's recursive
PluginOptioncomparison ("Excessive stack depth"). Binding the one array-valuedplugin entry (
react()) toPluginOption[]via a checked annotation lets theplugins array infer as
PluginOption[]without anascast.Ignore browser-mode artifact dirs (
__screenshots__,.vitest-attachments) anddocument the workflow in
AGENTS.md.Test plan
Before
Behavior that needs a real layout/selection engine (
Range.getBoundingClientRect, theSelection API,
contentEditable) could only be tested under jsdom by stubbing it invitest.setup.mts; there was no browser-mode harness.After
pnpm run test-browserruns the new suite in a real browser (default chromium), andpasses on Chromium, Firefox, and WebKit (
VITEST_BROWSER=webkit):pnpm run tsc,eslint, andprettierall pass.