Don’t want to read all this?
Just drop a link to https://xedant.com/agents/test/install.md or to https://xedant.com/llms.txt into any AI chat (Claude, ChatGPT, etc.) and ask it to generate the config files and commands. It will read the docs, ask you a few questions about your setup, and hand you a ready-to-use configuration. Save time — let the model do the reading for you.
You can also reach me on Telegram — I’m always glad to help. And that’s not just politeness — I genuinely enjoy talking to like-minded people, especially if you love coding as much as I do.
Test Agent is a program that checks your website, app or internal system. It lives on your server and deploys as a single Docker container, with permanent data in the /apps/test volume. No separate database is needed: the built-in one works at once, and PostgreSQL connects optionally. Runs and visual-build captures are executed by the built-in runner right in the container — no separate computer with Chrome is needed for that. Chrome with the addon stays for hand captures, action recording and runs in a live window.
Minimum requirements
- A server or computer with Docker — Windows, Linux or macOS all work, and an inexpensive server is enough for permanent use.
- About 2 GB of memory and permanent storage for the data (a volume): without it, everything disappears the first time the container is recreated. At idle the product spends no CPU and hands back the memory it took, so there is no need to keep a big server “just in case”. Visual-build snapshots and baselines are stored in the database too, so plan the space with a margin.
- Optionally, a Chrome with our addon. It is not needed for scheduled runs and visual-build captures: pages are opened by the built-in runner in the container, and no separate computer has to be kept on. The addon is useful only if you want to capture screens by hand, record actions or watch a test run in a live window. Details are in the Built-in Runner section.
- Optionally, the address and key of your Xedant Agent: without it everything works except comment analysis, action generation and auto-fix (more on that below).
Docker Compose
Create a compose.yml file with this content:
services:
test-agent:
image: xedant/test-agent:min-latest
container_name: test-agent
ports:
- "3978:80"
volumes:
- test-agent-data:/apps/test
- test-agent-config:/project/apps/tests
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_URLS=http://+:80
- TEST_APP_DATA=/apps/test
- TEST_APP_PROJECTS_FOLDER=/project/apps/tests
# TEST_APP_BASE_PATH=/tests # serve from a first-level subfolder (see ../subfolder-hosting.md)
# Runtime memory posture overrides — the image ships the minimal idle floor (see the
# Dockerfile ENV block / dev/configuration.md); uncomment to opt a busy box back into
# Server GC (compose wins over the image):
# DOTNET_gcServer=1
restart: unless-stopped
sysctls:
fs.inotify.max_user_watches: "524288"
fs.inotify.max_user_instances: "512"
volumes:
test-agent-data:
test-agent-config:
Then run:
docker compose up -d
After startup the interface is available on port 3978: http://<server-address>:3978. The two sysctls lines raise the system limits for file-change tracking — without them, some edits in the projects folder may not be picked up on the fly.
This file declares two permanent volumes. The first is the data (/apps/test): the database, the license, the keys, the logs and the app-wide search index. The second is the projects folder (/project/apps/tests): the project’s rules, actions, tests and knowledge. Both survive container recreation, and it is the second volume that makes projects plain files: the folder can be put wholly under git (a version store for files) and moved to another server by simple copying.
Running without Compose
A single docker run command works too:
docker run -d \
--name test-agent \
-p 3978:80 \
-v test-agent-data:/apps/test \
-v test-agent-config:/project/apps/tests \
-e TEST_APP_DATA=/apps/test \
-e TEST_APP_PROJECTS_FOLDER=/project/apps/tests \
--sysctl fs.inotify.max_user_watches=524288 \
--sysctl fs.inotify.max_user_instances=512 \
--restart unless-stopped \
xedant/test-agent:min-latest
The remaining environment variables are conveniently passed through a file with the --env-file .env flag — that way they are easier to change without rewriting a long command. Inside the container the product also picks up the /project/.env file on its own (the TEST_APP_ENV_FILE variable points elsewhere), and it watches the file for changes — no restart is needed for new values.
Environment variables
Settings are passed through environment variables — you write them once into compose.yml and change them without touching the code. Almost all names start with TEST_APP_; the exceptions are the connection to the agent (AGENT_API_URL, AGENT_API_KEY) and sign-in through a shared proxy (SSO_PROXY_TOKEN).
| Variable | Purpose | Default |
|---|---|---|
TEST_APP_BRAND | brand and interface language: xedant (English) or pastukhov (Russian) | xedant |
TEST_APP_DATA | permanent data folder (inside the container — /apps/test) | /apps/test |
TEST_APP_SEARCH_INDEX_FOLDER | the folder holding the app-wide search index (⌘K); delete it and the index rebuilds itself | {TEST_APP_DATA}/search-index |
TEST_APP_IDLE_TRIM_MINUTES | how many idle minutes before the taken memory is returned; 0 — never return it | 15 |
TEST_APP_KEY | the secret key guarding everything machine-facing: the link with the Chrome addon and the built-in runner, external access for agents, the live-page address, the commands for the model and the visual machine addresses; without it these addresses answer “not configured”, with a wrong one — refused | — (not configured) |
AGENT_API_URL | the address of your Xedant Agent: the chat, comment analysis, action generation and auto-fix work through it | — (chat hidden) |
AGENT_API_KEY | the access key to your Xedant Agent | — |
TEST_APP_URL | the address of this server from which the model downloads snapshots and opens the live page, and from which public links and visual-build notifications are built; when unset, the snapshot link is not placed into prompts and the links in notifications become relative | — |
TEST_APP_PROJECTS_FOLDER | the folder holding projects with rules, actions, scripts and tests | /project/apps/tests |
TEST_APP_SCRIPTS_ENABLED | the master switch of script execution: the value 0 stops all runs at once | enabled |
TEST_APP_PYTHON | which Python interpreter to use: the environment for scripts is created from it | python3 |
TEST_APP_RUNNER_ENABLED | the built-in runner: 0 disables it entirely (details in Built-in Runner) | 1 |
TEST_APP_RUNNER_ON_DEMAND | 1 — no runner process at idle, it starts for a queued run and exits by itself; 0 — a resident runner, restarted after idle | 1 (in the image) |
TEST_APP_RUNNER_NAME | the built-in runner’s name in the runners list | Playwright (built-in) |
TEST_APP_RUNNER_HEADLESS | 1 — headless mode (the image ships only that browser build); 0 — headed, needs a full build | 1 |
TEST_APP_RUNNER_SANDBOX | browser isolation: 0 — off (needed to work in a container), 1 — on | 0 |
TEST_APP_RUNNER_BROWSER_IDLE_SEC | how many seconds without runs before closing the browser and returning memory; 0 — never close | 600 |
TEST_APP_DB | database: a PostgreSQL connection string; empty value — the built-in SQLite | SQLite (/apps/test/data.db) |
TEST_APP_BASE_PATH | serve from a first-level subfolder (for example, /tests) | — (domain root) |
TEST_APP_LOGIN | login of a pre-configured administrator; together with the password, creates the user at startup | — (the first to register) |
TEST_APP_PASSWORD | lowercase SHA-256 hash of the password, not the password itself | — |
TEST_APP_SSL | self-signed HTTPS of your own: auto — enable | — (HTTP only) |
TEST_APP_SSL_PORT | port for HTTPS (by default — the one following the HTTP port) | — |
TEST_APP_SSL_DOMAINS | comma-separated names the certificate is issued for | — |
TEST_APP_ENV_FILE | the variables file: the product reads it at startup and watches it for changes | /project/.env |
TEST_APP_ERROR_REPORTING | 0 — disable error reporting | — (no receiving address configured, nothing is sent) |
SSO_PROXY_TOKEN | sign-in through a shared proxy with other products on the same server | — (regular sign-in) |
TEST_APP_PASSWORD holds not the password but its hash — a “fingerprint” the password cannot be recovered from. You get it with printf '%s' 'yourpassword' | sha256sum and paste it into the variable; at sign-in the app hashes what you typed and compares with the stored value. The TEST_APP_KEY key works differently — it is stored in plain text deliberately: the interface shows it so you can copy the key into the addon settings.
That key has a limitation: it is one per installation and does not fit when access is needed by a build pipeline or an outside runner. For that, the interface issues long-lived API keys (Settings → API tokens): a key is shown once and never recovered again, at work it is passed as the Authorization: Bearer taj_… header and comes in two scopes — “read only” (readonly) and “read and write” (full). Any such key can be revoked instantly, and, importantly, none of them can approve a baseline. Details are in the API Keys section.
The built-in runner has its own variables — TEST_APP_RUNNER_ENABLED, TEST_APP_RUNNER_ON_DEMAND, TEST_APP_RUNNER_NAME, TEST_APP_RUNNER_HEADLESS, TEST_APP_RUNNER_SANDBOX and TEST_APP_RUNNER_BROWSER_IDLE_SEC: they switch the runner on and off, pick the mode of operation, set its name in the list and manage memory. You normally do not need to change them: runs work right after installation. Details are in the Built-in Runner section.
Where the data lives
Permanent data lives in the /apps/test volume and survives container recreation:
/apps/test/data.db— the database (when PostgreSQL is not used);/apps/test/auth/— the keys sign-in sessions are signed with;/apps/test/.xedant/— the license (with thepastukhovbrand the folder is called.pastukhov);/apps/test/logs/addon-errors.log— the Chrome addon error log, useful during investigations;/apps/test/keys/visual-creds.key— the encryption key for visual-check credentials: staging logins and passwords are stored in the database encrypted (AES-256-GCM encryption);/apps/test/scripts/…— the Python environments and the working directories of scripts: the code itself lives in the project folder, while everything it needs to work the product keeps here, in the data;/apps/test/search-index/— the app-wide search index: it survives a restart and rebuilds itself when the content is out of date;- the projects folder (by default
/project/apps/tests) — the project’s rules, actions, scripts, tests and knowledge.
The dynamic part — screen snapshots, runs, issues and schedules, as well as visual-build snapshots, baselines with their versions and published share links — lives in the database. Rules, actions and tests, on the other hand, are plain files in the projects folder, and that is an important advantage: the folder can be put wholly under git (a version store for files), its edit history is visible, and a project moves to another server by simple copying. Commits into git are made by a person — the server never creates them on its own and pushes nothing to external repositories.
First sign-in
Open the interface in a browser. While the database has no users, the registration form is open by itself — the first person to register becomes the administrator. If a login and password are set through TEST_APP_LOGIN and TEST_APP_PASSWORD, sign in with those (the variable stores the hash; at sign-in you enter the password itself). The sign-in session lives for 90 days, so you will not have to re-enter the password daily. When other products work alongside, sign-in can go through a shared proxy — it is configured with the SSO_PROXY_TOKEN variable.
After that the administrator creates the users personally: a login, a name and an initial password the person changes at first sign-in. The roles are: viewer (only looks), reviewer (delivers verdicts and manages the todos), member (queues runs and edits tests), admin (project settings and users) and owner (API keys and ownership handover). A separate flag appoints a design approver — the person who approves design-system changes. There are no email invitations: the login and password are handed over in person. A disabled account cannot sign in, but its trace in the audit log stays. Details are in the Users and Roles section.
What to do after startup
When the interface is open, a few steps remain before the first check:
- Set the access key — the
TEST_APP_KEYvariable. The Chrome addon and the built-in runner connect to the server with it, and the model gets access to the live page. - Create a project: a name, the site’s base address and a timezone. A project is a folder where rules, actions and tests will accumulate.
- Connect Xedant Agent — set
AGENT_API_URLandAGENT_API_KEY. After that the chat with the AI agent, comment analysis, action generation in words and auto-fix appear. - If you need hand captures, action recording or runs in a live window — install the Chrome addon with the one-click install described below. For scheduled runs and visual-build captures it is not required.
- Look at “Operations” and make sure the runners list has a “Built-in runner” row, and that Settings → UI testing shows a separate built-in-runner row: that means there is someone to execute runs. Details are in the Built-in Runner section.
- Set up visual checks: pick a preset, sensitivity thresholds and capture stabilization — otherwise comparing builds against the baseline will produce noise. Details are in the Visual Testing Settings section.
- Create environments and credentials if the tests reach a protected staging server: the staging address, logins and passwords are stored in the database encrypted and are substituted into actions only for the duration of a run. Details are in the Environments, Credentials and Variables section.
- Issue an API key for the build pipeline — Settings → API tokens. The pipeline queues checks and reads the result with it; the
viztool reads the key from theTEST_APP_KEYvariable. - Capture the first screen and write a comment in words: “the button is invisible on the dark background”. Send the comment to the agent — it will turn it into a rule, and from then on plain code finds this remark.
- If you plan to write your own checks, open the “Scripts” tab and make sure no execution-environment warning hangs at the top: it appears when the server has no Python or the environment failed to build. Details are in the Scripts and Extensibility section.
- Walk through the setup checklist — a short list of steps; it shows what is already ready.
Visual snapshots are taken by whichever runner claimed the run: the built-in one (the chromium engine) or Chrome with the addon (the chrome engine). These engines keep separate baselines, and the product never compares a snapshot from one engine against a baseline from the other — so an engine is picked per project and not mixed. Details are in the Built-in Runner and Baselines sections.
More about the first steps is in the Getting Started section.
Installing the Chrome addon
The addon is installed from the product itself. In the interface open Settings → UI Testing → “One-click install and pairing” and choose a folder on disk — the server writes a test-agent-addon folder there with the pairing already in place (the server address and the key); nothing needs to be configured by hand. Chrome then asks for three familiar steps:
- Open
chrome://extensions. - Turn on the Developer mode switch in the top right corner.
- Click “Load unpacked” and select the
test-agent-addonfolder the app created for you.
Let us be honest: the addon is private and is not in the Chrome Web Store. That is deliberate — checks should not go through store review, otherwise every new rule would take weeks to wait for. So after a server update you rewrite the folder from the same dialog (“Update in one click”) and press the “Update” button on the extension’s card at chrome://extensions. If the addon was installed another way, it can be paired by hand: enter the server address and the key on the addon’s settings page and press “Test connection”.
Installing through Xedant MultiAgent
If you have several servers, it is more convenient to install the product from Xedant MultiAgent: it raises the container itself, gives it a permanent address and HTTPS, and connects the database. For Test Agent the catalog has a ready template — “TestAgent” (short name “Tests”, port 6070) — installation comes down to one click, and MultiAgent fills in the variables itself. One honest caveat: the MultiAgent template has not yet been updated to the current variable names — its settings still use the old prefix instead of TEST_APP, and the linked agent is connected through a foreign variable instead of AGENT_API_URL and AGENT_API_KEY — so after installing through MultiAgent those values have to be fixed by hand. That is a discrepancy of the template itself, not an error in this instruction. More about MultiAgent itself is on the MultiAgent page.
License
The license key is activated in the license dialog in the interface, or passed through the AGENT_LICENSE variable at startup. There is no ready license in the image: a fresh container starts without one. Almost everything works without a license — projects, screens, comments, rules, actions, tests, runs, schedules, issues, auto-fix and the attention summary. The license is needed for exactly one thing: to send a message to the agent in the chat. The trial period is 30 days; the personal license costs