Files
OpenHands/package.json
T
Rohit Malhotraandopenhands 449d1fc9e5 feat: reuse mock-LLM E2E tests for Docker image validation (#992)
* feat: reuse mock-LLM E2E tests for Docker image validation

Add a Docker-specific Playwright config (playwright.mock-llm-docker.config.ts)
that runs the exact same test specs and helpers against the agent-canvas Docker
image instead of the npm build path (bin/agent-canvas.mjs + uvx).

Key changes:

- Split MOCK_LLM_BASE_URL into two constants in mock-llm-helpers.ts:
  - MOCK_LLM_BASE_URL: always host-local, used by tests for admin API
  - MOCK_LLM_AGENT_URL: env-overridable, used when configuring the LLM
    profile (the URL the agent-server uses for inference). Defaults to
    MOCK_LLM_BASE_URL for backward compatibility with the npm path.

- New playwright.mock-llm-docker.config.ts:
  - Starts the mock LLM server on the host (same as npm path)
  - Runs the Docker container with --network host (Linux CI)
  - Points to the same testDir (tests/e2e/mock-llm/) and specs
  - Separate output dirs to avoid collision with npm path results

- New CI workflow (.github/workflows/mock-llm-docker-e2e.yml):
  - Builds the Docker image from current code (or uses a pre-built image)
  - Runs the same specs against the container
  - Posts PR comment with differentiated report title

- render-mock-llm-report.mjs: accept --title flag for Docker vs npm reports
- npm run test:e2e:mock-llm:docker script added
- .gitignore updated for docker test output dirs

The npm path (test:e2e:mock-llm) is fully backward-compatible — no env var
override needed since MOCK_LLM_AGENT_URL defaults to MOCK_LLM_BASE_URL.

Co-authored-by: openhands <openhands@all-hands.dev>

* refactor: chain Docker E2E off existing Docker CI via workflow_run

Instead of rebuilding the Docker image in the E2E workflow (duplicating
~10-15 min of Docker build time), use workflow_run to trigger automatically
after the existing 'Docker' workflow completes successfully.

The workflow now:
- Triggers on: workflow_run (Docker completed) + workflow_dispatch (manual)
- Derives the image tag from the Docker build's commit SHA
  (ghcr.io/openhands/agent-canvas:sha-<short>-amd64)
- Pulls the already-built image from GHCR — no rebuild needed
- Checks out code at the same SHA as the Docker build
- Extracts PR number from workflow_run.pull_requests[] for comments

Removed: Docker build steps, Buildx setup, build-arg resolution.
All image building stays in docker.yml where it belongs.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: replace flaky 1s timeout with polling for Active badge assertion

The 'Active badge' check in step 2 used a hardcoded 1-second
waitForTimeout before reloading. On a loaded CI runner the profile
activation mutation may not persist in time, causing the reload to
show stale state. This is a pre-existing flake (identical test code
passed on the first push and failed on the second).

Replace with expect.poll() that retries the reload+check cycle with
increasing intervals (1s, 2s, 3s) up to 15 seconds total.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: add pull_request trigger for Docker E2E (workflow_run bootstrap)

workflow_run only fires when the workflow file exists on the default
branch (main). Since mock-llm-docker-e2e.yml is new and only on the
PR branch, GitHub doesn't recognize it as a workflow_run listener yet.

Add pull_request trigger (gated by 'e2e-tests' label, skip forks) that
polls the Docker workflow via gh API until it completes for the PR's
head SHA, then pulls the already-built image from GHCR and runs tests.

After merge, workflow_run takes over as the primary automatic trigger.
The pull_request path remains as a fallback for label-gated runs.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: add FILE_STORE, AUTOMATION_BASE_URL, AUTOMATION_WORKSPACE_BASE to Docker entrypoint

The Docker entrypoint was missing several environment variables that the npm
path (dev-with-automation.mjs) sets for the automation backend:

- FILE_STORE=local — without this, the automation backend may fall back to
  cloud storage (S3/GCS) which fails without credentials, causing tarball-
  based presets (preset/prompt, preset/plugin) to silently error
- LOCAL_STORAGE_PATH — where to store files on the local filesystem
- AUTOMATION_BASE_URL — publicly-reachable base URL for callback URLs
- AUTOMATION_WORKSPACE_BASE — where automation runs unpack tarballs

This explains the Docker E2E failure: the agent's curl to create an automation
via /api/automation/v1/preset/prompt returned an error (likely 500 from missing
storage config), but the mock LLM doesn't care about terminal output and
proceeded to return the scripted final reply. The test then found 0 automations.

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: exclude auth-modes spec from Docker E2E tests

The mock-llm-auth-modes.spec.ts tests npm-binary-specific --auth-required
behaviour (a second static-server instance on port 18301). The Docker image
doesn't provide this second server — it has its own auth handling. Exclude
the spec from the Docker test run via testIgnore.

Co-authored-by: openhands <openhands@all-hands.dev>

* feat: run auth-modes tests inside Docker via PUBLIC_MODE_PORT

Instead of excluding the auth-modes spec from the Docker E2E run or
spinning up a host-side static server with a duplicate build/ directory,
the Docker entrypoint now supports an optional PUBLIC_MODE_PORT env var.

When set, entrypoint.sh starts a second static-server instance from the
same baked-in frontend assets with --auth-required (no session key
injected). This tests the actual Docker image's auth gate behaviour —
not a host-side approximation.

The Playwright Docker config passes -e PUBLIC_MODE_PORT=18301 to the
container and exports MOCK_LLM_PUBLIC_MODE_URL so the auth-modes spec
can reach it. With --network host the port is accessible from the host.

Co-authored-by: openhands <openhands@all-hands.dev>

* address review feedback: drop unlabeled trigger, improve error messages, document env vars

- Drop 'unlabeled' from pull_request trigger types to avoid wasted
  workflow runs when any label is removed (the job-level if: condition
  would skip immediately anyway)
- Distinguish 'no Docker run found' vs 'didn't complete in time' in
  the polling loop's final error message
- Add comment explaining /api/automation/v1 probe returns 200 without
  auth so the readiness check won't spin for 180s
- Document FILE_STORE, LOCAL_STORAGE_PATH, AUTOMATION_BASE_URL, and
  AUTOMATION_WORKSPACE_BASE in the entrypoint header — these affect
  production deployments, not just E2E tests

Co-authored-by: openhands <openhands@all-hands.dev>

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-06-01 15:09:01 -04:00

230 lines
7.9 KiB
JSON

{
"name": "@openhands/agent-canvas",
"version": "1.0.0-alpha.10",
"description": "Agent Canvas UI for OpenHands - run AI coding agents with a visual interface",
"license": "MIT",
"private": false,
"type": "module",
"repository": {
"type": "git",
"url": "https://github.com/OpenHands/agent-canvas"
},
"homepage": "https://github.com/OpenHands/agent-canvas#readme",
"bugs": {
"url": "https://github.com/OpenHands/agent-canvas/issues"
},
"bin": {
"agent-canvas": "bin/agent-canvas.mjs"
},
"engines": {
"node": ">=22.12.0"
},
"dependencies": {
"@heroui/react": "2.8.10",
"@microlink/react-json-view": "1.31.20",
"@monaco-editor/react": "4.7.0",
"@openhands/extensions": "git+https://github.com/OpenHands/extensions.git#e14f740c59b4bfd7369d4bb6aea5eeb33dd05909",
"@openhands/typescript-client": "1.24.3",
"@react-router/node": "7.14.2",
"@react-router/serve": "7.14.2",
"@tailwindcss/vite": "4.2.4",
"@tanstack/react-query": "5.100.9",
"@types/shell-quote": "^1.7.5",
"@uidotdev/usehooks": "2.4.1",
"@xterm/addon-fit": "0.11.0",
"@xterm/xterm": "6.0.0",
"axios": "1.16.0",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"downshift": "9.3.2",
"framer-motion": "12.38.0",
"i18next": "26.0.8",
"i18next-browser-languagedetector": "8.2.1",
"i18next-http-backend": "4.0.0",
"isbot": "5.1.39",
"lucide-react": "1.14.0",
"monaco-editor": "0.55.1",
"posthog-js": "1.372.6",
"react": "19.2.5",
"react-dom": "19.2.5",
"react-hot-toast": "2.6.0",
"react-i18next": "17.0.6",
"react-icons": "5.6.0",
"react-markdown": "10.1.0",
"react-router": "7.14.2",
"react-syntax-highlighter": "16.1.1",
"rehype-raw": "7.0.0",
"rehype-sanitize": "6.0.0",
"remark-breaks": "4.0.0",
"remark-gfm": "4.0.1",
"shell-quote": "^1.8.3",
"sirv-cli": "3.0.1",
"socket.io-client": "4.8.3",
"tailwind-merge": "3.5.0",
"tailwind-scrollbar": "4.0.2",
"unist-util-visit": "5.1.0",
"uuid": "14.0.0",
"vite": "8.0.10",
"zustand": "5.0.12"
},
"scripts": {
"dev": "node --env-file-if-exists=.env scripts/dev-with-automation.mjs",
"dev:static": "node --env-file-if-exists=.env scripts/dev-static.mjs",
"dev:extra-backend": "node --env-file-if-exists=.env scripts/dev-extra-backend.mjs",
"dev:minimal": "node --env-file-if-exists=.env scripts/dev-safe.mjs",
"dev:frontend": "npm run make-i18n && cross-env VITE_MOCK_API=false react-router dev",
"dev:mock": "npm run make-i18n && cross-env VITE_MOCK_API=true react-router dev",
"build": "npm run build:app",
"build:mock": "npm run make-i18n && cross-env VITE_MOCK_API=true react-router build",
"start": "npx sirv-cli build/ --single",
"test": "npm run make-i18n && vitest run",
"test:e2e": "playwright test --pass-with-no-tests",
"test:e2e:live": "node --env-file-if-exists=.env tests/e2e/live/scripts/run-live-e2e.mjs",
"test:e2e:mock-llm": "playwright test --config=playwright.mock-llm.config.ts",
"test:e2e:mock-llm:docker": "playwright test --config=playwright.mock-llm-docker.config.ts",
"test:e2e:snapshots": "playwright test tests/e2e/snapshots --project=chromium --retries=0",
"test:e2e:snapshots:update": "playwright test tests/e2e/snapshots --project=chromium --update-snapshots",
"test:coverage": "npm run make-i18n && vitest run --coverage",
"dev_wsl": "VITE_WATCH_USE_POLLING=true vite",
"preview": "vite preview",
"make-i18n": "node scripts/make-i18n-translations.cjs",
"prelint": "npm run make-i18n",
"lint": "npm run typecheck && eslint src && prettier --check src/**/*.{ts,tsx}",
"lint:fix": "eslint src --fix && prettier --write src/**/*.{ts,tsx}",
"prepare": "husky",
"typecheck": "react-router typegen && tsc",
"typecheck:staged": "react-router typegen && npx tsc --noEmit --skipLibCheck",
"check-translation-completeness": "node scripts/check-translation-completeness.cjs",
"build:app": "npm run make-i18n && react-router build",
"build:lib": "npm run make-i18n && react-router typegen && cross-env BUILD_LIB=true VITE_APP_ENV=production vite build && tsc -p tsconfig.lib.json",
"build:docker": "node scripts/docker-build.mjs"
},
"lint-staged": {
"src/**/*.{ts,tsx,js}": [
"eslint --fix",
"prettier --write"
],
"src/**/*.{ts,tsx}": [
"bash -c 'npm run typecheck:staged'"
],
"src/**/*": [
"npm run check-translation-completeness"
]
},
"devDependencies": {
"@eslint/eslintrc": "3.3.1",
"@eslint/js": "9.39.4",
"@mswjs/socket.io-binding": "0.2.0",
"@playwright/test": "1.59.1",
"@react-router/dev": "7.14.2",
"@tailwindcss/typography": "0.5.19",
"@tanstack/eslint-plugin-query": "5.100.9",
"@testing-library/dom": "10.4.1",
"@testing-library/jest-dom": "6.9.1",
"@testing-library/react": "16.3.2",
"@testing-library/user-event": "14.6.1",
"@types/mdast": "4.0.4",
"@types/node": "25.6.0",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@types/react-syntax-highlighter": "15.5.13",
"@typescript-eslint/eslint-plugin": "8.59.2",
"@typescript-eslint/parser": "8.59.2",
"@vercel/react-router": "1.3.0",
"@vitest/coverage-v8": "4.1.5",
"cross-env": "10.1.0",
"eslint": "9.39.4",
"eslint-config-prettier": "10.1.8",
"eslint-import-resolver-typescript": "4.4.4",
"eslint-plugin-i18next": "6.1.4",
"eslint-plugin-import-x": "4.16.2",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-prettier": "5.5.5",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "7.1.1",
"eslint-plugin-unused-imports": "4.4.1",
"globals": "16.5.0",
"husky": "9.1.7",
"jsdom": "29.1.1",
"lint-staged": "16.4.0",
"msw": "2.14.2",
"postcss-prefix-selector": "2.1.1",
"prettier": "3.8.3",
"tailwindcss": "4.2.4",
"typescript": "6.0.3",
"vite-plugin-svgr": "5.2.0",
"vitest": "4.1.5"
},
"packageManager": "npm@10.5.0",
"volta": {
"node": "22.12.0"
},
"msw": {
"workerDirectory": [
"public"
]
},
"overrides": {
"dompurify": "3.3.2"
},
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist",
"bin",
"build",
"config",
"scripts",
"tools"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./browser": {
"types": "./dist/components/browser/index.d.ts",
"import": "./dist/components/browser/index.js",
"require": "./dist/components/browser/index.cjs"
},
"./conversation": {
"types": "./dist/components/conversation/index.d.ts",
"import": "./dist/components/conversation/index.js",
"require": "./dist/components/conversation/index.cjs"
},
"./files": {
"types": "./dist/components/files/index.d.ts",
"import": "./dist/components/files/index.js",
"require": "./dist/components/files/index.cjs"
},
"./settings": {
"types": "./dist/components/settings/index.d.ts",
"import": "./dist/components/settings/index.js",
"require": "./dist/components/settings/index.cjs"
},
"./sidebar": {
"types": "./dist/components/sidebar/index.d.ts",
"import": "./dist/components/sidebar/index.js",
"require": "./dist/components/sidebar/index.cjs"
},
"./terminal": {
"types": "./dist/components/terminal/index.d.ts",
"import": "./dist/components/terminal/index.js",
"require": "./dist/components/terminal/index.cjs"
},
"./i18n": {
"types": "./dist/i18n/index.d.ts",
"import": "./dist/i18n/index.js",
"require": "./dist/i18n/index.cjs"
},
"./package.json": "./package.json"
},
"peerDependencies": {
"react": "19.2.5",
"react-dom": "19.2.5",
"react-router": "7.14.2"
}
}