From 25ac69afc5ac9324079be5f0d02b2a2b03dcc784 Mon Sep 17 00:00:00 2001
From: Stephen Zhou <38493346+hyoban@users.noreply.github.com>
Date: Thu, 29 Jan 2026 17:58:10 +0800
Subject: [PATCH 2/3] docs: relocate frontend docs for agents and human
(#31714)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.agents/skills/component-refactoring/SKILL.md | 2 +-
.agents/skills/frontend-testing/SKILL.md | 4 +-
.../frontend-testing/references/workflow.md | 2 +-
AGENTS.md | 33 +-----------
CONTRIBUTING.md | 2 +-
web/AGENTS.md | 6 ++-
web/README.md | 2 +
web/docs/lint.md | 51 +++++++++++++++++++
web/{testing/testing.md => docs/test.md} | 4 +-
web/eslint-suppressions.json | 5 --
web/scripts/analyze-component.js | 4 +-
11 files changed, 69 insertions(+), 46 deletions(-)
create mode 100644 web/docs/lint.md
rename web/{testing/testing.md => docs/test.md} (99%)
diff --git a/.agents/skills/component-refactoring/SKILL.md b/.agents/skills/component-refactoring/SKILL.md
index 7006c382c8..140e0ef434 100644
--- a/.agents/skills/component-refactoring/SKILL.md
+++ b/.agents/skills/component-refactoring/SKILL.md
@@ -480,4 +480,4 @@ const useButtonState = () => {
### Related Skills
- `frontend-testing` - For testing refactored components
-- `web/testing/testing.md` - Testing specification
+- `web/docs/test.md` - Testing specification
diff --git a/.agents/skills/frontend-testing/SKILL.md b/.agents/skills/frontend-testing/SKILL.md
index 0716c81ef7..280fcb6341 100644
--- a/.agents/skills/frontend-testing/SKILL.md
+++ b/.agents/skills/frontend-testing/SKILL.md
@@ -7,7 +7,7 @@ description: Generate Vitest + React Testing Library tests for Dify frontend com
This skill enables Claude to generate high-quality, comprehensive frontend tests for the Dify project following established conventions and best practices.
-> **⚠️ Authoritative Source**: This skill is derived from `web/testing/testing.md`. Use Vitest mock/timer APIs (`vi.*`).
+> **⚠️ Authoritative Source**: This skill is derived from `web/docs/test.md`. Use Vitest mock/timer APIs (`vi.*`).
## When to Apply This Skill
@@ -309,7 +309,7 @@ For more detailed information, refer to:
### Primary Specification (MUST follow)
-- **`web/testing/testing.md`** - The canonical testing specification. This skill is derived from this document.
+- **`web/docs/test.md`** - The canonical testing specification. This skill is derived from this document.
### Reference Examples in Codebase
diff --git a/.agents/skills/frontend-testing/references/workflow.md b/.agents/skills/frontend-testing/references/workflow.md
index 009c3e013b..bc4ed8285a 100644
--- a/.agents/skills/frontend-testing/references/workflow.md
+++ b/.agents/skills/frontend-testing/references/workflow.md
@@ -4,7 +4,7 @@ This guide defines the workflow for generating tests, especially for complex com
## Scope Clarification
-This guide addresses **multi-file workflow** (how to process multiple test files). For coverage requirements within a single test file, see `web/testing/testing.md` § Coverage Goals.
+This guide addresses **multi-file workflow** (how to process multiple test files). For coverage requirements within a single test file, see `web/docs/test.md` § Coverage Goals.
| Scope | Rule |
|-------|------|
diff --git a/AGENTS.md b/AGENTS.md
index 7d96ac3a6d..51fa6e4527 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -7,7 +7,7 @@ Dify is an open-source platform for developing LLM applications with an intuitiv
The codebase is split into:
- **Backend API** (`/api`): Python Flask application organized with Domain-Driven Design
-- **Frontend Web** (`/web`): Next.js 15 application using TypeScript and React 19
+- **Frontend Web** (`/web`): Next.js application using TypeScript and React
- **Docker deployment** (`/docker`): Containerized deployment configurations
## Backend Workflow
@@ -18,36 +18,7 @@ The codebase is split into:
## Frontend Workflow
-```bash
-cd web
-pnpm lint:fix
-pnpm type-check:tsgo
-pnpm test
-```
-
-### Frontend Linting
-
-ESLint is used for frontend code quality. Available commands:
-
-```bash
-# Lint all files (report only)
-pnpm lint
-
-# Lint and auto-fix issues
-pnpm lint:fix
-
-# Lint specific files or directories
-pnpm lint:fix app/components/base/button/
-pnpm lint:fix app/components/base/button/index.tsx
-
-# Lint quietly (errors only, no warnings)
-pnpm lint:quiet
-
-# Check code complexity
-pnpm lint:complexity
-```
-
-**Important**: Always run `pnpm lint:fix` before committing. The pre-commit hook runs `lint-staged` which only lints staged files.
+- Read `web/AGENTS.md` for details
## Testing & Quality Practices
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 20a7d6c6f6..d7f007af67 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -77,7 +77,7 @@ How we prioritize:
For setting up the frontend service, please refer to our comprehensive [guide](https://github.com/langgenius/dify/blob/main/web/README.md) in the `web/README.md` file. This document provides detailed instructions to help you set up the frontend environment properly.
-**Testing**: All React components must have comprehensive test coverage. See [web/testing/testing.md](https://github.com/langgenius/dify/blob/main/web/testing/testing.md) for the canonical frontend testing guidelines and follow every requirement described there.
+**Testing**: All React components must have comprehensive test coverage. See [web/docs/test.md](https://github.com/langgenius/dify/blob/main/web/docs/test.md) for the canonical frontend testing guidelines and follow every requirement described there.
#### Backend
diff --git a/web/AGENTS.md b/web/AGENTS.md
index 7362cd51db..5dd41b8a3c 100644
--- a/web/AGENTS.md
+++ b/web/AGENTS.md
@@ -1,5 +1,9 @@
+## Frontend Workflow
+
+- Refer to the `./docs/test.md` and `./docs/lint.md` for detailed frontend workflow instructions.
+
## Automated Test Generation
-- Use `web/testing/testing.md` as the canonical instruction set for generating frontend automated tests.
+- Use `./docs/test.md` as the canonical instruction set for generating frontend automated tests.
- When proposing or saving tests, re-read that document and follow every requirement.
- All frontend tests MUST also comply with the `frontend-testing` skill. Treat the skill as a mandatory constraint, not optional guidance.
diff --git a/web/README.md b/web/README.md
index 9c731a081a..64039709dc 100644
--- a/web/README.md
+++ b/web/README.md
@@ -107,6 +107,8 @@ Open [http://localhost:6006](http://localhost:6006) with your browser to see the
If your IDE is VSCode, rename `web/.vscode/settings.example.json` to `web/.vscode/settings.json` for lint code setting.
+Then follow the [Lint Documentation](./docs/lint.md) to lint the code.
+
## Test
We use [Vitest](https://vitest.dev/) and [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) for Unit Testing.
diff --git a/web/docs/lint.md b/web/docs/lint.md
new file mode 100644
index 0000000000..051f9e6ecd
--- /dev/null
+++ b/web/docs/lint.md
@@ -0,0 +1,51 @@
+# Lint Guide
+
+We use ESLint and Typescript to maintain code quality and consistency across the project.
+
+## ESLint
+
+### Common Flags
+
+**File/folder targeting**: Append paths to lint specific files or directories.
+
+```sh
+pnpm eslint [options] file.js [file.js] [dir]
+```
+
+**`--cache`**: Caches lint results for faster subsequent runs. Keep this enabled by default; only disable when you encounter unexpected lint results.
+
+**`--concurrency`**: Enables multi-threaded linting. Use `--concurrency=auto` or experiment with specific numbers to find the optimal setting for your machine. Keep this enabled when linting multiple files.
+
+- [ESLint multi-thread linting blog post](https://eslint.org/blog/2025/08/multithread-linting/)
+
+**`--fix`**: Automatically fixes auto-fixable rule violations. Always review the diff before committing to ensure no unintended changes.
+
+**`--quiet`**: Suppresses warnings and only shows errors. Useful when you want to reduce noise from existing issues.
+
+**`--suppress-all`**: Temporarily suppresses error-level violations and records them, allowing CI to pass. Treat this as an escape hatch—fix these errors when time permits.
+
+**`--prune-suppressions`**: Removes outdated suppressions after you've fixed the underlying errors.
+
+- [ESLint bulk suppressions blog post](https://eslint.org/blog/2025/04/introducing-bulk-suppressions/)
+
+### Type-Aware Linting
+
+Some ESLint rules require type information, such as [no-leaked-conditional-rendering](https://www.eslint-react.xyz/docs/rules/no-leaked-conditional-rendering). However, [typed linting via typescript-eslint](https://typescript-eslint.io/getting-started/typed-linting) is too slow for practical use, so we use [TSSLint](https://github.com/johnsoncodehk/tsslint) instead.
+
+```sh
+pnpm lint:tss
+```
+
+This command lints the entire project and is intended for final verification before committing or pushing changes.
+
+## Type Check
+
+You should be able to see suggestions from TypeScript in your editor for all open files.
+
+However, it can be useful to run the TypeScript 7 command-line (tsgo) to type check all files:
+
+```sh
+pnpm type-check:tsgo
+```
+
+Prefer using `tsgo` for type checking as it is significantly faster than the standard TypeScript compiler. Only fall back to `pnpm type-check` (which uses `tsc`) if you encounter unexpected results.
diff --git a/web/testing/testing.md b/web/docs/test.md
similarity index 99%
rename from web/testing/testing.md
rename to web/docs/test.md
index 47341e445e..cac0e0e351 100644
--- a/web/testing/testing.md
+++ b/web/docs/test.md
@@ -360,11 +360,11 @@ describe('ComponentName', () => {
let mockPortalOpenState = false
vi.mock('@/app/components/base/portal-to-follow-elem', () => ({
- PortalToFollowElem: ({ children, open, ...props }: any) => {
+ PortalToFollowElem: ({ children, open, ...props }) => {
mockPortalOpenState = open || false // Update shared state
return
{children}
},
- PortalToFollowElemContent: ({ children }: any) => {
+ PortalToFollowElemContent: ({ children }) => {
// ✅ Matches actual: returns null when open is false
if (!mockPortalOpenState)
return null
diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json
index 6193a8ad4e..63f10d238c 100644
--- a/web/eslint-suppressions.json
+++ b/web/eslint-suppressions.json
@@ -4318,11 +4318,6 @@
"count": 10
}
},
- "testing/testing.md": {
- "ts/no-explicit-any": {
- "count": 2
- }
- },
"types/app.ts": {
"ts/no-explicit-any": {
"count": 1
diff --git a/web/scripts/analyze-component.js b/web/scripts/analyze-component.js
index b09301503c..2fdff2f3d0 100755
--- a/web/scripts/analyze-component.js
+++ b/web/scripts/analyze-component.js
@@ -337,7 +337,7 @@ Test file under review:
${testPath}
Checklist (ensure every item is addressed in your review):
-- Confirm the tests satisfy all requirements listed above and in web/testing/TESTING.md.
+- Confirm the tests satisfy all requirements listed above and in web/docs/test.md.
- Verify Arrange → Act → Assert structure, mocks, and cleanup follow project conventions.
- Ensure all detected component features (state, effects, routing, API, events, etc.) are exercised, including edge cases and error paths.
- Check coverage of prop variations, null/undefined inputs, and high-priority workflows implied by usage score.
@@ -382,7 +382,7 @@ Examples:
# Review existing test
pnpm analyze-component app/components/base/button/index.tsx --review
-For complete testing guidelines, see: web/testing/testing.md
+For complete testing guidelines, see: web/docs/test.md
`)
}
From 8aeef36e2d16c9b9ba41088aee937d0348b5cbec Mon Sep 17 00:00:00 2001
From: yihong
Date: Thu, 29 Jan 2026 18:17:40 +0800
Subject: [PATCH 3/3] feat: use xdist to make make test faster (#30824)
Signed-off-by: yihong0618
---
.github/workflows/api-tests.yml | 1 +
Makefile | 2 +-
api/pyproject.toml | 1 +
api/tests/unit_tests/conftest.py | 17 +++++++++++++
.../console/app/test_app_response_models.py | 7 ++++++
api/uv.lock | 24 +++++++++++++++++++
dev/pytest/pytest_unit_tests.sh | 10 ++++++--
7 files changed, 59 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml
index 190e00d9fe..52e3272f99 100644
--- a/.github/workflows/api-tests.yml
+++ b/.github/workflows/api-tests.yml
@@ -72,6 +72,7 @@ jobs:
OPENDAL_FS_ROOT: /tmp/dify-storage
run: |
uv run --project api pytest \
+ -n auto \
--timeout "${PYTEST_TIMEOUT:-180}" \
api/tests/integration_tests/workflow \
api/tests/integration_tests/tools \
diff --git a/Makefile b/Makefile
index 20cede9a5e..984e8676ee 100644
--- a/Makefile
+++ b/Makefile
@@ -80,7 +80,7 @@ test:
echo "Target: $(TARGET_TESTS)"; \
uv run --project api --dev pytest $(TARGET_TESTS); \
else \
- uv run --project api --dev dev/pytest/pytest_unit_tests.sh; \
+ PYTEST_XDIST_ARGS="-n auto" uv run --project api --dev dev/pytest/pytest_unit_tests.sh; \
fi
@echo "✅ Tests complete"
diff --git a/api/pyproject.toml b/api/pyproject.toml
index 575c1434c5..af2dba6fac 100644
--- a/api/pyproject.toml
+++ b/api/pyproject.toml
@@ -175,6 +175,7 @@ dev = [
# "locust>=2.40.4", # Temporarily removed due to compatibility issues. Uncomment when resolved.
"sseclient-py>=1.8.0",
"pytest-timeout>=2.4.0",
+ "pytest-xdist>=3.8.0",
]
############################################################
diff --git a/api/tests/unit_tests/conftest.py b/api/tests/unit_tests/conftest.py
index c5e1576186..e3c1a617f7 100644
--- a/api/tests/unit_tests/conftest.py
+++ b/api/tests/unit_tests/conftest.py
@@ -3,6 +3,7 @@ from unittest.mock import MagicMock, patch
import pytest
from flask import Flask
+from sqlalchemy import create_engine
# Getting the absolute path of the current file's directory
ABS_PATH = os.path.dirname(os.path.abspath(__file__))
@@ -36,6 +37,7 @@ import sys
sys.path.insert(0, PROJECT_DIR)
+from core.db.session_factory import configure_session_factory, session_factory
from extensions import ext_redis
@@ -102,3 +104,18 @@ def reset_secret_key():
yield
finally:
dify_config.SECRET_KEY = original
+
+
+@pytest.fixture(scope="session")
+def _unit_test_engine():
+ engine = create_engine("sqlite:///:memory:")
+ yield engine
+ engine.dispose()
+
+
+@pytest.fixture(autouse=True)
+def _configure_session_factory(_unit_test_engine):
+ try:
+ session_factory.get_session_maker()
+ except RuntimeError:
+ configure_session_factory(_unit_test_engine, expire_on_commit=False)
diff --git a/api/tests/unit_tests/controllers/console/app/test_app_response_models.py b/api/tests/unit_tests/controllers/console/app/test_app_response_models.py
index 40eb59a8f4..c557605916 100644
--- a/api/tests/unit_tests/controllers/console/app/test_app_response_models.py
+++ b/api/tests/unit_tests/controllers/console/app/test_app_response_models.py
@@ -31,6 +31,13 @@ def _load_app_module():
def schema_model(self, name, schema):
self.models[name] = schema
+ return schema
+
+ def model(self, name, model_dict=None, **kwargs):
+ """Register a model with the namespace (flask-restx compatibility)."""
+ if model_dict is not None:
+ self.models[name] = model_dict
+ return model_dict
def _decorator(self, obj):
return obj
diff --git a/api/uv.lock b/api/uv.lock
index 7808c16a8c..a3ad292168 100644
--- a/api/uv.lock
+++ b/api/uv.lock
@@ -1479,6 +1479,7 @@ dev = [
{ name = "pytest-env" },
{ name = "pytest-mock" },
{ name = "pytest-timeout" },
+ { name = "pytest-xdist" },
{ name = "ruff" },
{ name = "scipy-stubs" },
{ name = "sseclient-py" },
@@ -1678,6 +1679,7 @@ dev = [
{ name = "pytest-env", specifier = "~=1.1.3" },
{ name = "pytest-mock", specifier = "~=3.14.0" },
{ name = "pytest-timeout", specifier = ">=2.4.0" },
+ { name = "pytest-xdist", specifier = ">=3.8.0" },
{ name = "ruff", specifier = "~=0.14.0" },
{ name = "scipy-stubs", specifier = ">=1.15.3.0" },
{ name = "sseclient-py", specifier = ">=1.8.0" },
@@ -1896,6 +1898,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/19/d8/2a1c638d9e0aa7e269269a1a1bf423ddd94267f1a01bbe3ad03432b67dd4/eval_type_backport-0.3.0-py3-none-any.whl", hash = "sha256:975a10a0fe333c8b6260d7fdb637698c9a16c3a9e3b6eb943fee6a6f67a37fe8", size = 6061, upload-time = "2025-11-13T20:56:49.499Z" },
]
+[[package]]
+name = "execnet"
+version = "2.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" },
+]
+
[[package]]
name = "faker"
version = "38.2.0"
@@ -5141,6 +5152,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" },
]
+[[package]]
+name = "pytest-xdist"
+version = "3.8.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "execnet" },
+ { name = "pytest" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" },
+]
+
[[package]]
name = "python-calamine"
version = "0.5.4"
diff --git a/dev/pytest/pytest_unit_tests.sh b/dev/pytest/pytest_unit_tests.sh
index 496cb40952..7c39a48bf4 100755
--- a/dev/pytest/pytest_unit_tests.sh
+++ b/dev/pytest/pytest_unit_tests.sh
@@ -5,6 +5,12 @@ SCRIPT_DIR="$(dirname "$(realpath "$0")")"
cd "$SCRIPT_DIR/../.."
PYTEST_TIMEOUT="${PYTEST_TIMEOUT:-20}"
+PYTEST_XDIST_ARGS="${PYTEST_XDIST_ARGS:--n auto}"
-# libs
-pytest --timeout "${PYTEST_TIMEOUT}" api/tests/unit_tests
+# Run most tests in parallel (excluding controllers which have import conflicts with xdist)
+# Controller tests have module-level side effects (Flask route registration) that cause
+# race conditions when imported concurrently by multiple pytest-xdist workers.
+pytest --timeout "${PYTEST_TIMEOUT}" ${PYTEST_XDIST_ARGS} api/tests/unit_tests --ignore=api/tests/unit_tests/controllers
+
+# Run controller tests sequentially to avoid import race conditions
+pytest --timeout "${PYTEST_TIMEOUT}" api/tests/unit_tests/controllers