Assay
Visual verification for software projects.
Assay captures browser screenshots, computes pixel-level diffs against approved baselines, and surfaces results through a dashboard with a before/after slider. It runs Playwright tests in Docker and delivers structured results to a Grain workflow via an ingest API.
Features
- Visual diff — pixel-level comparison of screenshots against approved baselines; highlights regressions in red
- Before/after slider — side-by-side and overlay views in the dashboard
- Baseline management — approve or reject baseline updates per check; no silent regressions
- Screenshot gallery — browseable dashboard with thumbnails, check status, and diff overlays
- Scheduled runs — cron-based scheduling with configurable suites and targets
- Grain integration — ingest results directly into a Grain task packet for structured review and closure
- Browser SDK — TypeScript SDK for capturing screenshots and metadata from inside your app
- Docker runner — isolated Playwright execution with a prebuilt image
Requirements
- Python 3.11+
- Docker (for
assay run) - Node.js 18+ (for the TypeScript browser SDK)
Install
uv tool install assay-kit
Or with pip:
pip install assay-kit
Or from source:
git clone https://github.com/Diwata-Labs/Assay.git
cd Assay
pip install -e .
Quick start
1. Configure
Create assay.toml in your project root (all fields optional — defaults shown):
[project]
name = "my-project"
[runner]
docker_image = "assay-playwright:latest"
timeout_seconds = 300
[output]
directory = "./assay-output"
[serve]
host = "127.0.0.1"
port = 8000
[keys]
store = "~/.assay/keys.json"
[schedule]
store = "~/.assay/schedules.json"
2. Run a test
assay run --target https://your-app.example.com
Exit codes: 0 = pass, 3 = fail, 1 = inconclusive or error.
3. Start the ingest server
# Create an API key first
assay key create --name ci
# Start the server
assay serve
4. Open the dashboard
Navigate to http://localhost:8000 to see the screenshot gallery, check results, and diff views.
5. Schedule recurring runs
# Add a schedule (standard 5-field cron)
assay schedule add --cron "0 8 * * *" --suite smoke --target https://your-app.example.com
# List schedules
assay schedule list
# Start the scheduler (foreground; Ctrl+C to stop)
assay schedule run
# Remove a schedule
assay schedule remove <id>
Dashboard
The Assay dashboard (assay serve) provides:
- Screenshot gallery — thumbnails for every captured check, sorted by run
- Check results — pass/fail/inconclusive status per check with diff metadata
- Visual diff viewer — side-by-side before/after comparison with a draggable overlay slider
- Baseline management — approve a new baseline or reject and keep the existing one per check
Baselines are stored locally alongside your output directory. Approving a baseline makes it the reference for future runs.
Grain integration
Assay results can be ingested directly into a Grain task packet for structured review before closure.
The ingest API is protected by API key auth. Create a key before starting the server:
assay key create --name grain
assay key list
The ingest endpoint accepts structured payloads from the browser SDK or from assay run. Grain reads the result via:
grain verify submit --id TASK-0001
grain verify status --verification-id VERIFY-0001-001
Browser SDK
The browser SDK is for human/in-app capture from a real browser. It is not the agent path — its
capture()reads the live DOM and throws outside a browser. Agents should use the CLI,POST /ingest, or MCP (see Agent / programmatic access).
Install the TypeScript SDK in your web app:
npm install @diwata-labs/assay-sdk
import { AssaySDK } from "@diwata-labs/assay-sdk";
const sdk = new AssaySDK({
apiKey: "your-api-key",
endpoint: "http://localhost:8000",
});
// Capture a screenshot and metadata, POST to /ingest
await sdk.capture({ comment: "Optional human note" });
The SDK captures the current viewport, serializes metadata, and POSTs to the ingest endpoint. Results appear in the dashboard immediately.
Agent / programmatic access
Assay is fully familiar-drivable: an agent can run the entire verification loop headlessly,
with no browser and no dashboard. Agents use the CLI, POST /ingest, or the MCP server —
never the browser SDK (its capture() is DOM-locked and throws in Node).
The machine-readable contract — every tool, its JSON Schema, and the HTTP endpoints — lives at
src/assay/contracts/tool_manifest.json and is served at GET /mcp/manifest. Treat that as
the source of truth.
Three agent surfaces
-
CLI — the most complete surface. Every agent-relevant command takes
--format json:assay init --non-interactive --admin-email a@b.com --admin-password "$PW" --format json assay run --target https://app.example.com --task-id TASK-0001 --format json assay check --format json assay key create --name agent --format json assay baseline approve VERIFY-0001-001 --format json assay baseline list --format json
assay init --non-interactive(alias--yes) never prompts or reads a TTY — pass the admin email/password as flags or viaASSAY_ADMIN_EMAIL/ASSAY_ADMIN_PASSWORDfor zero-touch setup. -
POST /ingest— submit a captured screenshot payload directly. Authenticated with theX-Assay-Keyheader. The request body conforms to the JSON Schemasrc/assay/schemas/sdk_ingest.schema.json; the stored result conforms tosrc/assay/schemas/assay_payload.schema.json. -
MCP server —
GET /mcp/tools,GET /mcp/manifest,POST /mcp/call. All/mcp/*and/baselines*routes require a valid API key (X-Assay-Key). The tools cover the full loop:tool purpose run_verificationtrigger a real run; returns verification_id+ outcome + diffget_reportstructured report for a verification_idget_statuspoll a verification_id(pending/complete)list_runslist runs, filter by task_id/outcomelist_baselineslist set baselines approve_baseline/reject_baseline/set_baselinedrive the baseline workflow KEY=$(assay key create --name agent --format json | tail -1 | python -c 'import sys,json;print(json.load(sys.stdin)["key"])') curl -s localhost:8000/mcp/call -H "X-Assay-Key: $KEY" \ -d '{"tool":"run_verification","input":{"target":"https://app.example.com"}}' curl -s localhost:8000/mcp/call -H "X-Assay-Key: $KEY" \ -d '{"tool":"approve_baseline","input":{"verification_id":"VERIFY-0001-001"}}'
Baselines are also manageable headlessly over API-key HTTP: GET /baselines,
POST /baselines/set|approve|reject (body {"verification_id": "..."}) — no dashboard needed.
Docker runner
The assay run command requires a pre-built Docker image containing Playwright.
Build locally
cd runner
docker build -t assay-playwright:latest .
Use a custom image
[runner]
docker_image = "myregistry.example.com/assay-playwright:latest"
Development
# Install with dev dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Lint
ruff check src/ tests/
# Type check
mypy src/assay
License
AGPL-3.0-only. Commercial licensing for closed-source or hosted use: ss@diwata.domains
Metadata
Release files for assay-kit 0.3.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| assay_kit-0.3.3.tar.gz | 150.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| assay_kit-0.3.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 254.9 kB
Release files / assay_kit-0.3.3.tar.gz
| Download URL | assay_kit-0.3.3.tar.gz |
|---|---|
| Size | 150.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bdad6ac12d42c54adfb1bf727cb578cdbfff965947684923e9a4c6533e2bad80
|
|
BLAKE2b-256 checksum How to use checksums |
daba84384dbeb4deb009792ab32dd01d1da5cb656a42fd50c738d1ad66c272a2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / assay_kit-0.3.3-py3-none-any.whl
| Download URL | assay_kit-0.3.3-py3-none-any.whl |
|---|---|
| Size | 104.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a6a9f00732810cf520cab91361be44438b366c6b461b2177536afcfd0de2679f
|
|
BLAKE2b-256 checksum How to use checksums |
dc91f2d1d6b583017e0bd236667daa8735d2d75b61ed7b1658e8b447ed802636
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|