Repository: streamlit/streamlit
Pull request #10947: Add vitest workspace configuration
Author: login: lukasmasuch
Labels: name: change:other
description: PR contains other type of change
name: impact:internal
description: PR changes only affect internal code
Created: 2025-03-27T22:36:36Z | Merged: 2025-03-28T16:50:48Z

## Describe your changes

Adds a [vitest workspace configuration](https://vitest.dev/guide/workspace) to combine multiple project configurations within a single Vitest process. One of the main benefits is that we get a single coverage report across all the sub-projects.

---

**Contribution License Agreement**

By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.



Diff:
diff --git a/.github/workflows/js-tests.yml b/.github/workflows/js-tests.yml
index f7d18951a7f..d8b4008a6ea 100644
--- a/.github/workflows/js-tests.yml
+++ b/.github/workflows/js-tests.yml
@@ -72,14 +72,17 @@ jobs:
           fi
       - name: Run frontend tests
         run: make jstestcoverage
-      - name: Upload coverage report
+      - name: Upload coverage json-summary
         uses: actions/upload-artifact@v4
         with:
-          name: coverage_report_vitest
-          path: |
-            frontend/lib/coverage
-            frontend/app/coverage
-          retention-days: 7
+          name: vitest_coverage_json
+          path: frontend/coverage/coverage-summary.json
+      - name: Upload coverage HTML report
+        uses: actions/upload-artifact@v4
+        with:
+          name: vitest_coverage_html
+          path: frontend/coverage
+          retention-days: 30
 
   components-lib-tests:
     runs-on: ubuntu-latest
diff --git a/Makefile b/Makefile
index 1542d9ca0d2..e0d3d7515b3 100644
--- a/Makefile
+++ b/Makefile
@@ -344,12 +344,12 @@ jsformat:
 .PHONY: jstest
 # Run JS unit tests.
 jstest: frontend-dependencies
-	cd frontend; TESTPATH=$(TESTPATH) yarn workspaces foreach --all run test
+	cd frontend; TESTPATH=$(TESTPATH) yarn test
 
 .PHONY: jstestcoverage
 # Run JS unit tests and generate a coverage report.
 jstestcoverage: frontend-dependencies
-	cd frontend; TESTPATH=$(TESTPATH) yarn workspaces foreach --all run test --coverage
+	cd frontend; TESTPATH=$(TESTPATH) yarn testCoverage
 
 .PHONY: update-snapshots
 # Update e2e playwright snapshots based on the latest completed CI run.
diff --git a/frontend/app/.eslintrc.cjs b/frontend/app/.eslintrc.cjs
index c1a09a71da7..ef6ff28337d 100644
--- a/frontend/app/.eslintrc.cjs
+++ b/frontend/app/.eslintrc.cjs
@@ -16,6 +16,8 @@
 
 module.exports = {
   extends: ["../.eslintrc.js"], // Extend from the root configuration
+  // Explicitly override plugins to avoid duplicate plugin loading
+  plugins: [],
   rules: {
     "no-restricted-imports": [
       "error",
diff --git a/frontend/app/package.json b/frontend/app/package.json
index 578b88c0520..05b03928180 100644
--- a/frontend/app/package.json
+++ b/frontend/app/package.json
@@ -74,7 +74,6 @@
     "@typescript-eslint/eslint-plugin": "^7.18.0",
     "@typescript-eslint/parser": "^7.18.0",
     "@vitejs/plugin-react-swc": "^3.6.0",
-    "@vitest/coverage-v8": "^3.0.9",
     "axios-mock-adapter": "^2.1.0",
     "buffer": "^6.0.3",
     "chrome-launcher": "^1.1.2",
diff --git a/frontend/app/vite.config.ts b/frontend/app/vite.config.ts
index ecbccdda78a..71d8772bc12 100644
--- a/frontend/app/vite.config.ts
+++ b/frontend/app/vite.config.ts
@@ -13,13 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 import { defineConfig } from "vite"
 import { version } from "./package.json"
-import react from "@vitejs/plugin-react-swc"
-import viteTsconfigPaths from "vite-tsconfig-paths"
 
+import react from "@vitejs/plugin-react-swc"
 import path from "path"
+import viteTsconfigPaths from "vite-tsconfig-paths"
 
 const BASE = "./"
 const HASH = process.env.OMIT_HASH_FROM_MAIN_FILES ? "" : ".[hash]"
@@ -134,11 +133,6 @@ export default defineConfig({
         },
       },
     },
-    coverage: {
-      reporter: ["text", "json", "html"],
-      include: ["src/**/*"],
-      exclude: [],
-    },
     server: {
       // Want a Non-Dev port for testing
       port: 3001,
diff --git a/frontend/connection/vite.config.ts b/frontend/connection/vite.config.ts
index 2697112b3bd..624306009e3 100644
--- a/frontend/connection/vite.config.ts
+++ b/frontend/connection/vite.config.ts
@@ -63,10 +63,5 @@ export default defineConfig({
         },
       },
     },
-    coverage: {
-      reporter: ["text", "json", "html"],
-      include: ["src/**/*"],
-      exclude: [],
-    },
   },
 })
diff --git a/frontend/lib/vite.config.ts b/frontend/lib/vite.config.ts
index 0c2d5c93682..365a608f1c6 100644
--- a/frontend/lib/vite.config.ts
+++ b/frontend/lib/vite.config.ts
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-import { defineConfig } from "vite"
 import react from "@vitejs/plugin-react-swc"
+import { defineConfig } from "vite"
 import dts from "vite-plugin-dts"
 import viteTsconfigPaths from "vite-tsconfig-paths"
 
@@ -78,10 +78,5 @@ export default defineConfig({
         },
       },
     },
-    coverage: {
-      reporter: ["text", "json", "html"],
-      include: ["src/**/*"],
-      exclude: [],
-    },
   },
 })
diff --git a/frontend/package.json b/frontend/package.json
index 51e1ed06454..12f233b21ce 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -13,7 +13,10 @@
   "scripts": {
     "start": "yarn workspace @streamlit/app start",
     "buildWithProfiler": "yarn workspace @streamlit/app buildWithProfiler",
-    "dev": "concurrently \"yarn workspaces foreach --all --parallel --exclude @streamlit/app --exclude @streamlit/lib run buildWatch\" \"yarn workspace @streamlit/app start\""
+    "dev": "concurrently \"yarn workspaces foreach --all --parallel --exclude @streamlit/app --exclude @streamlit/lib run buildWatch\" \"yarn workspace @streamlit/app start\"",
+    "test": "vitest run",
+    "testWatch": "vitest",
+    "testCoverage": "vitest run --coverage"
   },
   "resolutions": {
     "nwsapi": "2.2.9",
@@ -31,6 +34,8 @@
   ],
   "packageManager": "yarn@4.5.3",
   "devDependencies": {
-    "concurrently": "^9.1.2"
+    "@vitest/coverage-v8": "^3.0.9",
+    "concurrently": "^9.1.2",
+    "vitest": "^3.0.9"
   }
 }
diff --git a/frontend/utils/vite.config.ts b/frontend/utils/vite.config.ts
index e0b1dabf85e..855333b0cbc 100644
--- a/frontend/utils/vite.config.ts
+++ b/frontend/utils/vite.config.ts
@@ -53,10 +53,5 @@ export default defineConfig({
     css: true,
     reporters: ["verbose"],
     setupFiles: ["../vitest.setup.ts"],
-    coverage: {
-      reporter: ["text", "json", "html"],
-      include: ["src/**/*"],
-      exclude: [],
-    },
   },
 })
diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts
new file mode 100644
index 00000000000..54cff1a2ce3
--- /dev/null
+++ b/frontend/vitest.config.ts
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { coverageConfigDefaults, defineConfig } from "vitest/config"
+
+export default defineConfig({
+  test: {
+    // Include all packages that have a vite.config.ts file
+    workspace: ["*/vite.config.ts"],
+
+    // Global coverage configuration
+    coverage: {
+      provider: "v8",
+      reporter: ["text-summary", "json-summary", "html"],
+      include: ["*/src/**/*"],
+      exclude: ["lib/src/vendor/**", ...coverageConfigDefaults.exclude],
+    },
+  },
+})
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index f2944a15339..00d96139b7c 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -3028,7 +3028,6 @@ __metadata:
     "@typescript-eslint/eslint-plugin": "npm:^7.18.0"
     "@typescript-eslint/parser": "npm:^7.18.0"
     "@vitejs/plugin-react-swc": "npm:^3.6.0"
-    "@vitest/coverage-v8": "npm:^3.0.9"
     axios-mock-adapter: "npm:^2.1.0"
     baseui: "npm:12.2.0"
     buffer: "npm:^6.0.3"
@@ -17923,7 +17922,9 @@ __metadata:
   version: 0.0.0-use.local
   resolution: "streamlit@workspace:."
   dependencies:
+    "@vitest/coverage-v8": "npm:^3.0.9"
     concurrently: "npm:^9.1.2"
+    vitest: "npm:^3.0.9"
   languageName: unknown
   linkType: soft
 


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces a new centralized vitest workspace configuration and removes redundant coverage configurations from individual package vite.config.ts files.  
- Adds a global vitest configuration file in the frontend directory.  
- Removes local coverage configurations from the app, lib, connection, and utils vite.config.ts files.

### Reviewed Changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated no comments.

<details>
<summary>Show a summary per file</summary>

| File                          | Description                                                |
| ----------------------------- | ---------------------------------------------------------- |
| frontend/vitest.config.ts     | Adds a new global configuration with workspace and coverage settings. |
| frontend/utils/vite.config.ts | Removes individual coverage config to use the global settings.         |
| frontend/lib/vite.config.ts   | Removes individual coverage config to use the global settings.         |
| frontend/connection/vite.config.ts | Removes individual coverage config to use the global settings.         |
| frontend/app/vite.config.ts    | Removes individual coverage config to use the global settings, with minor import reordering. |
</details>


<details>
<summary>Files not reviewed (1)</summary>

* **frontend/package.json**: Language not supported
</details>



state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T22:39:16Z
user: login: sfc-gh-bnisco
body: Awesome! I took a look at the outputted coverage HTML report and it looks great
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-28T15:46:51Z
user: login: kmcgrady
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-28T15:49:28Z
user: login: lukasmasuch
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-28T16:01:29Z
user: login: kmcgrady
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-28T16:09:29Z
user: login: lukasmasuch
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-28T16:28:28Z

Review comments:
diff_hunk: @@ -0,0 +1,42 @@
+/**
+ * Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { coverageConfigDefaults, defineConfig } from "vitest/config"
+
+export default defineConfig({
+  test: {
+    // Define workspace paths directly
+    workspace: [
+      "app/vite.config.ts",
+      "lib/vite.config.ts",
+      "connection/vite.config.ts",
+      "utils/vite.config.ts",
+    ],
+
+    // Global coverage configuration
+    coverage: {
+      provider: "v8",
+      reporter: ["text-summary", "json-summary", "html"],
+      include: [
+        "app/src/**/*",
+        "lib/src/**/*",
+        "utils/src/**/*",
+        "connection/src/**/*",
+      ],
path: frontend/vitest.config.ts
user: login: kmcgrady
body: There is starting to be an effort for any new package to include these files in multiple locations. Is this something that could be resolved with a single `packages` directory by chance?
created_at: 2025-03-28T15:48:58Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,42 @@
+/**
+ * Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { coverageConfigDefaults, defineConfig } from "vitest/config"
+
+export default defineConfig({
+  test: {
+    // Define workspace paths directly
+    workspace: [
+      "app/vite.config.ts",
+      "lib/vite.config.ts",
+      "connection/vite.config.ts",
+      "utils/vite.config.ts",
+    ],
+
+    // Global coverage configuration
+    coverage: {
+      provider: "v8",
+      reporter: ["text-summary", "json-summary", "html"],
+      include: [
+        "app/src/**/*",
+        "lib/src/**/*",
+        "utils/src/**/*",
+        "connection/src/**/*",
+      ],
path: frontend/vitest.config.ts
user: login: lukasmasuch
body: You mean that we manually need to include the path for all new sub-packages here? I think we could also just use slightly more generic globs here, e.g.:
```
include: ["*/src/**/*"]
```

and above:

```
 workspace: [
      "*/vite.config.ts"
    ],
```

This would probably allow all packages to be picked up automatically. We would just need to manage more specific coverage excludes here.

Would you prefer to make this config more generic? 
created_at: 2025-03-28T16:01:29Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,42 @@
+/**
+ * Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { coverageConfigDefaults, defineConfig } from "vitest/config"
+
+export default defineConfig({
+  test: {
+    // Define workspace paths directly
+    workspace: [
+      "app/vite.config.ts",
+      "lib/vite.config.ts",
+      "connection/vite.config.ts",
+      "utils/vite.config.ts",
+    ],
+
+    // Global coverage configuration
+    coverage: {
+      provider: "v8",
+      reporter: ["text-summary", "json-summary", "html"],
+      include: [
+        "app/src/**/*",
+        "lib/src/**/*",
+        "utils/src/**/*",
+        "connection/src/**/*",
+      ],
path: frontend/vitest.config.ts
user: login: kmcgrady
body: I'm fine with it as is for now, I am not sure when we are adding new packages, so it might be fine. Was just curious if there's a way to make this simpler so that when I add a package I don't accidentally forget to add it somewhere.
created_at: 2025-03-28T16:09:28Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,42 @@
+/**
+ * Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { coverageConfigDefaults, defineConfig } from "vitest/config"
+
+export default defineConfig({
+  test: {
+    // Define workspace paths directly
+    workspace: [
+      "app/vite.config.ts",
+      "lib/vite.config.ts",
+      "connection/vite.config.ts",
+      "utils/vite.config.ts",
+    ],
+
+    // Global coverage configuration
+    coverage: {
+      provider: "v8",
+      reporter: ["text-summary", "json-summary", "html"],
+      include: [
+        "app/src/**/*",
+        "lib/src/**/*",
+        "utils/src/**/*",
+        "connection/src/**/*",
+      ],
path: frontend/vitest.config.ts
user: login: lukasmasuch
body: I changed the vite workspace config to be more generic 👍  -> the coverage report looks the same and lowers the maintenance. 
created_at: 2025-03-28T16:28:28Z
author_association: COLLABORATOR

PR comments:
user: login: snyk-io[bot]
created_at: 2025-03-27T22:36:45Z
body: ![](https://res.cloudinary.com/snyk/image/upload/r-d/scm-platform/snyk-pull-requests/pr-banner-default.svg)

### :tada: **Snyk checks have passed. No issues have been found so far.**

:white_check_mark: **security/snyk** check is complete. No issues have been found. [(View Details)](https://app.snyk.io/org/streamlit-open-source/pr-checks/adc02956-9dcf-403d-8438-4aaa2eff7fb7?refs=a4d26f6ec5589fbd1dae187892d6b6366ad26ccf&source=prChecksComment)

:white_check_mark: **license/snyk** check is complete. No issues have been found. [(View Details)](https://app.snyk.io/org/streamlit-open-source/pr-checks/adc02956-9dcf-403d-8438-4aaa2eff7fb7/license?refs=a4d26f6ec5589fbd1dae187892d6b6366ad26ccf&source=prChecksComment)



author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-27T22:36:58Z
body: ### ✅ PR preview is ready!

| Name | Link |
| :----: | ---- |
| 📦 Wheel file | https://core-previews.s3-us-west-2.amazonaws.com/pr-10947/streamlit-1.44.0-py3-none-any.whl |
| 🕹️ Preview app | [pr-10947.streamlit.app](https://pr-10947.streamlit.app) (☁️ Deploy [here](https://share.streamlit.io/deploy?repository=streamlit/core-previews&branch=pr-10947&mainModule=E2E_Tester_🧪.py&subdomain=pr-10947) if not accessible)| 
author_association: CONTRIBUTOR