13 KiB
Conductor UI v2
The open-source React UI for Conductor. It ships as both a standalone web application and an npm library that enterprise packages can extend via a plugin system.
Running locally
Prerequisites
- Node.js 18+
- pnpm 10.x — we use pnpm 10 (not v11) since pnpm v11 requires Node.js v22+. The exact version is pinned via
packageManagerinpackage.json. Run once to activate it:corepack enable - A running Conductor server (default:
http://localhost:8080)
Setup
pnpm install
Configure the backend URL in .env (see .env for defaults):
VITE_WF_SERVER=http://localhost:8080
Start the dev server
pnpm dev
The app will be available at http://localhost:1234.
Runtime configuration
The app reads runtime config from public/context.js, which is loaded at startup (not bundled). Copy the example and edit as needed:
cp public/context.js.example public/context.js
This file sets feature flags (window.conductor) and auth config (window.authConfig) without requiring a rebuild.
Available scripts
| Script | Description |
|---|---|
pnpm dev |
Start dev server with HMR |
pnpm build |
Build standalone app to dist/ |
pnpm build:lib |
Build npm library to dist/ |
pnpm build:all |
Build both app and library |
pnpm lint |
Run ESLint |
pnpm lint:fix |
Run ESLint with auto-fix |
pnpm prettier:check |
Check formatting |
pnpm prettier:write |
Auto-format all files |
pnpm typecheck |
Type-check without emitting |
pnpm test |
Run Vitest unit tests (single pass) |
pnpm test:watch |
Run Vitest in watch mode |
pnpm test:coverage |
Run Vitest with v8 coverage report |
pnpm test:e2e |
Run Playwright UI tests (mocked backend, headless) |
pnpm test:e2e:ui |
Open the Playwright interactive UI |
pnpm test:e2e:headed |
Run UI tests in a visible browser |
pnpm test:e2e:debug |
Step through UI tests in the Playwright debugger |
pnpm test:e2e:integration |
Integration E2E in Docker (Linux Chromium) |
pnpm test:e2e:integration:update-snapshots |
Regenerate integration screenshot baselines |
pnpm test:e2e:integration:ui |
Host Playwright UI (debug only; not for baselines) |
pnpm test:e2e:integration:headed |
Host headed Chromium (debug only) |
Testing
Unit tests (Vitest)
Tests live alongside source files as *.test.{ts,tsx} and run in jsdom.
They cover utilities, state machines, and component logic without a browser or server.
pnpm test # single run
pnpm test:watch # re-runs on file change
pnpm test:coverage # produces coverage/index.html
E2E tests (Playwright)
E2E tests live in e2e/ and are run by Playwright against a real Chromium
browser. Every test mocks the Conductor backend with page.route(), so no
running Conductor server is required — the suite works entirely against the
built-in Vite dev server.
First-time setup
Install the Playwright browser binaries (one-time per machine):
pnpm exec playwright install --with-deps chromium
Running locally
# Headless (fastest) — reuses a running dev server on :1234 if one exists
pnpm test:e2e
# Interactive Playwright UI — best for writing and debugging tests
pnpm test:e2e:ui
# Watch the browser run the tests
pnpm test:e2e:headed
# Step through a single test with the Playwright debugger
pnpm test:e2e:debug
# Run one file
pnpm test:e2e e2e/smoke.spec.ts
# Run tests whose name matches a pattern
pnpm test:e2e --grep "navigates to"
If pnpm dev is already running on port 1234, Playwright reuses that server.
If nothing is running, it starts a dev server automatically for the test run.
Running in CI
Set CI=true (GitHub Actions does this automatically) and run:
pnpm exec playwright install --with-deps chromium
pnpm test:e2e
With CI=true the config:
- Always starts a fresh dev server (never reuses an existing one)
- Retries each failing test up to 2 times before marking it failed
- Uses a single worker to avoid resource contention
Example GitHub Actions job:
- name: Install Playwright browsers
run: pnpm exec playwright install --with-deps chromium
- name: Run E2E tests
run: pnpm test:e2e
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 7
Integration tests (Playwright + live backend)
Integration tests live in e2e/integration/ and use
playwright.integration.config.ts. They talk to a real Conductor server and
verify the full stack end-to-end: the API client creates test data, the
browser navigates through the UI, and assertions confirm the data is rendered
correctly.
Default runs use Docker for both the Conductor backend and Playwright
(Linux Chromium) so screenshot baselines are identical on developer machines
and in CI — the same approach as pnpm test:e2e:snapshots.
How it works (pnpm test:e2e:integration)
scripts/run-integration-e2e.shensures theconductor:serverimage exists (builds it if missing) and thatdist/is present (pnpm buildif needed).docker-compose.integration.ymlstarts Postgres + Conductor, serves the production UI withvite preview, then runs Playwright insidemcr.microsoft.com/playwright(shared network with the preview container).- Each test file uses
e2e/integration/api-client.tsto create isolated test data (unique names per run) and cleans up inafterAll. - The compose project (
conductor-ui-e2e-integration) is torn down when the script exits.
Running integration tests locally
Prerequisites: Docker must be running.
pnpm test:e2e:integration
This single command:
- Builds
conductor:serverif missing (first run ~5–10 min; later ~30s via layer cache) - Builds the UI (
pnpm build) whendist/is missing - Starts Postgres + Conductor + vite preview + Playwright via
docker-compose.integration.yml - Tears the stack down when finished
Visual baselines (always update via Docker Chromium):
pnpm test:e2e:integration:update-snapshots
# or a single file:
pnpm test:e2e:integration:update-snapshots e2e/integration/workflows.spec.ts
Common options
# Run a single spec file
pnpm test:e2e:integration e2e/integration/workflows.spec.ts
# Run tests whose name matches a pattern
pnpm test:e2e:integration --grep "appears in the"
# Host-only debugging (local Chromium — do NOT use to refresh baselines)
pnpm test:e2e:integration:ui
pnpm test:e2e:integration:headed
:ui / :headed still use the host Playwright +
docker/docker-compose-ui-e2e.yaml path (global-setup). Prefer them for
stepping through failures; always regenerate screenshots with
test:e2e:integration:update-snapshots.
To stop a leftover integration stack manually:
docker compose -p conductor-ui-e2e-integration -f docker-compose.integration.yml down
Running integration tests in CI
Build conductor:server and the UI on the runner, then run the Dockerized
Playwright suite (Chromium comes from the Playwright image — no host browser
install):
- name: Build UI
run: pnpm build
env:
E2E_COVERAGE: "true"
NODE_OPTIONS: --max-old-space-size=8192
- name: Run integration tests
run: pnpm test:e2e:integration
env:
E2E_COVERAGE: "true"
SKIP_WEBSERVER_BUILD: "true" # reuse dist/ from the build step
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- name: Upload integration report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-integration-report
path: playwright-integration-report/
retention-days: 7
If you cache the Docker image between CI runs (e.g. using GitHub Actions
docker/build-push-action with cache-to/cache-from), the server build
step drops from ~10 minutes to ~30 seconds on cache hits.
Using as a library
Install directly from a tagged release of this repository. The &path:/ui-next
argument tells the package manager to use the ui-next/ subdirectory as the
package root:
# pnpm (recommended)
pnpm add "conductor-oss/conductor#<tag>&path:/ui-next"
# npm / yarn
npm install "conductor-oss/conductor#<tag>&path:/ui-next"
Or pin the version in package.json:
"conductor-ui": "conductor-oss/conductor#v1.0.0&path:/ui-next"
Replace <tag> / v1.0.0 with the release tag you want to consume
(e.g. v3.2.1). Available tags:
https://github.com/conductor-oss/conductor/releases
Import styles in your app entry point:
import "conductor-ui/styles.css"; // component styles
import "conductor-ui/global.css"; // global body/font styles (optional)
Extending with plugins
The plugin system lets you register additional routes, sidebar items, task forms, auth providers, and more without modifying the core package.
import { pluginRegistry, App } from "conductor-ui";
// Register a custom sidebar item
pluginRegistry.registerSidebarItem({
position: { target: "root", after: "definitionsSubMenu" },
item: {
id: "myFeature",
title: "My Feature",
icon: <MyIcon />,
linkTo: "/my-feature",
shortcuts: [],
hidden: false,
position: 350,
},
});
// Register a custom route
pluginRegistry.registerRoutes([
{
path: "/my-feature",
element: <MyFeaturePage />,
},
]);
// Render the app
function Root() {
return <App />;
}
Plugin extension points
| Extension | Method | Description |
|---|---|---|
| Routes | registerRoutes(routes) |
Add authenticated routes |
| Public routes | registerPublicRoutes(routes) |
Add unauthenticated routes |
| Sidebar items | registerSidebarItem(reg) |
Inject items into the sidebar |
| Task forms | registerTaskForm(reg) |
Custom forms for task types in the workflow editor |
| Task menu items | registerTaskMenuItem(reg) |
Add task types to the "Add Task" menu |
| Auth provider | registerAuthProvider(reg) |
Replace the auth implementation |
| Search provider | registerSearchProvider(reg) |
Add results to global search |
Sidebar item positioning
Sidebar items use numeric positions so plugins can inject between core items without collisions. The core OSS positions are exported for reference:
import { CORE_SIDEBAR_POSITIONS } from "conductor-ui";
// CORE_SIDEBAR_POSITIONS.ROOT:
// executionsSubMenu: 100
// runWorkflow: 200
// definitionsSubMenu:300
// helpMenu: 400
// swaggerItem: 500
pluginRegistry.registerSidebarItem({
position: { target: "root" },
item: {
id: "myItem",
position: 350, // between definitionsSubMenu (300) and helpMenu (400)
// ...
},
});
Project structure
src/
├── components/ # Shared UI components
│ └── Sidebar/ # Sidebar with plugin-injectable menu
├── pages/ # Route-level page components
├── plugins/ # Plugin registry and fetch utilities
├── shared/ # Auth state machine and context
├── theme/ # MUI theme provider
├── types/ # Shared TypeScript types
└── utils/ # Feature flags, constants, helpers
public/
├── context.js # Runtime config (gitignored, not bundled)
└── context.js.example
Peer dependencies
When consuming as a library, the following must be provided by the host app:
react^18react-dom^18react-router/react-router-dom^7@mui/material,@mui/icons-material,@mui/system,@mui/x-date-pickers@emotion/react,@emotion/styled