Skip to content

Migrate the whole test suite to Vitest browser mode and delete Cypress tests - #2663

Open
matyasf wants to merge 8 commits into
masterfrom
vitest_browser_convert
Open

Migrate the whole test suite to Vitest browser mode and delete Cypress tests#2663
matyasf wants to merge 8 commits into
masterfrom
vitest_browser_convert

Conversation

@matyasf

@matyasf matyasf commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Convert Jest unit tests and Cypress tests to Vitest browser tests because:

  • Much faster now: ~15 secs in total vs ~22 secs + 5 mins.
  • Everything tests in a real browser.
  • Single test suite for everything

Changes in detail:

  • All 181 test files are converted off React Testing Library onto the vitest-browser API — This was a mechanical codemod pass with no change of test intent.
  • The 56 Cypress specs move into their matching __tests__ files, Truncate.cy.tsx and i18n.cy.tsx land in new files (truncate.test.tsx, getTextDirection.test.tsx) since they had no unit test file to merge into. The whole Cypress component-testing setup and its docs are then deleted.
  • The cases that needed real thought are in a separate commit (eb53eb3)
  • Performance: ui-icons and ui-themes resolve to their built es/ output, which roughly halves suite runtime (with a catch, see below)
  • DateInput (the largest Cypress spec) is split across two files plus its optimized a bit for speed.
  • vitest.setup.ts and its jsdom shims are removed as unnecessary.
  • One product fix: View.omitViewProps logged bad error messages

Note: after editing the source of ui-icons or ui-themes you now have to build that package before browser tests see the change.

For reviewers

  • Run the full suite locally (pnpm run test:vitest) and confirm it's green and meaningfully faster than on master
  • Verify the CI config: one Vitest job instead of two, and confirm nothing that used to be covered by the Cypress job is now unrun.
  • Spot-check the "needed real thought" commit (eb53eb3)
  • Sanity-check the ported Cypress specs against the deleted cypress/component/ files
  • Test the instui-prebundle-stamp invalidation by hand: edit ui-themes source, rebuild it, and confirm the tests pick up the change; check the other changes in vitest.config.mts
  • Check the DateInput split.

@matyasf matyasf self-assigned this Jul 28, 2026
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://instructure.design/pr-preview/pr-2663/

Built to branch gh-pages at 2026-08-05 13:03 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

github-actions Bot pushed a commit that referenced this pull request Jul 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Visual regression report

⚠️ Changes detected.

Status Count
Unchanged 0
Changed 1
New 96
Removed 32

📊 View full report

Diff images (33)

alert.png — baseline no longer produced

avatar.png — baseline no longer produced

badge.png — baseline no longer produced

billboard.png — baseline no longer produced

breadcrumb.png — baseline no longer produced

button-and-derivatives.png — baseline no longer produced

byline.png — baseline no longer produced

calendar.png — baseline no longer produced

checkbox.png — baseline no longer produced

checkboxgroup.png — baseline no longer produced

colorpicker.png — baseline no longer produced

contextview.png — baseline no longer produced

custom-and-lucide-icons.png — baseline no longer produced

dateinput-dateinput2.png — baseline no longer produced

datetimeinput.png — baseline no longer produced

diff-demo.png — 6324 pixels differ

drilldown.png — baseline no longer produced

filedrop.png — baseline no longer produced

form-errors.png — baseline no longer produced

heading.png — baseline no longer produced

img.png — baseline no longer produced

link.png — baseline no longer produced

menu.png — baseline no longer produced

metric-pill-tag-timeselect-text.png — baseline no longer produced

options.png — baseline no longer produced

pagination.png — baseline no longer produced

progressbar.png — baseline no longer produced

select-simpleselect.png — baseline no longer produced

table.png — baseline no longer produced

tabs.png — baseline no longer produced

tooltip.png — baseline no longer produced

treebrowser.png — baseline no longer produced

view.png — baseline no longer produced

Baselines come from the visual-baselines branch. They refresh on every merge to master.

github-actions Bot pushed a commit that referenced this pull request Jul 29, 2026
github-actions Bot pushed a commit that referenced this pull request Jul 29, 2026
Comment thread tsconfig.json
@@ -1,32 +1,8 @@
{
"compilerOptions": {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

turns out that tsconfig.json can be very similar to tsconfig.build.json, this makes the tests not emit TS errors (because the old one was using an old value for moduleResolution)

@matyasf
matyasf force-pushed the vitest_browser_convert branch 3 times, most recently from 0a3608d to 9803b43 Compare August 4, 2026 19:42
github-actions Bot pushed a commit that referenced this pull request Aug 4, 2026
The suite was split across two Vitest projects: a `web` project running most
tests in jsdom, and a `browser` project running a handful of packages in a real
Chromium via Playwright. jsdom's approximations meant tests could pass while the
component was broken in a browser (no layout, no real focus, no computed
styles), so the `browser` project now owns every `packages/**/__tests__` file
and the jsdom project is gone.

- collapse the `web` and `browser` projects into one `browser` project and drop
  the per-package include/exclude lists that kept them from double-running
- delete `vitest.setup.ts` — its jsdom shims (the `ResizeObserver` mock and the
  hardcoded `16px` root font size) are unnecessary in a real browser
- `vitest.setup.browser.ts` restores mocks after each test; jsdom's setup did
  this and browser tests need it too, otherwise a `console.warn` spy from one
  test observes the next one's output
- stop excluding `src/**/__tests__/**` from the packages' `tsconfig.build.json`
  and have the root `tsconfig.json` extend `tsconfig.build.json`, so test files
  are type-checked with the same options as the source
- collapse the two Vitest CI jobs into one

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@matyasf
matyasf force-pushed the vitest_browser_convert branch from 9803b43 to 626e4c9 Compare August 4, 2026 20:48
github-actions Bot pushed a commit that referenced this pull request Aug 4, 2026
Comment thread vitest.config.mts
// built `es/` output.
// This trades DX for speed — after editing the *source* of one
// of these, run that package's `build` before browser tests will see the change.
const PREBUNDLED_PACKAGES = ['@instructure/ui-icons', '@instructure/ui-themes']

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am unsure, that this is a good change. The tests run in:

  • ~45 secs is nothing is pre-bundled
  • ~25 secs if only ui-icons is pre-bundled
  • ~15 secs if ui-icons and ui-themes are pre-bundled
  • (~11 secs if everything is pre-bundled, but in this case one needs to rebuild the component(s) to see changes)

@matyasf
matyasf force-pushed the vitest_browser_convert branch 2 times, most recently from 46f0b5c to 656ec13 Compare August 5, 2026 09:55
github-actions Bot pushed a commit that referenced this pull request Aug 5, 2026
@matyasf
matyasf force-pushed the vitest_browser_convert branch from 656ec13 to efed3a7 Compare August 5, 2026 10:06
github-actions Bot pushed a commit that referenced this pull request Aug 5, 2026
@matyasf
matyasf force-pushed the vitest_browser_convert branch from efed3a7 to f1201fd Compare August 5, 2026 10:17
github-actions Bot pushed a commit that referenced this pull request Aug 5, 2026
@matyasf
matyasf force-pushed the vitest_browser_convert branch from f1201fd to d5ef351 Compare August 5, 2026 10:27
github-actions Bot pushed a commit that referenced this pull request Aug 5, 2026
@matyasf
matyasf force-pushed the vitest_browser_convert branch 2 times, most recently from 0c628d9 to 1279d8d Compare August 5, 2026 10:44
github-actions Bot pushed a commit that referenced this pull request Aug 5, 2026
github-actions Bot pushed a commit that referenced this pull request Aug 5, 2026
matyasf and others added 2 commits August 5, 2026 14:06
Mechanical, codemod-driven conversion of all 181 test files off React Testing
Library and onto the vitest-browser API. No test's intent changes here; the
follow-up commits deal with the cases that need real thought.

The substitutions:

- `render` now comes from `vitest-browser-react` and returns a promise, so every
  call becomes `await render(...)`.
- `screen.getBy*` -> `page.getBy*(...).element()`, `getAllBy*` -> `.elements()`,
  `queryBy*` -> `.query()`; vitest-browser locators are lazy, so the `.element()`
  call is what actually resolves the node.
- `userEvent` now comes from `vitest/browser`
- `waitFor` -> `vi.waitFor`
- `fireEvent` now comes from `@testing-library/dom` directly
- `import '@testing-library/jest-dom'` and
`import { describe, it, expect, ... } from 'vitest'` becomes an explicit

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A real browser does layout, real hit-testing, real focus and real event ordering,
and a fair number of tests were relying on jsdom not doing any of that.
The fixes:

- `userEvent.click(..., { force: true })` in ~36 places. Our inputs are visually
  hidden and covered by a styled facade, so a real click lands on the facade and
  Playwright refuses the click as intercepted.
- swap the remaining `fireEvent.click` for `userEvent.click` where the test is
  about a user clicking. `fireEvent` dispatches one synthetic event; only a real
  click reproduces the browser's own default handling.
- fake timers replaced by `vi.waitFor` and `expect.element(...)`. jsdom needed
  `act(() => vi.runAllTimers())` to flush transitions synchronously; in a browser
  the transitions are real, so the tests wait for the end state instead.
- a couple of keyboard assertions were simply wrong. A real checkbox toggles on
  Space, not Enter — jsdom fired the `change` handler for either.
- Tray/Popover/Dialog tests now `cleanup()` and let the teardown drain in
  `afterEach`, because `FocusRegion` unregisters its document listeners
  asynchronously and a leaked `keydown` listener from one test was swallowing the
  Tab presses of the next test.
- helpers that wrap `render` are awaited

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
matyasf and others added 2 commits August 5, 2026 14:11
The 56 tests under `cypress/component/` move into the matching `__tests__`
files, mostly as a nested `describe('Component tests', ...)`.
The translation of Cypress idioms:

- `cy.mount` -> `await render`
- `cy.get(...).should(...)` -> `page.getBy*` / `expect(...)` inside `vi.waitFor`,
  since Cypress retried assertions implicitly and Vitest does not
- `cy.realClick` / `cy.realHover` (cypress-real-events) -> `userEvent` from
  `vitest/browser`
- `cy.tick` -> `vi.advanceTimersByTime`

Two specs land in new files: `Truncate.cy.tsx` becomes `truncate.test.tsx` and
`i18n.cy.tsx` becomes `getTextDirection.test.tsx`, because neither had a unit
test file to merge into.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every spec is now a vitest-browser test co-located with its component, so the
whole Cypress component-testing setup is deleted.

The testing docs are updated to match.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@matyasf
matyasf force-pushed the vitest_browser_convert branch from 1279d8d to 0843238 Compare August 5, 2026 12:12
@matyasf matyasf changed the title [WIP] convert all test to vitest-browser Migrate the whole test suite to Vitest browser mode and delete Cypress tests Aug 5, 2026
github-actions Bot pushed a commit that referenced this pull request Aug 5, 2026
matyasf and others added 3 commits August 5, 2026 14:57
`DateInput.cy.tsx` was the largest Cypress spec. Merging all of
it into `DateInput.test.tsx` would have made a 1700+ lines file, so it was
split into 2 files.

Splitting means the shared plumbing needs a home, so `dateInputTestHelpers.ts`
holds what both files use. Two other changes for performance were made:

- `clickElement` dispatches mousedown/mouseup/click via `fireEvent` instead of
  `userEvent.click`, which is much faster (around 10x).
- `waitFor2ms` wraps `vi.waitFor` with `{ interval: 2 }`. The default 50ms poll
  interval is too much when the thing being waited for already happened on the
  same tick, which is the case for every one of these DOM assertions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Now ui-icons and ui-themes are resolved to their built `es/` output. This
roughly halves the test suite's runtime.

The cost is a DX trade-off: after editing the *source* of either package you have
to build it before browser tests see the change.

Vite does not notice any changes in the `es/` output, so a rebuild would not
triggeer a rerun on the tests. To fix this, the `instui-prebundle-stamp`
plugin is added to caclulate packages' modified times into that cache key,
so a rebuild changes the plugin name and invalidates the bundle.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`View.omitViewProps` built its dev warning from `Component.name`, but the
components that call it are wrapped by `@withStyle`, so `name` is empty,
so the warnings read `[undefined] prop 'x' is not allowed.`

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@matyasf
matyasf force-pushed the vitest_browser_convert branch from 0843238 to 89d8460 Compare August 5, 2026 12:58
Comment on lines +102 to +106
const name = Component.displayName
? Component.displayName
: Component.name
Object.keys(pickProps(props, propsToOmit)).forEach((prop) => {
error(false, `[${Component.name}] prop '${prop}' is not allowed.`)
error(false, `[${name}] prop '${prop}' is not allowed.`)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (and the same change for v1) is the only code change

github-actions Bot pushed a commit that referenced this pull request Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant