Skip to content

Run tests and CI checks

Run the right checks for your change scope before committing. CI runs through GitHub Actions and covers static checks, type checks, tests, builds, automatic format fixes, and documentation deployment. A CircleCI watchdog (.circleci/) mirrors the check pipeline: when GitHub Actions entirely fails for a commit (for example a GitHub platform outage), CircleCI automatically takes over the full static analysis, database test matrix, and docs checks. Builds, releases, and deployment remain GitHub Actions only. Local automation uses Taskfile.yml as the main entrypoint.

Terminal window
task check
task test
task build
task ci

task check covers Ruff, Ruff format check, Markdown lint, Turbo lint, Pyright, ty, and Turbo type check. task test covers root pytest, lingc-cli pytest, and docs Vitest. task ci runs check, test, and build in order.

Terminal window
uv run -m ruff check . --output-format=github
uv run -m ruff format --check .
uv run -m pyright
uv run -m ty check --output-format github
uv run -m pytest

If only one module changed, run the related test file first, then broaden the scope as needed.

After hook, adapter, or startup-flow changes, static checks are not enough. Run the three-stage live smoke test below to catch forward-reference signature errors, import-order issues, schema-write leaks, and CLI regressions that static analysis misses.

Start the bot with the development environment selected by ENVIRONMENT=dev so NoneBot2 loads .env + .env.dev:

Terminal window
uvx --from nb-cli nb.exe run

Wait until you see Application startup complete. and at least one event cycle. To bound the run, wrap the command with timeout (e.g. timeout 10s); exit code 124 is expected when the timeout kills the process. If the process exits before startup finishes, increase the timeout.

Stage 2 — Production environment (clean localstore)

Section titled “Stage 2 — Production environment (clean localstore)”

Verify the bot starts cleanly from a pristine localstore state, which proves startup is schema-write-free and survives a fresh deployment.

  1. Delete every localstore-owned directory under the project:

    Terminal window
    rm -rf config/nonebot_plugin_lingchu_bot/ \
    data/nonebot_plugin_lingchu_bot/ \
    data/nonebot_plugin_orm/ \
    cache/nonebot_plugin_lingchu_bot/

    These four paths are resolved by nonebot_plugin_localstore (set LOCALSTORE_USE_CWD=true in .env). Repository-root config/, data/, and cache/ are local development artifacts and disposable.

  2. Set ENVIRONMENT=prod so NoneBot2 loads .env + .env.prod and start the bot:

    Terminal window
    ENVIRONMENT=prod uvx --from nb-cli nb.exe run
  3. Acceptance: the process reaches Application startup complete. without hard errors. A LLM configuration missing or invalid; AI is unavailable warning is acceptable; any traceback, schema-write, or migration side effect is a hard failure.

In addition to the manual runtime smoke test, CI/CD runs an automated smoke test stage after the build job and before deployment. This verifies that the built container can start NoneBot, register runtime hooks, initialize core services, and produce a JUnit report.

  • smoke-test runs after the build job in 👷 CI-builds and only starts when the build succeeds.
  • docs-smoke runs after the docs build job in 📚 Docs Deploy and only starts when the docs build succeeds.

If any smoke check fails, the workflow stops immediately and does not deploy. The job uploads smoke-test-results.xml as an artifact so you can inspect the failing assertion and traceback.

You can run the same smoke checks locally:

Terminal window
# Build and run the smoke test container with Docker Compose
task smoke
# Run in CI mode and emit the JUnit XML report
task ci:smoke
# Equivalent raw Docker Compose command
docker compose --profile smoke up --build --abort-on-container-exit smoke-test

The project supports four database backends for testing via the SQLALCHEMY_DATABASE_URL environment variable, following NoneBot database testing best practices.

By default, tests use SQLite. To test against PostgreSQL / MySQL / MariaDB locally:

  1. Install the database drivers (already in the test dependency group):
Terminal window
uv sync --all-groups
  1. Start a database server (via Docker or local install).

  2. Set the SQLALCHEMY_DATABASE_URL environment variable and run tests:

Terminal window
# PostgreSQL
export SQLALCHEMY_DATABASE_URL="postgresql+psycopg://postgres:postgres@localhost:5432/postgres"
uv run -m pytest
# MySQL
export SQLALCHEMY_DATABASE_URL="mysql+aiomysql://mysql:mysql@localhost:3306/mymysql"
uv run -m pytest
# MariaDB (SQLAlchemy auto-detects MariaDB via VERSION() without dedicated driver)
export SQLALCHEMY_DATABASE_URL="mysql+aiomysql://mariadb:mariadb@localhost:3306/mymariadb"
uv run -m pytest
  1. Run migrations before testing:
Terminal window
uv run nb orm upgrade

CI runs tests on 8 jobs across 4 database engines, covering both LTS and latest versions:

  • SQLite (default, no service required; version pinned by Python sqlite3/aiosqlite)
  • PostgreSQL 16 (postgres:16-alpine, supported until Nov 2028) and PostgreSQL 18 (postgres:18-alpine, latest)
  • MySQL 8.4 LTS (mysql:8.4, until ~2032) and MySQL 9.7 LTS (mysql:9.7, until ~2034)
  • MariaDB 11.4 LTS (mariadb:11.4, until May 2029) and MariaDB 11.8 LTS (mariadb:11.8, until Jun 2028)

Each matrix entry carries an engine + image field; service containers select their image via ${{ matrix.db.engine == '<engine>' && matrix.db.image || '' }} so multiple versions of the same engine coexist in one matrix. The fail-fast: false strategy ensures all databases are tested even if one fails.

The docs site uses pnpm, Astro Starlight, Vitest, Playwright, ESLint, and Turbo:

Terminal window
pnpm --filter docs lint
pnpm --filter docs test
pnpm --filter docs run test:e2e:hook
pnpm --filter docs run test:e2e
pnpm --filter docs check-types
pnpm turbo run build --filter=docs

Vitest covers component and library behavior under apps/docs/src/__tests__. Playwright tests live under apps/docs/e2e and cover browser-level docs navigation. Local hooks run the Chromium smoke command (test:e2e:hook) on docs changes; CI runs all configured Playwright browser projects and uploads the HTML report plus traces.

Write Playwright tests with role/text locators and web-first assertions such as toBeVisible() and toHaveAttribute(). Avoid sleeps, screenshot assertions, and broad visual checks unless the change specifically needs them.

Terminal window
pnpm exec markdownlint-cli2

Globs and ignores are configured centrally in .markdownlint-cli2.jsonc at the repository root, so no path arguments are needed.

After changing translatable strings, run:

Terminal window
task i18n

If you only change documentation that describes i18n, gettext catalogs do not need to be regenerated.

  • 🧪 Python CI: A shared detect-changes composite action (.github/actions/detect-changes) outputs boolean flags per file type (python/markdown/frontend/frontend-code/frontend-style/frontend-content/frontend-tsx), then conditionally runs Static Analysis (on Python or markdown changes, via task ci:static) and Tests & Type Check (on Python changes, across the multi-database matrix). Auto Fix & Format runs on main/dev pushes. Conditions align with pre-commit v3 NEEDS_LINT/NEEDS_TYPE_CHECK/NEEDS_DOCS_TEST.
  • 🧪 Frontend CI: Uses the same detect-changes action, then conditionally runs Docs Check — ESLint on code/style, check-types on any frontend, link validation on content, Vitest on code/content.
  • 🎭 Playwright: runs docs E2E tests for apps/docs and packages changes, installs browser dependencies, and uploads Playwright report artifacts.
  • 👷 CI-builds: runs task ci:build; the versioned-build job runs on a daily schedule (02:00 Asia/Shanghai, UTC 0 18 * * *) — it syncs the version from the latest tag, bumps the dev version, writes core_version, builds, archives artifacts, generates provenance, and tags the version.
  • 📚 Docs Deploy: when docs-related paths are pushed to main or dev, it runs pnpm/Turbo lint, docs test, docs build, and deploys GitHub Pages.

Open the failing job logs and locate the command, rule, and line number. Fix only the smallest related scope and rerun the corresponding local command.