cargo nextest run --workspace # everything, as CI doescargo nextest run -p ide-text # one cratecargo nextest run -p ide-host --test vcs_flow # one integration test file
Unit tests sit next to the code (#[cfg(test)] modules or a tests.rs); integration tests in crates/<crate>/tests/. ide-host has the most: opening projects, trust, VCS flows, run configurations, plugins, the remote protocol.
Tests that need real external tools (language servers, Docker, build tools) are #[ignore]d with a reason and run explicitly with -- --ignored.
Use NullSink when a crate wants an EventSink and the test doesn’t care about events.
CI runs the suite on macOS, Ubuntu 22.04 and Windows with --no-fail-fast. Test fixtures write LF files, so CI sets git config --global core.autocrlf false; do the same on Windows if fixture comparisons fail.
The CI test run also regenerates src/bindings.ts (the export_bindings test) and fails if it differs from the committed file.
pnpm test runs vitest run over src/**/*.test.{ts,tsx}, perf/**/*.test.ts and scripts/**/*.test.ts in jsdom. The full suite takes several minutes, so while iterating run a subset:
pnpm vitest run src/platform/keymap.test.tspnpm vitest run src/features/searchpnpm test:watch # watch mode
pnpm test -- <path> ignores the path and runs the whole suite. Use pnpm vitest run <path>.
What to know when writing them:
Tests live next to the module (foo.test.ts) or in a feature’s __tests__/ folder.
src/test/setup.ts makes jsdom look like macOS (so ⌘ keymaps apply; opt into Windows explicitly) and gives elements a size so virtualized lists render rows.
Outside Tauri, isTauri() is false and features use their in-memory backends. To simulate a backend event, use emitLocalIdeEvent from @/platform/ipc.
Some tests are repository checks rather than unit tests: the text catalogs (src/platform/locales/*.test.ts), WebKit compatibility (src/platform/webkitCompatibility.test.ts), the theme token reference (scripts/theme/theme.test.ts). They run in pnpm test too.
pnpm e2e runs Playwright against the UI in plain-browser mode: Vite serves the frontend (port 1450, or OPENIDEA_E2E_PORT), the shell injects a sample project, and features fall back to in-memory backends — no Tauri, no Rust build, no network.
pnpm exec playwright install chromium # oncepnpm e2e # all specspnpm e2e e2e/editor.spec.ts # one filepnpm e2e -g "opens a file" # by test name
Specs are e2e/*.spec.ts. Import test and expect from e2e/fixtures.ts; its workbench fixture opens the sample project (acme-api) and helpers like openFromTree and doubleShift cover common steps.
Specs use the macOS keymap (Meta+…) on every OS; the fixture forces it.
To exercise code paths that need a desktop backend, stubDesktop(page) switches the page to desktop mode over a stub: you register handlers per command name (window.__ipc.handlers.lsp_goto = …), push values into a Channel with send, and emit IdeEvents with emit. Unhandled commands fail as unavailable. ipcCalls(page, cmd) returns the calls made.
Standalone pages for focused component tests are e2e/*.html + e2e/*.fixture.ts.
In CI, E2E runs in the mcr.microsoft.com/playwright container matching the @playwright/test version, with one retry; on failure the HTML report and traces (trace: "retain-on-failure") are uploaded as an artifact.
Unit and E2E tests don’t run the Rust backend inside the webview. For UI or runtime behaviour, run pnpm tauri dev and check the change in the app. Debug builds include the Tauri MCP bridge, so the check can be scripted; find the port with pnpm dev:status (see Development setup). The repository’s agent skill .agents/skills/verify describes driving the app that way and capturing screenshots.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"OpenIDEA","description":"The IDE you know, rebuilt in Rust.","colors":{"primary":"#3871E1","light":"#8FB3FF","dark":"#3871E1"},"logo":{"light":"https://mintcdn.com/openidea/wPxqSmDfBpsYEW4W/logo/light.svg?fit=max\u0026auto=format\u0026n=wPxqSmDfBpsYEW4W\u0026q=85\u0026s=e8b1130beea815a3ea47ad655940657c","dark":"https://mintcdn.com/openidea/wPxqSmDfBpsYEW4W/logo/dark.svg?fit=max\u0026auto=format\u0026n=wPxqSmDfBpsYEW4W\u0026q=85\u0026s=9caa5d6893fe2558669c2070046abee6"},"favicon":"https://mintcdn.com/openidea/wPxqSmDfBpsYEW4W/favicon.svg?fit=max\u0026auto=format\u0026n=wPxqSmDfBpsYEW4W\u0026q=85\u0026s=8805ec0e4503dd288547d13eeb1e59f1","appearance":{"default":"dark"},"navbar":{"links":[{"href":"https://github.com/l0ng-ai/openidea","label":"GitHub"}],"primary":{"type":"button","label":"Download","href":"https://github.com/l0ng-ai/openidea/releases/latest"}},"footer":{"socials":{"github":"https://github.com/l0ng-ai/openidea","discord":"https://discord.gg/2Zy4yn7gP"}},"styling":{"latex":false},"navigation":{"languages":[{"language":"en","default":true,"href":"en"},{"language":"zh","href":"zh"}]}},"deployment":{"deploymentMetadataValue":{"subdomain":"openidea","hasAudioNarration":false,"actualSubdomain":"openidea","customDomains":["openidea.sh"],"gitSource":{"type":"github","owner":"l0ng-ai","repo":"openidea","deployBranch":"main","contentDirectory":"docs","isPrivate":false,"mountPath":"","label":"","_id":"6af31815b16397a01be8b027"},"entitlements":{},"buildId":"6af31db08e323f6b7c3b3fed","clientVersion":"0.0.4094","searchFilterCounts":[{"language":"en","tag":"Guide","count":41},{"language":"zh","tag":"指南","count":41},{"language":"en","tag":"Plugins","count":25},{"language":"zh","tag":"插件","count":25},{"language":"en","tag":"Languages","count":24},{"language":"en","tag":"Contribute","count":17},{"language":"zh","tag":"参与开发","count":17},{"language":"zh","tag":"语言","count":14},{"language":"en","tag":"Get started","count":11},{"language":"en","tag":"Extension points","count":11},{"language":"zh","tag":"开始","count":11},{"language":"zh","tag":"扩展点","count":11},{"language":"zh","tag":"主要语言","count":10},{"language":"en","tag":"Architecture","count":8},{"language":"en","tag":"Tools","count":8},{"language":"en","tag":"Editing","count":8},{"language":"zh","tag":"架构","count":8},{"language":"zh","tag":"工具","count":8},{"language":"zh","tag":"编辑","count":8},{"language":"en","tag":"Run and debug","count":6}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-rbac-roles","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"rbac-expansion","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-metadata","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-serve","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"live-agents","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[],"page":{"pagePath":"en/contribute/testing/unit-e2e","slug":"/en/contribute/testing/unit-e2e","pageMetadata":{"title":"Unit and E2E tests","description":"Running and writing Rust tests with cargo nextest, frontend tests with Vitest, and UI flows with Playwright.","href":"/en/contribute/testing/unit-e2e"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"Running and writing Rust tests with cargo nextest, frontend tests with Vitest, and UI flows with Playwright.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,li:`li`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,ul:`ul`,..._provideComponents(),...props.components},{CodeBlock,Heading,Table,Warning}=_components;if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Table)_missingMdxReference(`Table`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Layer`}),_jsx(_components.th,{children:`Runner`}),_jsx(_components.th,{children:`Command`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Rust crates`}),_jsxs(_components.td,{children:[`cargo-nextest (or `,_jsx(_components.code,{children:`cargo test`}),`)`]}),_jsx(_components.td,{children:_jsx(_components.code,{children:`cargo nextest run --workspace`})})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Frontend modules and components`}),_jsx(_components.td,{children:`Vitest + jsdom + Testing Library`}),_jsxs(_components.td,{children:[_jsx(_components.code,{children:`pnpm test`}),`, or `,_jsx(_components.code,{children:`pnpm vitest run \u003cpath>`})]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`UI flows`}),_jsx(_components.td,{children:`Playwright (Chromium)`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`pnpm e2e`})})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Real language servers, toolchains, debuggers`}),_jsxs(_components.td,{children:[`ignored Rust tests and `,_jsx(_components.code,{children:`pnpm smoke`})]}),_jsxs(_components.td,{children:[`see `,_jsx(_components.a,{href:`/en/contribute/testing/smoke`,children:`Smoke tests`})]})]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`rust`,children:`Rust`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`3`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`3`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`cargo`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` nextest`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` run`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --workspace`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # everything, as CI does`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`cargo`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` nextest`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` run`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -p`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` ide-text`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # one crate`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`cargo`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` nextest`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` run`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -p`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` ide-host`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --test`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` vcs_flow`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # one integration test file`})]}),`\n`]})})}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[`Unit tests sit next to the code (`,_jsx(_components.code,{children:`#[cfg(test)]`}),` modules or a `,_jsx(_components.code,{children:`tests.rs`}),`); integration tests in `,_jsx(_components.code,{children:`crates/\u003ccrate>/tests/`}),`. `,_jsx(_components.code,{children:`ide-host`}),` has the most: opening projects, trust, VCS flows, run configurations, plugins, the remote protocol.`]}),`\n`,_jsxs(_components.li,{children:[`Tests that need real external tools (language servers, Docker, build tools) are `,_jsx(_components.code,{children:`#[ignore]`}),`d with a reason and run explicitly with `,_jsx(_components.code,{children:`-- --ignored`}),`.`]}),`\n`,_jsxs(_components.li,{children:[`Use `,_jsx(_components.code,{children:`NullSink`}),` when a crate wants an `,_jsx(_components.code,{children:`EventSink`}),` and the test doesn’t care about events.`]}),`\n`,_jsxs(_components.li,{children:[`CI runs the suite on macOS, Ubuntu 22.04 and Windows with `,_jsx(_components.code,{children:`--no-fail-fast`}),`. Test fixtures write LF files, so CI sets `,_jsx(_components.code,{children:`git config --global core.autocrlf false`}),`; do the same on Windows if fixture comparisons fail.`]}),`\n`,_jsxs(_components.li,{children:[`The CI test run also regenerates `,_jsx(_components.code,{children:`src/bindings.ts`}),` (the `,_jsx(_components.code,{children:`export_bindings`}),` test) and fails if it differs from the committed file.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`Plugins have their own workspaces: see `,_jsx(_components.a,{href:`/en/contribute/architecture/plugin-host#testing-the-host`,children:`Plugin host › Testing the host`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`frontend-unit-tests`,children:`Frontend unit tests`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`pnpm test`}),` runs `,_jsx(_components.code,{children:`vitest run`}),` over `,_jsx(_components.code,{children:`src/**/*.test.{ts,tsx}`}),`, `,_jsx(_components.code,{children:`perf/**/*.test.ts`}),` and `,_jsx(_components.code,{children:`scripts/**/*.test.ts`}),` in jsdom. The full suite takes several minutes, so while iterating run a subset:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`3`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`3`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`pnpm`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` vitest`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` run`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` src/platform/keymap.test.ts`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`pnpm`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` vitest`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` run`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` src/features/search`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`pnpm`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` test:watch`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # watch mode`})]}),`\n`]})})}),`\n`,_jsx(Warning,{children:_jsxs(_components.p,{children:[_jsx(_components.code,{children:`pnpm test -- \u003cpath>`}),` ignores the path and runs the whole suite. Use `,_jsx(_components.code,{children:`pnpm vitest run \u003cpath>`}),`.`]})}),`\n`,_jsx(_components.p,{children:`What to know when writing them:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[`Tests live next to the module (`,_jsx(_components.code,{children:`foo.test.ts`}),`) or in a feature’s `,_jsx(_components.code,{children:`__tests__/`}),` folder.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`src/test/setup.ts`}),` makes jsdom look like macOS (so ⌘ keymaps apply; opt into Windows explicitly) and gives elements a size so virtualized lists render rows.`]}),`\n`,_jsxs(_components.li,{children:[`Outside Tauri, `,_jsx(_components.code,{children:`isTauri()`}),` is false and features use their in-memory backends. To simulate a backend event, use `,_jsx(_components.code,{children:`emitLocalIdeEvent`}),` from `,_jsx(_components.code,{children:`@/platform/ipc`}),`.`]}),`\n`,_jsxs(_components.li,{children:[`Some tests are repository checks rather than unit tests: the text catalogs (`,_jsx(_components.code,{children:`src/platform/locales/*.test.ts`}),`), WebKit compatibility (`,_jsx(_components.code,{children:`src/platform/webkitCompatibility.test.ts`}),`), the theme token reference (`,_jsx(_components.code,{children:`scripts/theme/theme.test.ts`}),`). They run in `,_jsx(_components.code,{children:`pnpm test`}),` too.`]}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`end-to-end-tests`,children:`End-to-end tests`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`pnpm e2e`}),` runs Playwright against the UI in `,_jsx(_components.strong,{children:`plain-browser mode`}),`: Vite serves the frontend (port 1450, or `,_jsx(_components.code,{children:`OPENIDEA_E2E_PORT`}),`), the shell injects a sample project, and features fall back to in-memory backends — no Tauri, no Rust build, no network.`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`4`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`4`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`pnpm`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` exec`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` playwright`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` install`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` chromium`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # once`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`pnpm`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` e2e`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # all specs`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`pnpm`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` e2e`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` e2e/editor.spec.ts`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # one file`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`pnpm`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` e2e`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -g`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` \"opens a file\"`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # by test name`})]}),`\n`]})})}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[`Specs are `,_jsx(_components.code,{children:`e2e/*.spec.ts`}),`. Import `,_jsx(_components.code,{children:`test`}),` and `,_jsx(_components.code,{children:`expect`}),` from `,_jsx(_components.code,{children:`e2e/fixtures.ts`}),`; its `,_jsx(_components.code,{children:`workbench`}),` fixture opens the sample project (`,_jsx(_components.code,{children:`acme-api`}),`) and helpers like `,_jsx(_components.code,{children:`openFromTree`}),` and `,_jsx(_components.code,{children:`doubleShift`}),` cover common steps.`]}),`\n`,_jsxs(_components.li,{children:[`Specs use the macOS keymap (`,_jsx(_components.code,{children:`Meta+…`}),`) on every OS; the fixture forces it.`]}),`\n`,_jsxs(_components.li,{children:[`To exercise code paths that need a desktop backend, `,_jsx(_components.code,{children:`stubDesktop(page)`}),` switches the page to desktop mode over a stub: you register handlers per command name (`,_jsx(_components.code,{children:`window.__ipc.handlers.lsp_goto = …`}),`), push values into a `,_jsx(_components.code,{children:`Channel`}),` with `,_jsx(_components.code,{children:`send`}),`, and emit `,_jsx(_components.code,{children:`IdeEvent`}),`s with `,_jsx(_components.code,{children:`emit`}),`. Unhandled commands fail as `,_jsx(_components.code,{children:`unavailable`}),`. `,_jsx(_components.code,{children:`ipcCalls(page, cmd)`}),` returns the calls made.`]}),`\n`,_jsxs(_components.li,{children:[`Standalone pages for focused component tests are `,_jsx(_components.code,{children:`e2e/*.html`}),` + `,_jsx(_components.code,{children:`e2e/*.fixture.ts`}),`.`]}),`\n`,_jsxs(_components.li,{children:[`In CI, E2E runs in the `,_jsx(_components.code,{children:`mcr.microsoft.com/playwright`}),` container matching the `,_jsx(_components.code,{children:`@playwright/test`}),` version, with one retry; on failure the HTML report and traces (`,_jsx(_components.code,{children:`trace: \"retain-on-failure\"`}),`) are uploaded as an artifact.`]}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`verifying-in-the-real-app`,children:`Verifying in the real app`}),`\n`,_jsxs(_components.p,{children:[`Unit and E2E tests don’t run the Rust backend inside the webview. For UI or runtime behaviour, run `,_jsx(_components.code,{children:`pnpm tauri dev`}),` and check the change in the app. Debug builds include the Tauri MCP bridge, so the check can be scripted; find the port with `,_jsx(_components.code,{children:`pnpm dev:status`}),` (see `,_jsx(_components.a,{href:`/en/contribute/setup#mcp-bridge-and-ports`,children:`Development setup`}),`). The repository’s agent skill `,_jsx(_components.code,{children:`.agents/skills/verify`}),` describes driving the app that way and capturing screenshots.`]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Unit and E2E tests","description":"Running and writing Rust tests with cargo nextest, frontend tests with Vitest, and UI flows with Playwright.","href":"/en/contribute/testing/unit-e2e"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Rust","slug":"rust","depth":2,"children":[]},{"title":"Frontend unit tests","slug":"frontend-unit-tests","depth":2,"children":[]},{"title":"End-to-end tests","slug":"end-to-end-tests","depth":2,"children":[]},{"title":"Verifying in the real app","slug":"verifying-in-the-real-app","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"openidea","siteName":"OpenIDEA","title":"Unit and E2E tests - OpenIDEA","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{"en":{"href":"/en/contribute/testing/unit-e2e","matchLevel":0},"zh":{"href":"/zh/contribute/testing/unit-e2e","matchLevel":1}}},"lastModified":"2026-11-10T13:15:17.706Z","originalFileLocation":"en/contribute/testing/unit-e2e.mdx","locale":"en"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"dark\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"Running and writing Rust tests with cargo nextest, frontend tests with Vitest, and UI flows with Playwright.\">\u003cmeta data-mint-head name=\"application-name\" content=\"OpenIDEA\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/openidea/9AJ8ATO7sH33l3ls/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/openidea/9AJ8ATO7sH33l3ls/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/openidea/9AJ8ATO7sH33l3ls/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/openidea/9AJ8ATO7sH33l3ls/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/openidea/9AJ8ATO7sH33l3ls/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/openidea/9AJ8ATO7sH33l3ls/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/openidea/9AJ8ATO7sH33l3ls/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/openidea/9AJ8ATO7sH33l3ls/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/openidea/9AJ8ATO7sH33l3ls/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Unit and E2E tests - OpenIDEA\">\u003cmeta data-mint-head property=\"og:description\" content=\"Running and writing Rust tests with cargo nextest, frontend tests with Vitest, and UI flows with Playwright.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"OpenIDEA\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://openidea.sh/en/contribute/testing/unit-e2e\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://openidea.mintlify.site/_mintlify/api/og/v2?division=Testing\u0026amp;title=Unit+and+E2E+tests\u0026amp;description=Running+and+writing+Rust+tests+with+cargo+nextest%2C+frontend+tests+with+Vitest%2C+and+UI+flows+with+Playwright.\u0026amp;theme=1e79528d9c677c294901aa3f\u0026amp;signature=94e3fa7578641c0a2d0268b6eaab12b6807bf218e569826c9210600dc93defc2\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Unit and E2E tests - OpenIDEA\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Running and writing Rust tests with cargo nextest, frontend tests with Vitest, and UI flows with Playwright.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://openidea.mintlify.site/_mintlify/api/og/v2?division=Testing\u0026amp;title=Unit+and+E2E+tests\u0026amp;description=Running+and+writing+Rust+tests+with+cargo+nextest%2C+frontend+tests+with+Vitest%2C+and+UI+flows+with+Playwright.\u0026amp;theme=1e79528d9c677c294901aa3f\u0026amp;signature=94e3fa7578641c0a2d0268b6eaab12b6807bf218e569826c9210600dc93defc2\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/openidea/9AJ8ATO7sH33l3ls/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"OpenIDEA\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#3871E1\">\u003clink data-mint-head rel=\"canonical\" href=\"https://openidea.sh/en/contribute/testing/unit-e2e\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/en/contribute/testing/unit-e2e.md\">\u003clink data-mint-head rel=\"alternate\" hreflang=\"en\" href=\"https://openidea.sh/en/contribute/testing/unit-e2e\">\u003clink data-mint-head rel=\"alternate\" hreflang=\"zh\" href=\"https://openidea.sh/zh/contribute/testing/unit-e2e\">\u003clink data-mint-head rel=\"alternate\" hreflang=\"x-default\" href=\"https://openidea.sh/en/contribute/testing/unit-e2e\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://openidea.sh/#organization\",\"name\":\"OpenIDEA\",\"url\":\"https://openidea.sh\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/openidea/wPxqSmDfBpsYEW4W/logo/light.svg?fit=max\u0026auto=format\u0026n=wPxqSmDfBpsYEW4W\u0026q=85\u0026s=e8b1130beea815a3ea47ad655940657c\"}},{\"@type\":\"WebSite\",\"@id\":\"https://openidea.sh#website\",\"name\":\"OpenIDEA\",\"url\":\"https://openidea.sh\",\"publisher\":{\"@id\":\"https://openidea.sh/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://openidea.sh/en/contribute/testing/unit-e2e#webpage\",\"url\":\"https://openidea.sh/en/contribute/testing/unit-e2e\",\"name\":\"Unit and E2E tests\",\"description\":\"Running and writing Rust tests with cargo nextest, frontend tests with Vitest, and UI flows with Playwright.\",\"dateModified\":\"2026-11-10T13:15:17.706Z\",\"isPartOf\":{\"@id\":\"https://openidea.sh#website\"},\"breadcrumb\":{\"@id\":\"https://openidea.sh/en/contribute/testing/unit-e2e#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://openidea.sh/en/contribute/testing/unit-e2e#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"OpenIDEA\",\"item\":\"https://openidea.sh\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Unit and E2E tests\",\"item\":\"https://openidea.sh/en/contribute/testing/unit-e2e\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://openidea.sh/en/contribute/testing/unit-e2e#article\",\"headline\":\"Unit and E2E tests\",\"name\":\"Unit and E2E tests\",\"description\":\"Running and writing Rust tests with cargo nextest, frontend tests with Vitest, and UI flows with Playwright.\",\"url\":\"https://openidea.sh/en/contribute/testing/unit-e2e\",\"mainEntityOfPage\":{\"@id\":\"https://openidea.sh/en/contribute/testing/unit-e2e#webpage\"},\"dateModified\":\"2026-11-10T13:15:17.706Z\",\"publisher\":{\"@id\":\"https://openidea.sh/#organization\"},\"isPartOf\":{\"@id\":\"https://openidea.sh#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"OpenIDEA\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"OpenIDEA","nav":{"global":null,"languages":[{"language":"en","default":true,"tabs":[{"tab":"Guide","pages":[{"title":"Guide","href":"/en"}]},{"tab":"Languages","pages":[{"title":"Languages","href":"/en/languages"}]},{"tab":"Plugins","pages":[{"title":"Plugins","href":"/en/plugins"}]},{"tab":"Contribute","groups":[{"group":"Get started","pages":[{"title":"Development setup","href":"/en/contribute/setup"},{"title":"Contribution workflow","href":"/en/contribute/workflow"},{"title":"Security","href":"/en/contribute/security"}]},{"group":"Architecture","pages":[{"title":"Architecture overview","href":"/en/contribute/architecture/overview"},{"title":"Rust or UI?","href":"/en/contribute/architecture/boundaries"},{"title":"IPC between UI and Rust","href":"/en/contribute/architecture/ipc"},{"title":"Crates","href":"/en/contribute/architecture/crates"},{"title":"Frontend architecture","href":"/en/contribute/architecture/frontend"},{"title":"Code intelligence","href":"/en/contribute/architecture/code-intelligence"},{"title":"Plugin host","href":"/en/contribute/architecture/plugin-host"},{"title":"Remote development","href":"/en/contribute/architecture/remote"}]},{"group":"Guidelines","pages":[{"title":"UI guidelines","href":"/en/contribute/guidelines/ui"},{"title":"Localization","href":"/en/contribute/guidelines/i18n"},{"title":"Code style","description":"Formatting, linting and dead-code checks for Rust and TypeScript, plus the conventions for comments, errors and generated files.","href":"/en/contribute/guidelines/code-style"}]},{"group":"Testing","pages":[{"title":"Unit and E2E tests","href":"/en/contribute/testing/unit-e2e"},{"title":"Smoke tests","description":"Language smoke tests with real language servers, and run and debug smoke tests through a real openidea-server, on the fixtures projects.","href":"/en/contribute/testing/smoke"},{"title":"Performance tests","href":"/en/contribute/testing/perf"}]}]},{"tab":"Changelog","pages":[{"title":"Changelog","href":"/en/changelog"}]}]},{"language":"zh","pages":[{"title":"zh","href":"/zh/contribute/testing/unit-e2e"}]}]},"basePath":"","title":"Unit and E2E tests - OpenIDEA","buildId":"dpl_42LwxxU1kGN114H4XzwGYXa9ZDah","deploymentId":"dpl_42LwxxU1kGN114H4XzwGYXa9ZDah","dynamics":[],"chunks":[]}