AgentOps ArgusUser manual
User manual

AgentOps Argus

Write, run and manage tests, from the desktop app to the web server.

This manual covers AgentOps Argus from start to finish: the desktop app where you write and run tests, the argus command line and MCP server, GitLab CI/CD, and argus-server with its web app, where teams plan cycles, triage runs and decide whether a release can ship.

It is based on the code on main (November 2026). Screenshots come from the real desktop app and web app, taken on Linux.

Where to start

You want to Read
Install the desktop app 2. Installing and updating
Write your first test 3. Projects, then 5. Writing tests
Test a desktop app or a mobile app 6.21 desktop-app, 13.5
Run tests in a pipeline 16. Running in GitLab CI/CD
Set up the server for a team 17. Setting up argus-server
Plan and run a test cycle 18. Using the web app
Decide whether to ship 20. Releases, gates and deployments
Look up a variable, file or key 22. Reference

What has been tested

Most of Argus has been tested on Linux, and many integrations only against fakes. Before you rely on the following, try them in your own setup:

  • Windows: the desktop app is built and type-checked for Windows, but most screens were tested on Linux only. That includes the installer and updater, the terminal, headed runs, Stop and the browser recorder.
  • Real systems: Duende and Entra sign-in, Jira, GitLab pipelines, QMetry import, Azure Blob storage and Key Vault, SendGrid and SES were tested against fakes. SQL Server has not been tested. Redis was tested against real Redis 7 and 8.

Chapters say where a feature is not built yet.

Reading it in Argus

The manual is built into both apps, so nobody has to find this folder:

  • Web app: the Help button, User manual in the account menu, or F1 open it at help/ on the server.
  • Desktop app: the book icon in the top bar, Read the user manual on the welcome screen, Open the user manual in the palette, or F1 open the app's own copy in your browser. It works offline.

The video tutorials are together on one page, Video tutorials (under Welcome in the contents), in order: the web app, then the desktop app. Each plays the next when it ends. Chapters that a video covers link to it. Videos are MP4 files in Cloudflare R2, played by the browser itself. The desktop app also downloads each video in the background the first time you open the manual (into <data>/manual/videos/), so after that they play offline too; the installer stays the same size. The list of videos is videos.json (bucket and base_url, the bucket's public address); to add or replace one, run python3 build/videos.py upload <id> <file.mp4> with CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN (Workers R2 Storage: Edit) set, then rebuild the help page.

Video tutorials

9 short narrated videos of the real apps with the Northwind Shop sample project, one after another on one page: the web app first, then the desktop app.

Videos

Video tutorials

  1. 1A tour of the web app
  2. 2Desktop 1: Getting started
  3. 3Desktop 2: Building a test on the canvas
  4. 4Desktop 3: Passing data between steps
  5. 5Desktop 4: Connections and variables
  6. 6Desktop 5: Running tests and reading results
  7. 7Desktop 6: The assistant
  8. 8Desktop 7: Files, terminal and git
  9. 9Desktop 8: Automations, CI and the server

Every tutorial in order. Each one plays the next when it ends, so you can watch them straight through.

1A tour of the web app 1:35

A tour of the web app1:35
Video A tour of the web app. Home, test cases, cycles and the player, runs and triage, defects, reports and release readiness. Watch it at https://videos.agentops.build/videos/web-tour-2689ff0394.mp4

2Desktop 1: Getting started 1:34

Desktop 1: Getting started1:34
Video Desktop 1: Getting started. The welcome screen, the workspace, the rail, the command palette, environments and the status bar. Watch it at https://videos.agentops.build/videos/desktop-01-getting-started-c7564c5dfe.mp4

3Desktop 2: Building a test on the canvas 1:28

Desktop 2: Building a test on the canvas1:28
Video Desktop 2: Building a test on the canvas. A new test, the step palette, an HTTP request and a SQL check, Split view and a first run. Watch it at https://videos.agentops.build/videos/desktop-02-canvas-b040913703.mp4

4Desktop 3: Passing data between steps 1:21

Desktop 3: Passing data between steps1:21
Video Desktop 3: Passing data between steps. Insert value, the reference lens, flows, payloads, transforms and data sets. Watch it at https://videos.agentops.build/videos/desktop-03-data-75a5d36f0f.mp4

5Desktop 4: Connections and variables 1:20

Desktop 4: Connections and variables1:20
Video Desktop 4: Connections and variables. Adding and testing connections, environments, and local variables kept in the keychain. Watch it at https://videos.agentops.build/videos/desktop-04-connections-93d26a67cd.mp4

6Desktop 5: Running tests and reading results 1:11

Desktop 5: Running tests and reading results1:11
Video Desktop 5: Running tests and reading results. Runs, live progress, the run view, evidence and filing defects. Watch it at https://videos.agentops.build/videos/desktop-05-running-bf282bb624.mp4

7Desktop 6: The assistant 0:59

Desktop 6: The assistant0:59
Video Desktop 6: The assistant. Asking the assistant to write, run and fix tests, and reviewing what it changed. Watch it at https://videos.agentops.build/videos/desktop-06-assistant-01993f7910.mp4

8Desktop 7: Files, terminal and git 0:53

Desktop 7: Files, terminal and git0:53
Video Desktop 7: Files, terminal and git. The Files screen, the terminal panel and the Repository view. Watch it at https://videos.agentops.build/videos/desktop-07-files-9497b1724c.mp4

9Desktop 8: Automations, CI and the server 1:06

Desktop 8: Automations, CI and the server1:06
Video Desktop 8: Automations, CI and the server. Schedules and webhooks, GitLab CI and sending runs to argus-server. Watch it at https://videos.agentops.build/videos/desktop-08-automations-c56039357b.mp4
Part I

Getting started

Install the desktop app, open a project and find your way around.

Chapter 1
01

Introduction

  1. 1.1The pieces
  2. 1.2How the pieces fit
  3. 1.3Words used in this manual
  4. 1.4What you need
  5. 1.5How this manual is organised

AgentOps Argus is a QA platform. You write tests as small YAML files, build and debug them on a visual canvas in the desktop app, run them on your own machine or headless in GitLab CI, and keep their results, test cases, cycles, requirements and defects in a web server that replaces QMetry. An AI assistant (Claude Code, Codex or Gemini CLI) works inside the desktop app and through an MCP server, so it can write, run and triage tests with you.

This manual covers every part of Argus as it is built today, from installing the desktop app to running the web server for a team.

The desktop app with a test open on the canvas
Figure 1.1 The desktop app with a test open on the canvas

1.1 The pieces

Piece What it is Who uses it
Desktop app (argus-desktop) The authoring and debugging tool: test explorer, flow canvas, YAML view, step inspector, local runs with live status, evidence, review of healed locators and screenshots, connections, assistant, file explorer, payload library, API explorer, repository view, terminal. QA engineers, test authors
CLI (argus) The same engine on the command line: validate, check, run, plan, report, connections, payloads, automate, triggers, ci gitlab, import, upgrade, review, visual, sync, gate, login, mcp, skill. Everyone, and every CI job
MCP server (argus mcp) Argus as tools and prompts for AI coding CLIs: list, write, validate, run and triage tests, drive a real browser, desktop apps and mobile emulators, read Jira, manage cycles. AI agents (and the desktop assistant)
Runner images (argus:core, argus:full) Docker images that run tests in GitLab CI. full adds Robot Framework, Playwright, browsers, Node.js, Python and database clients. CI pipelines
Web server (argus-server) The system of record: projects, test cases, requirements, releases, plans, cycles, executions, runs, defects, reports, performance results, Jira and GitLab integration, roles and sign-in. Serves the web app. Testers, leads, managers, admins
The report (report.html) One self-contained HTML file per run, readable offline, from verdict down to the failing step's evidence. Anyone a run is shared with

All of them run the same engine (argus-runner), so a test behaves the same on a laptop, in a pipeline and when the assistant runs it.

1.2 How the pieces fit

How the pieces of Argus fit together
Figure 1.2 How the pieces of Argus fit together
  • The tests repository is the source of truth for automation. Tests, flows, payloads, data files and argus.yaml live in git. The desktop app and the assistant edit those files; nothing about a test's steps is hidden in a database.
  • The server is the source of truth for test management. Manual test cases, cycles, executions, requirements, releases and defects live in argus-server. Automated results reach it from the CLI and from CI. argus sync sends each test's flow to the server so the web app can draw it.
  • Secrets never live in YAML. argus.yaml names variables (${QA2_PASSWORD}), OS keychain items (${keychain:…}) or Azure Key Vault secrets (${keyvault:…}). On a desktop they come from local variables; in CI from masked CI/CD variables. Every secret is masked as •••••• in every result, log, report and evidence file.

1.3 Words used in this manual

Term Meaning
Project folder / tests repository The folder that holds argus.yaml and the tests. One Argus project per repository is usual.
Test (test case) One YAML file with an id (e.g. TC-1042), a title and steps. kind: test is the default.
Flow A YAML file with kind: flow: reusable steps that tests call with a sub-flow step or run first with setup:.
Automation A YAML file with kind: automation: the same steps doing work (a sync, a digest) instead of checking it. It succeeds or fails rather than passing.
Step One action in a test: an HTTP call, a SQL query, a Kafka message, a Playwright spec, a branch, a loop … Each has an id and a type.
Environment A named set of variables and connections in argus.yaml (qa2, uat). Runs pick one with --env or the desktop's environment menu.
Connection Settings for something a step talks to (a database, a Redis cache, a Kafka cluster, an SSH host, an AKS cluster, a mail service), named in argus.yaml.
Payload A reusable request body or message in the project's payloads/ library, read as {{payload.<name>}}.
Run One execution of a set of tests. It writes argus-results/ (output.json, junit.xml, report.html, events, evidence).
Evidence What a step leaves behind to look at: request and response, rows, logs, screenshots, videos, traces, files.
Cycle In the web app: a planned round of testing (a sprint, a nightly) with executions assigned to testers.
Execution One test case in one cycle, on one environment and platform, with its result.
Release A version you ship, with a quality gate and go / no-go sign-off.

1.4 What you need

To … You need
Author and run tests on a workstation The desktop app (Windows installer, or built from source on Linux and macOS). The argus CLI comes with it.
Run playwright steps Node.js and @playwright/test in the project (npm install, npx playwright install chromium). The recorder writes the package.json for you.
Run robot steps Python and Robot Framework (pip install robotframework). Connections → Tools on this computer can install it.
Use the assistant Claude Code, Codex or Gemini CLI on PATH (or named in ARGUS_CLAUDE, ARGUS_CODEX, ARGUS_GEMINI).
Run in CI A GitLab project with a Docker runner (or a Windows shell runner) and the argus:core / argus:full images in a registry it can pull from.
Share results, manage cycles and defects An argus-server (PostgreSQL 15 or later), reachable over HTTPS.

1.5 How this manual is organised

The chapters follow the work: install, open a project, write tests, connect them to systems, run them, then share results and manage releases on the server. Each chapter is complete on its own and links to the others; the reference chapters at the end list every command, environment variable, file and keyboard shortcut.

Throughout the manual:

  • ⌘ means Ctrl on Windows and Linux, and Cmd on macOS. ⇧ is Shift, ⌥ is Alt / Option.
  • Paths such as argus-results/ are relative to the project folder.
  • Screenshots come from the real app, mostly in the dark theme; the light theme (⇧⌘L) looks the same in light colours.
Chapter 2
02

Installing and updating

  1. 2.1Windows: the one-line install
  2. 2.2Updates
  3. 2.3Publishing a release (for maintainers)
  4. 2.4Linux and macOS: building from source
  5. 2.5First start
  6. 2.6Signing in to argus-server
  7. 2.7Where things are kept
  8. 2.8Graphics and remote desktops

2.1 Windows: the one-line install

AgentOps Argus is installed the same way as AgentOps: one line in PowerShell, from the update server at agentops.build.

irm https://agentops.build/argus/install.ps1 | iex

The script downloads AgentOpsArgus-<version>-windows-x64-setup.exe into C:\InternalApps\AgentOpsArgus, checks it against its published .sha256 and opens the installer. The installer needs no administrator rights and installs to %LOCALAPPDATA%\Programs\AgentOpsArgus:

Component Notes
AgentOps Argus (required) argus-desktop.exe (the app), argus.exe (the CLI, which the assistant also runs as argus mcp), argus-server.exe (with the web app built in) and the Robot Framework listener. Start menu shortcuts and an uninstaller.
Add argus to PATH Puts the install folder on your user PATH, so argus works in any terminal. Untick it to keep PATH as it is.
Argus skill for AI coding CLIs Runs argus skill install --user, so Claude Code, Codex and Gemini CLI know how Argus works (see chapter 13). Refreshed on every update, removed by the uninstaller.
Desktop shortcut Off by default.

Install and uninstall ask you to close running copies from the install folder first.

Options for the script (environment variables, set before the irm line):

Variable Effect
ARGUS_INSTALL_SILENT=1 Install without the installer's pages.
ARGUS_INSTALL_VERSION=0.1.42 Install that version instead of the newest.
ARGUS_INSTALL_BASE_URL Another distribution point (an http(s) URL, a UNC path or a local folder).
ARGUS_INSTALL_ROOT Where the installer is downloaded to (default C:\InternalApps\AgentOpsArgus).
ARGUS_INSTALL_NO_LAUNCH=1 Download and check only, don't open the installer.
ARGUS_CA_CERTS Extra PEM CA files (;-separated) to trust for HTTPS.

irm | iex cannot pass parameters; to pass -Silent use the scriptblock form:

& ([scriptblock]::Create((irm https://agentops.build/argus/install.ps1))) -Silent

A portable ZIP with the same files is published next to the installer: extract it anywhere and run argus-desktop.exe.

2.2 Updates

The desktop app checks its update feed about 20 seconds after it starts and then every 6 hours. When a newer version is published it asks once per version:

  • Download and install downloads the installer to the app's data folder, checks its SHA-256, quits the app and runs the installer silently. Setup closes whatever still runs from the install folder, installs and starts the new version.
  • Later leaves an Update to x.y.z chip in the status bar.

The version chip at the right end of the status bar, or Check for updates in the command palette (⌘K), checks now.

The update dialog
Figure 2.1 The update dialog

On Linux and macOS the dialog opens the download page instead of installing.

Behind Netskope or another TLS-inspecting proxy

Every connection the app and argus make (sign-in, the Argus server, Jira, GitLab, updates, http steps, the API explorer) goes over SChannel on Windows, so it trusts what your browser and PowerShell trust (the Windows certificate store, where IT normally places Netskope's CA). It also trusts every PEM file it finds in the places AgentOps uses:

  1. ARGUS_CA_CERTS (;-separated files), NODE_EXTRA_CA_CERTS, SSL_CERT_FILE
  2. ~/.argus/certificates/*.pem
  3. ~/.agentops/certificates/netskope.pem (AgentOps' Install Certificate writes it)
  4. certificates/netskope.pem next to the app (the installer puts it there)

The install script retries a failed HTTPS download trusting the same files.

Proxies

Argus uses the proxy your browser uses. In order:

  1. localhost and 127.0.0.1 always go direct.
  2. ARGUS_PROXY: direct skips every proxy, system uses only the Windows settings (ignoring HTTPS_PROXY), a URL (http://host:port, optionally http://user:password@host:port) is the proxy to use.
  3. NO_PROXY hosts go direct; then HTTPS_PROXY / HTTP_PROXY / ALL_PROXY.
  4. On Windows, the system settings: the PAC script from Internet Options or WPAD auto-detect (evaluated like Edge does), else the manual proxy and its bypass list.

A proxy that wants a Windows (NTLM/Kerberos) sign-in is not supported; put a user and password in ARGUS_PROXY, or ask IT to let Argus through.

When a connection fails, the message says which part failed (DNS lookup, could not connect, TLS certificate not trusted, proxy failed, timed out), the system's own words for it, the route it took (direct or through proxy … and where that proxy came from), and what to do. For example:

TRACIntermodal sign-in: https://identity-qa.example.com/connect/deviceauthorization:
TLS certificate not trusted (Connection Failed: tls connection init failed: invalid
peer certificate: UnknownIssuer), going direct (no proxy set). This network may be
intercepting HTTPS (Netskope). Put its CA certificate in …

Where the feed comes from

Setting Effect
ARGUS_UPDATE_URL at build time Baked into the executable. The GitLab release job leaves it unset, so builds follow agentops.build.
Otherwise https://agentops.build/argus/latest.json
ARGUS_UPDATE_URL at run time (environment, local variable or desktop.json) Overrides both. off turns update checks off.
ARGUS_UPDATE_TRACE=1 Prints why a quiet check found nothing.

For an internal server the feed is the install script itself: the app reads its $DefaultVersion = '…' line (it never runs the script). Either way the installer must match its published SHA-256 before it runs.

2.3 Publishing a release (for maintainers)

Releases go to the update server (a Cloudflare Worker behind https://agentops.build/argus, in packaging/update-server/). On Windows, with the server's admin token:

$env:ARGUS_RELEASE_TOKEN = '<ADMIN_TOKEN>'
scripts\publish-argus.ps1 -Sign -Release -Notes 'Data sets on steps'   # patch bump, signed build, upload, release

Every installed app, and every irm https://agentops.build/argus/install.ps1 from then on, gets the released version. packaging/update-server/README.md covers deploying the server and each step of a release.

To publish to an internal download server instead:

$env:VERSION = '0.1.42'; .\scripts\build-windows.ps1 -Package     # dist\AgentOpsArgus-0.1.42-windows-x64-setup.exe + portable.zip
.\packaging\windows\publish-internal.ps1 -Version 0.1.42 -Destination \\10.84.96.75\agentops\downloads

publish-internal.ps1 copies the installer, ZIP and checksums to the served folder, then writes the install script with $DefaultVersion stamped. The script goes last, so clients only see a version once its installer is in place.

In GitLab, packaging/gitlab/windows-release.gitlab-ci.yml (included from the root .gitlab-ci.yml) builds on a Windows runner tagged windows (ARGUS_WINDOWS_RUNNER_TAG) and publishes to agentops.build: automatically for a vX.Y.Z tag, by hand from a default-branch pipeline, or with ARGUS_RELEASE=1. Set a masked ARGUS_RELEASE_TOKEN (the server's admin token) and ARGUS_SIGN=1 with the signing variables in the project's CI/CD variables. A tag pipeline uploads and releases; any other pipeline only uploads, unless ARGUS_PUBLISH_RELEASE=1. A release needs a signed installer. Without a tag or VERSION, a build's version is Cargo.toml's <major>.<minor> with the pipeline number as the patch.

build-windows.ps1 Result
(no options) argus-desktop.exe, argus.exe, argus-server.exe in target\release\
-Configuration Debug The same in target\debug\
-Package Installer and portable ZIP in dist\, each with a .sha256
-Sign The same, signed with Azure Trusted Signing (AZURE_CODESIGNING_*); or set WINDOWS_SIGN_THUMBPRINT / WINDOWS_SIGN_PFX without -Sign
-Sign -SkipBuild Sign and package the last build again

It needs Windows x64, Rust with the MSVC toolchain, the Visual Studio C++ Build Tools and NSIS 3.08 or later (winget install NSIS.NSIS). The packages are built with a static C runtime, so target machines need no Visual C++ redistributable.

2.4 Linux and macOS: building from source

The repository pins its Rust version in rust-toolchain.toml. On Linux, GPUI needs the X11/Wayland and font headers:

sudo apt-get install libxkbcommon-dev libxkbcommon-x11-dev libxcb1-dev \
  libfreetype-dev libfontconfig-dev libwayland-dev libvulkan-dev

cargo build --release -p argus-cli -p argus-desktop -p argus-server
./target/release/argus-desktop path/to/tests       # open a project folder at start

The desktop app and its UI crates are workspace members but not default ones, so plain cargo build / cargo test skip them; name them with -p or use --all.

2.5 First start

Start AgentOps Argus from the Start menu (or argus-desktop [folder]). The welcome screen lists your recent projects and other ways to start:

The welcome screen
Figure 2.2 The welcome screen
  • Open folder… (⌘O), or drop a folder on the window: open a folder of tests (the one with argus.yaml). See chapter 3.
  • Recent projects show their branch, last result and when they last ran. Pin a project to keep it on top (pinned ones survive Clear); remove one with its × button. Up to 12 unpinned projects are kept.
  • Try the sample suite opens examples/local, which runs with no network.
  • Import a Robot Framework project… and Import with the assistant… (see chapter 15).
  • Record a browser test… (see chapter 5).
  • API explorer (⌘4) and Repository (⌘5) work without a project.
  • Sign in… connects the app to an argus-server (below).

2.6 Signing in to argus-server

The desktop app works fully without a server. Signing in adds server runs in the run view, defects filed in Argus (and Jira through it), and cycles for the assistant's test-management tools.

  1. Open the account menu (the person icon at the bottom of the rail) or Sign in to argus-server… in the palette.
  2. Set the server address under Argus-server → Change. It is kept as the ARGUS_URL local variable, which wins over an ARGUS_URL environment variable.
  3. Click Sign in. When the server has single sign-on (Duende IdentityServer, Entra ID or another OpenID Connect provider) the app shows a code and opens the provider's device page; enter the code there.
Waiting for the device code
Figure 2.3 Waiting for the device code
Signed in
Figure 2.4 Signed in

The refresh token is kept in the OS keychain (service argus, account login:<server>), never in a file. Without single sign-on, paste a personal token from the web app's My tokens page; it is kept as the ARGUS_TOKEN local variable.

Set ARGUS_PROJECT for the folder (Connections → Variables → This project, or the palette's Set this folder's Argus project…) so runs and defects land in the right server project.

The CLI signs in the same way:

argus login --server https://argus.example.com    # shows a code for the provider's device page
argus logout --server https://argus.example.com

2.7 Where things are kept

What Where
Desktop settings (desktop.json) ~/.config/argus/desktop.json; on Windows %USERPROFILE%\.config\argus\desktop.json; $XDG_CONFIG_HOME/argus/desktop.json; or ARGUS_DESKTOP_SETTINGS
Desktop data (assistant chats, API explorer, local variables, flow-card state) The platform data folder (~/.local/share/argus on Linux, %APPDATA%-based on Windows), or ARGUS_DESKTOP_DATA
Secrets (connection passwords, local secret variables, sign-in tokens) The OS keychain, service argus (Windows Credential Manager, macOS Keychain, secret-tool on Linux)
Run results argus-results/ in the project folder (never commit it)
Run history argus-results/history/ (newest 30 runs)

2.8 Graphics and remote desktops

Without a GPU on Windows (the software adapter of many VMs) and in a Remote Desktop session, the window draws in low-power mode, as AgentOps does: at most 30 frames a second when a redraw is needed, no blurred shadows, no decorative animations or blinking carets. ARGUS_LOW_POWER=1 forces this mode on, ARGUS_LOW_POWER=0 turns the frame limit off. ARGUS_THEME=light starts in the light theme.

Chapter 3
03

Projects, environments and variables

  1. 3.1What a project is
  2. 3.2Opening a project in the desktop app
  3. 3.3argus.yaml
  4. 3.4Project settings screen
  5. 3.5Values and secrets
  6. 3.6Local variables (desktop)
  7. 3.7Masking
  8. 3.8Checking a project

3.1 What a project is

An Argus project is a folder (usually a git repository) with an argus.yaml at its root and test files below it. Everything Argus needs to run a test is in that folder or named by it.

orders-tests/
├── argus.yaml                 # environments, connections, MCP servers, masking, timeout, workers
├── tests/
│   ├── orders/
│   │   ├── create-order.yaml        # kind: test (the default)
│   │   └── create-order.layout.json # canvas positions (written by the desktop app)
│   └── nightly-digest.yaml          # kind: automation
├── flows/
│   └── login.yaml             # kind: flow, called with sub-flow or setup:
├── payloads/
│   └── orders/create.json     # {{payload.orders/create}}
├── data/
│   └── skus.csv               # data: data/skus.csv
├── e2e/checkout.spec.ts       # Playwright specs a playwright step runs
├── robot/…                    # Robot suites a robot step runs
├── package.json               # @playwright/test, when you have UI tests
└── argus-results/             # written by runs — add it to .gitignore

Rules Argus follows when it looks for tests:

  • Every .yaml / .yml file that parses as a test, flow or automation is one. One test per file; file names in kebab-case.
  • The nearest argus.yaml above a test applies to it.
  • Hidden folders (.robot, .git), node_modules and argus-results/ are skipped.
  • A folder holding a file named .argusignore is skipped, with everything in it. Migrated application copies get one, so their CI and compose YAML are never read as tests.
  • payloads/ beside argus.yaml is the payload library, not tests.

3.2 Opening a project in the desktop app

  • Open folder… on the welcome screen, ⌘O, or drop a folder on the window.
  • From a terminal: argus-desktop path/to/tests.
  • The folder menu (the folder name at the top left) switches projects and holds folder-wide commands: Set up GitLab CI/CD…, Migrate an application from another folder…, Upgrade this project to Argus in place….

Opening a folder parses it in the background; the previous project stays usable until the new one is ready. The app watches the folder, so tests that change on disk (a git pull, an edit in another editor, the assistant) reload by themselves; the explorer's Refresh button and Reload tests from disk in the palette force it.

3.3 argus.yaml

timeout: 45s                     # default step timeout (else 30s)
workers: 4                       # tests at once for argus run (auto = one per CPU; else 1)

environments:
  qa2:
    vars:                        # read in tests as {{env.<name>}}
      api: https://qa2.example.com/api
      web: https://qa2.example.com
      user: qa-bot
      password: "${QA2_PASSWORD}"          # a process / CI / local variable
    connections:                 # only this environment (wins over shared ones)
      orders-db: { type: postgres, url: "${QA2_PG_URL}" }
    mcp:
      servers:
        orders: { type: http, url: https://orders-mcp.qa2.example.com/mcp, headers: { Authorization: "Bearer ${ORDERS_MCP_TOKEN}" } }
  uat:
    vars:
      api: https://uat.example.com/api
      web: https://uat.example.com

connections:                     # shared by every environment
  batch-host: { type: ssh, host: batch01.qa, user: qa, key: "${BATCH_SSH_KEY}" }

mcp:
  config: mcp.json               # an mcpServers file (Claude Code / Cursor format)
  servers:                       # or inline, same format; these win
    servicebus: { command: npx, args: [-y, "@acme/servicebus-mcp@1.4.2"] }

mask:
  fields: [ssn, card_number]     # JSON keys masked anywhere in results
  patterns: ['\b\d{3}-\d{2}-\d{4}\b']
  allow: [QA_API_URL]            # ${NAME}s that are not secret
Key Meaning
timeout Default step timeout for every test under this file (see 9.3); none for no limit.
timeouts test and run time limits, limits per step type (sql: 10m) and per folder (folders: { tests/batch: { step: 5m } }). Environments can set timeout and timeouts too.
workers How many tests argus run runs at once (auto = one per CPU).
environments.<name>.vars Values tests read as {{env.<name>}}: base URLs, users, feature switches.
environments.<name>.connections Connections only that environment has. An environment's connection wins over a shared one of the same name.
environments.<name>.mcp MCP servers only that environment has (config and/or servers).
connections Connections every environment shares (chapter 8).
mcp MCP servers for mcp steps: a config file and/or inline servers.
mask Extra masking: JSON fields, regex patterns, and allow for ${NAME}s that are not secret.

Unknown keys are errors, so a typo is caught by argus validate instead of being ignored.

A test picks its environment with env: in the test, --env on the command line (which wins), or the desktop's environment menu in the top bar.

3.4 Project settings screen

The desktop's Project settings (the gear in the rail, ⌘7) edits argus.yaml as a form and as YAML, side by side or one at a time. Both views edit the same text: a change in the form appears in the YAML at once, and YAML you type refills the form as soon as it parses.

Project settings: form and YAML
Figure 3.1 Project settings: form and YAML

The form covers the default step timeout, workers, environments (add, rename, remove) with their variables and per-environment MCP file, the shared MCP file, and masking (fields, patterns and allowed names, one per line). Connections and inline MCP servers are listed there and edited on the Connections screen or in the YAML view.

Edits keep your comments, key order and formatting: only the keys you changed are rewritten. Nothing is saved until Save (⌘S); Revert reloads the file. If the file changed on disk while you were editing, a banner offers Load the file or Keep mine and save. Keys the runner would reject are flagged before you save.

A form edit shows in the YAML
Figure 3.2 A form edit shows in the YAML

3.5 Values and secrets

Values in argus.yaml can read secrets from four places. Write them as a whole value or inside a string:

Syntax Reads Typical use
${NAME} A process environment variable. On a desktop, a local variable (below); in GitLab, a masked CI/CD variable. Passwords, tokens, URLs that differ per machine
${keychain:<item>} The OS keychain, service argus (or ARGUS_KEYCHAIN_SERVICE). Where there is no keychain (a CI container), the variable ARGUS_SECRET_<ITEM>: upper case, other characters as _. ${keychain:qa2.orders-db.password} → ARGUS_SECRET_QA2_ORDERS_DB_PASSWORD. Secrets the Connections screen saves for you
${keyvault:<name>} Azure Key Vault (AZURE_KEYVAULT_URL or AZURE_KEYVAULT_NAME, with AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET; AZURE_AUTHORITY_HOST for other clouds). Team secrets kept in Key Vault
${switchyard:<item>} A secret Switchyard keeps in the OS keychain on this machine. Reusing Switchyard's saved passwords locally (never in CI)
Where secret values come from
Figure 3.3 Where secret values come from

A missing secret's error names every place it looked.

{{env.NAME}} without an environment

A test may read {{env.NAME}} for a name no argus.yaml environment defines. Argus then reads the variable NAME directly (a local variable on the desktop, a CI/CD variable in a pipeline), masked unless mask.allow lists it. This is how migrated applications get their settings and how a project with no environments still reads a password.

3.6 Local variables (desktop)

In a pipeline, ${NAME} comes from the job's CI/CD variables. On your workstation the same names are local variables, set on the Connections screen's Variables tab (or the palette's Set a local environment variable…).

Variables: This project and All projects
Figure 3.4 Variables: This project and All projects
  • Plain values are kept in local-env.json in the desktop's data folder; secret values in the OS keychain as local-env.<NAME>. Neither is ever in the repository.
  • Every run started from the app sees them as environment variables: ${NAME} in argus.yaml, process.env.NAME in Playwright, %{NAME} in Robot, scripts, {{env.NAME}}, and the assistant's argus mcp. The app's terminal gets them too.
  • They win over a variable of the same name the app was started with.
  • Secret ones are masked in results.
  • The tab starts with a callout listing the ${NAME}s argus.yaml reads that nothing sets yet; click one to set it.

This project or All projects

Each variable applies to either All projects or This project only (the open folder). In that folder its own value wins over the one for all projects; the overridden value is shown struck through. Other folders don't see it. A folder's secrets are keychain items local-env.<NAME>.<hash of the folder>.

This project overrides All projects
Figure 3.5 This project overrides All projects

ARGUS_PROJECT (the argus-server project key) and GITLAB_PROJECT_ID start as This project variables, since each tests repository has its own Argus project: use Set ARGUS_PROJECT for this folder on the tab, or the palette's Set this folder's Argus project….

The plain argus CLI outside the app reads only its shell's environment; set the same names there (or in CI) for command-line runs.

3.7 Masking

Secrets never reach output.json, events.ndjson, junit.xml, the report or text evidence. Argus replaces with ••••••:

  • every value argus.yaml reads through ${...} (environment variables, connections, MCP servers), and secret local variables;
  • literal values under a connection's password, key, token, secret, headers or env, and an inline kubeconfig;
  • each of those in its URL-encoded form too;
  • any user:pass@ in a URL;
  • JSON fields named password, secret, client_secret, api_key or private_key;
  • tokens an HTTP step's auth: signs in with;
  • whatever mask.fields and mask.patterns add.

Text evidence files up to 200 MB are masked (above 16 MB one line at a time); a larger one is left as it is, with a warning in the run's output. The report's Copy as cURL turns masked credential headers into shell variables such as ${AUTHORIZATION}, so a copied command never carries a secret.

3.8 Checking a project

argus validate tests/ --env qa2      # schema, references, and every environment/connection/MCP server named
argus check --env qa2                # reach each connection, MCP server and base URL
argus check --env qa2 --name orders-db

validate checks every test's schema, required fields per step type, durations, every {{steps.x}} reference and transform name, and that every environment, connection and MCP server a test names exists. check goes further and connects: SELECT 1 on databases, true over SSH, the cluster version for AKS, MCP initialize and tools/list, PING on Redis, and a GET on the environment's http(s) variables. Both exit 2 on a problem.

In the desktop app the breadcrumb bar shows Valid · 0 issues for the open test (or the count, with the problems listed), and the Connections screen's Test button runs the same checks as argus check.

Chapter 4
04

A tour of the desktop app

  1. 4.1Top bar
  2. 4.2The rail
  3. 4.3Explorer
  4. 4.4The command palette (⌘K)
  5. 4.5Side panel: Inspector and Assistant
  6. 4.6Status bar
  7. 4.7Terminal
  8. 4.8Files
  9. 4.9API explorer
  10. 4.10Repository
  11. 4.11Copying text
  12. 4.12Keyboard shortcuts at a glance
  13. 4.13Closing the window: Argus keeps running in the tray
The workspace
Figure 4.1 The workspace

The window has five parts:

  1. Top bar: the folder menu, the command palette search, the environment menu, the assistant button and the run buttons.
  2. Rail (left edge): one button per screen.
  3. Main area: the explorer and canvas, or the screen the rail picked.
  4. Side panel (right): the step Inspector and the Assistant.
  5. Status bar (bottom): what is running, the terminal, the server and the version.

4.1 Top bar

Control What it does
AgentOps Argus / folder name The folder menu: open another folder, recent projects, Set up GitLab CI/CD…, Migrate an application from another folder…, Migrate and convert to Argus flows…, Upgrade this project to Argus in place….
Open anything… (⌘K) The command palette: one search over tests, their steps, kept and server runs, connections and every command (4.4).
Environment menu (● qa2 ▾) Picks the environment local runs use. Its Local runs section sets the step timeout, the test time limit, Show the browser (headed runs) and Tests at once. Options that aren't the default show beside the name (qa2 · headed · 2m).
User manual (book icon, F1) Opens this manual in your browser. The app carries its own copy, so it works offline.
Assistant (⌘J) Opens or hides the assistant in the side panel.
Run locally (⌘R) Runs the open test (or folder) on this machine. While a run is live it becomes Stop.
Run on GitLab (⇧⌘R) Starts the tests repository's GitLab pipeline for the open test or folder and follows it (chapter 16).

Under the top bar, the breadcrumb bar shows where the open test lives (local › flows › TC-0001), its title, its validation state (Valid · 0 issues, or the problems) and the Author / Run view switch.

4.2 The rail

Button Screen Shortcut
Layers Explorer: tests and the canvas (authoring) ⌘1
Pulse Run view: results, history and evidence ⌘2
Plug Connections: connections, variables, Jira, tools on this computer ⌘3
Globe API explorer: HTTP, GraphQL, gRPC and WebSocket requests ⌘4
Branch Repository: commit graph, changes, branches ⌘5
Folder Files: the project tree and a code editor ⌘6
Eye Review: healed locators and new or changed screenshots waiting for a decision, with a count (9.13)
Gear Project settings: argus.yaml as a form and as YAML ⌘7
Package Payloads: the payload library ⌘8
Sun / moon (bottom) Toggle light and dark theme ⇧⌘L
Person (bottom) Account: argus-server address, sign in and out

4.3 Explorer

The explorer lists the project's tests, flows and automations as a folder tree, each with its last result.

  • Filter box: words match a file name, id, title or tag; tag:smoke keeps tests with that tag; status:fail keeps tests whose last result failed. Words combine: orders tag:smoke status:fail.
  • Collapse all / Expand all buttons beside the filter (handy with thousands of tests).
  • New test (file-plus icon) creates a test in the selected folder from a template; New automation (lightning icon) creates an automation (chapter 11).
  • Run N runs every test in the selected folder (⇧⌘↵). Folder runs leave automations out.
  • Refresh reloads the folder from disk.
  • Marks: auto for automations, a clock for anything with an enabled trigger.
  • Right-click a row: Move to…, Rename… (F2), Show in Files, Copy relative path, Delete….
  • Drag a test or folder onto a folder to move it (5.8).
  • Select a row and press Delete (or ⌘⌫) to delete it, after a confirmation that lists the tests that use it.

The explorer, the side panel and the evidence drawer are resizable: drag their edges. Sizes are saved. ⌥⌘B collapses or opens the side panel; its expand button widens it over the canvas.

4.4 The command palette (⌘K)

The palette searches everything the window can open: tests (by id or title), their steps, kept local runs and server runs, connections, and every command. Type a few letters and press Enter.

The command palette
Figure 4.2 The command palette

Some commands only live in the palette (or a menu), for example:

Command
Record a browser test (Chrome or Edge)… 5.9
Test the web app from the chat (ad-hoc UI test)… 13.5
Test a desktop app from the chat (computer use)…, Test a mobile app on an emulator from the chat… 13.5
Import a Robot Framework project…, Convert a Robot Framework file to Argus steps… chapter 15
Migrate an application…, Upgrade this project to Argus in place… chapter 15
Set up GitLab CI/CD… 16.1
Commit changed tests and open a merge request… 5.12
Re-run failed tests 9.6
Capture the screen as evidence…, Record a screen clip… 9.10
Collapse / Expand all flow cards on the canvas 5.6
Reset the canvas layout (auto layout)
Set a local environment variable…, Set this folder's Argus project… 3.6
Connect Jira…, Put a Jira issue in the assistant chat… chapter 19
Load runs from argus-server, Sign in / Sign out of argus-server 2.6
Check for updates 2.2

4.5 Side panel: Inspector and Assistant

The side panel has two tabs:

  • Inspector: with no step selected, the test's settings (id, title, tags, data, timeout, requirements, triggers); with a step selected, that step's settings in tabs (Config, Data, Behavior, …) and a composer made for its type. See 5.4.
  • Assistant: the AI chat (chapter 13).

The panel can be resized, expanded over the canvas, or collapsed to a strip.

4.6 Status bar

From left to right: the run state (LOCAL, or the GitLab pipeline being followed), the project's folder and git branch, runner idle or what is running, Terminal, the argus-server you are signed in to (or no argus-server), and the version chip (click it to check for updates).

4.7 Terminal

Ctrl+` opens a terminal under the workspace, full width. Shells start in the open project folder with your local variables set, so argus run, npx playwright test or robot behave as runs from the app do. On Windows the shell is PowerShell (pwsh.exe, else powershell.exe) through ConPTY.

Terminal panel
Figure 4.3 Terminal panel
Keys
Ctrl+` Open, focus or hide the terminal
Ctrl+Shift+` New terminal tab
Ctrl+Shift+C / Ctrl+Shift+V Copy / paste (⌘C / ⌘V on macOS; on Windows Ctrl+C with a selection copies and Ctrl+V pastes)
Right-click Copy the selection, or paste
exit Closes the tab

Drag the panel's top edge to resize it; the height is saved.

4.8 Files

The Files screen (⌘6) shows the project folder as a tree beside a code editor, like an IDE's project view. Use it for Playwright specs, Robot suites, Python, data files, payloads and anything else in the repository.

Files: tree and editor
Figure 4.4 Files: tree and editor

The tree

  • Click a file to open it in an editor tab. Right-click for New file, New folder, Rename (F2), Delete, Cut / Copy / Paste / Duplicate, Move to…, Open preview, Open in browser, Open with default app, Convert to Argus steps… (Robot files), Convert to Playwright TypeScript (pytest files), git history, blame and changes, and … with the assistant.
  • Pick several rows with Ctrl/Shift-click or Shift+arrows; copy (⌘C), cut (⌘X) and paste (⌘V) work with your system's file manager too. Pasting an image from the clipboard saves it as pasted image <date>.png.
  • The search box finds files by name anywhere under the folder (ext:, type: and size: filters work). In files (⇧⌘F) searches file contents, with globs to include or exclude and Replace all; Find in folder scopes it to one folder. Click a hit to open the file at the line.
  • Expand all opens up to 400 folders; Collapse all closes them.
  • Git changes mark the rows. The tree updates by itself when files change (a folder watcher, not polling).
  • Drag a file or folder onto a folder to move it; references in tests follow.

The editor

  • Highlighting for YAML, JSON, TypeScript/JavaScript, Python, SQL, shell, Robot Framework and more; line numbers, indent guides, folding.
  • Ctrl+F find, Ctrl+H (⌘⇧F on macOS) replace, Ctrl+G go to line, Alt+Z word wrap, Ctrl+S save, Ctrl+W close, Ctrl+Shift+T reopen.
  • Autocomplete (Ctrl+Space): Robot sections, settings, BuiltIn and library keywords and your project's own keywords; the Playwright API in TypeScript; Python keywords; Argus YAML keys, step types, {{steps.<id>}} references and transforms after |.
  • Format document (Shift+Alt+F, or the Format button): Prettier for TS/JS/JSON (from node_modules/.bin, then PATH; Argus's own JSON formatter when Prettier is missing), Ruff then Black for Python, Robocop format then Robotidy for Robot. Connections → Tools on this computer can install them.
  • Open as test opens an Argus test in the test editor.
  • CSV files, JSON lists and YAML lists under data/ have a Table view: edit cells (Tab / Shift+Tab, Enter moves down, Escape reverts), then save.
  • Previews: images (with Copy image), PDFs (rendered in the app), HTML and SVG (a Source / Preview switch). Text files over 5 MB open in a read-only big-file viewer (up to 1 GB), for logs.
  • Tab right-click menu: close others / left / right / saved / all, reopen, save, copy path, show in tree.

Folder view opens a two-pane explorer (sizes, dates, preview) on the same folder.

4.9 API explorer

The API explorer (⌘4) is a full API workbench (ported from Switchyard): projects, collections and environments of HTTP, GraphQL, gRPC and WebSocket requests, with scripts, tests, runs and import/export. Its data lives in the desktop's data folder, not in the tests repository. {{vault.name}} reads the OS keychain item name.

API explorer
Figure 4.5 API explorer
  • Its AI buttons ask the assistant panel beside it.
  • Requests the assistant writes in ```http blocks get an Open in the API explorer button.
  • An HTTP step's composer can copy a saved request in with From API explorer… (6.1).
  • Inside it, ⌘1–⌘8 switch the explorer's own pages; use the rail or ⌘K to leave.

4.10 Repository

The Repository screen (⌘5) is a git client for the open folder's repository: the commit graph, diffs, local changes and staging, branches, tags, stashes, remotes, git-flow, bisect, LFS, accounts (GitHub, GitLab, Azure DevOps) and pull requests, with its own assistant. Events in .gitignored paths are ignored, so a run writing argus-results/ doesn't make it busy.

Repository: commit graph
Figure 4.6 Repository: commit graph

4.11 Copying text

Text in the side panel, the run view, the evidence drawer, logs, the Robot keyword tree, the YAML view, the assistant's replies and the Connections screen can be selected with the mouse (drag, double-click a word, triple-click a line) and copied with ⌘C, or with right-click → Copy.

4.12 Keyboard shortcuts at a glance

Keys Action
F1 This manual, in your browser
⌘K Command palette
⌘O Open a folder
⌘1 … ⌘8 Explorer, Run view, Connections, API explorer, Repository, Files, Project settings, Payloads
⌘J / ⇧⌘J Toggle assistant / new conversation
⌥⌘B Collapse or open the side panel
⇧⌘L Toggle light / dark theme
⌘R Run the open test locally
⇧⌘↵ Run all tests in the selected folder
⇧⌘R Run on GitLab
⌘↵ Run only the selected step
⌘\ Cycle Canvas / Split / YAML
/ Step palette (add a step)
⌘Z / ⇧⌘Z (⌘Y) Undo / redo
⌘A, ⌘C, ⌘V, ⌘D Select all, copy, paste, duplicate steps
Delete / ⌘⌫ Delete the selected steps (canvas) or test (explorer)
⌥↑ / ⌥↓ Move the selected step up / down
⌘+ / ⌘- / ⌘0 / ⌘9 Zoom in / out / 100 % / fit
J / K Next / previous failure (run view)
Ctrl+` / Ctrl+Shift+` Terminal / new terminal
Esc Close a menu, clear the selection

The full list is in chapter 22.

4.13 Closing the window: Argus keeps running in the tray

Closing the window does not quit Argus. The window goes away and Argus keeps running behind its icon (the watcher's eye) in the tray, so the open project's schedules and intervals keep firing (11.4), runs in progress finish, and the assistant keeps working. The first time in a session a notification says AgentOps Argus is still running; click it to bring the window back.

The tray icon is in the Windows notification area (it may sit under the ^ arrow at first; drag it onto the taskbar to keep it in sight), the macOS menu bar, and on Linux any desktop that shows status icons (KDE and most others; GNOME needs the AppIndicator extension).

On the tray icon
Left click Open the window again
Right click → Open AgentOps Argus Open the window again
Right click → Pause all schedules Stop schedules and intervals from starting runs (ticked while paused). The same switch as Pause in a test's Triggers section
Right click → Quit AgentOps Argus Quit for real: schedules stop, and runs in progress are stopped with the app

Only one Argus runs at a time. Starting it again (Start menu, desktop shortcut, argus-desktop) while it sits in the tray shows the window it already has instead of starting a second copy that would fire the same schedules twice. argus-desktop <folder> opens that folder there; argus-desktop --quit quits the running Argus.

To make closing the window quit Argus as before, run Keep running in the tray when the window is closed from the palette (⌘K); run it again to turn it back on. It is kept as keep_running in desktop.json ("off"), and ARGUS_KEEP_RUNNING=0 turns it off for one start. With it off, every start is its own copy. The palette also has Pause or resume all schedules in this app and Quit AgentOps Argus.

Part II

Writing tests

Build tests on the canvas, choose the right steps, pass data between them and connect to your systems.

Chapter 5
05

Writing tests: the canvas, YAML and the inspector

  1. 5.1Anatomy of a test
  2. 5.2Creating a test
  3. 5.3The canvas
  4. 5.4The inspector
  5. 5.5The YAML view
  6. 5.6Flows, sub-flows and setup
  7. 5.7Selecting, copying and pasting steps
  8. 5.8Moving, renaming and deleting tests
  9. 5.9Recording a browser test
  10. 5.10What the assistant changed
  11. 5.11Validation as you type
  12. 5.12Committing and merge requests

A test is one YAML file. The desktop app gives you three ways to edit it, all on the same text: the canvas (cards you click and drag), the YAML view (the file itself) and the inspector (a form for the selected step). Every change in one shows in the others at once, and edits keep your comments and formatting.

5.1 Anatomy of a test

id: TC-1042                       # unique key: results, Jira links and argus-server use it
title: Approved order gets an invoice
tags: [smoke, orders]             # --tags smoke,!slow selects by them; `serial` = never in parallel
priority: high
requirements: [EBM-412]           # the stories this test proves
folder: orders                    # grouping in argus-server
env: qa2                          # default environment (--env wins)
timeout: 45s                      # default step timeout for this test
setup: { use: flows/login, with: { user: "{{env.user}}" } }   # a flow run first
data: data/orders.csv             # optional: run the whole test once per row

steps:
  - id: create
    type: http
    request:
      method: POST
      url: "{{env.api}}/orders"
      json: "{{payload.orders/create}}"
    expect: [ "status == 201", "body.id" ]

  - id: invoice
    type: sql
    connection: orders-db
    query: SELECT id, total FROM invoices WHERE order_id = :id
    params: { id: "{{steps.create.body.id}}" }
    retry: { until: "count == 1", every: 2s, max: 30s }

  - id: totals-match
    type: assert
    expect: [ "steps.invoice.row.total == steps.create.body.total" ]
Top-level field Meaning
id Required. Unique key such as TC-1042.
title Required. What the test proves.
kind test (default), flow or automation.
steps Required, at least one.
env Default environment.
tags Labels for selecting tests. serial keeps a test from running beside others.
folder, priority Grouping and priority shown in argus-server.
requirements Requirement keys the test covers ([EBM-412]), for coverage and Jira.
data A data file; the whole test runs once per row as TC-1042[1], TC-1042[2] …, reading {{row.<column>}}.
setup { use: flows/login, with: {...} }: a flow that runs first, as the step setup.
timeout Default timeout for each step of this test (2m, none).
timeouts test: the whole test's time limit; per step type, sql: 10m, robot: none (9.3).
triggers Schedules, intervals and webhooks (chapter 11).
input, on-error, output Automations only (chapter 11).

Every step has an id (unique in the whole test, nested steps included) and a type, plus these optional fields:

Step field Meaning
expect Assertions on the step's output (7.4).
retry { until: "status == 200", every: 2s, max: 30s } polls until the condition holds; { times: 3, every: 5s } re-runs while the step fails.
timeout How long the step may take (none: no limit). On loop, branch, poll-until and sub-flow steps: the limit of each step inside.
continue-on-failure true: a failure is recorded but later steps still run.
always true: runs even after an earlier step failed (teardown: clean up data, sign out).
vars Step-local values, read as {{vars.<name>}}.
data The step's own data set: it runs once per row (7.6).
tags, documentation Labels and prose.

Every step type and its fields are in chapter 6.

5.2 Creating a test

  • Explorer → New test (file-plus icon) creates TC-<next> in the selected folder from a template and opens it. The palette has New test in the selected folder.
  • New automation (lightning icon) does the same for an automation.
  • Record a browser test (5.9) writes a test from what you do in a browser.
  • Ask the assistant: "write a test that creates an order and checks the invoice" (chapter 13).
  • Write the YAML in any editor; the app picks the file up from disk.

5.3 The canvas

Canvas with data lines
Figure 5.1 Canvas with data lines

The canvas draws the test as a flow of cards, top to bottom. Each card shows the step's type badge (HTTP, SQL, SH, ASRT …), its id, a one-line summary (POST /orders, the query, the command) and its assertions. During and after a run, each card shows its status: running (with elapsed time and the current action), passed, failed, skipped.

Views. The switch above the canvas picks Canvas, Split (canvas and YAML side by side) or YAML. ⌘\ cycles them. In Split view, drag the divider to give either side more room; double-click it to reset. The width is saved.

Data lines. With Data lines on, dotted lines connect each step to the steps whose data it reads ({{steps.onHand.available}}), labelled with the field. It shows at a glance where each value comes from.

Navigating.

Drag on empty canvas, or scroll Pan
⌘+scroll, ⌘+ / ⌘- Zoom
⌘0 100 %
⌘9 or Fit Fit the whole flow
Drag a card Move it (positions are saved in <test>.layout.json beside the test)
auto layout Arrange the cards again (palette: Reset the canvas layout)
⌥↑ / ⌥↓ Move the selected step earlier / later in the test

Adding steps. Press / (or click the + between two cards) to open the step palette, then type to filter or click a chip. Steps are grouped:

Group Chips
CALL HTTP, MCP, SCRIPT
DATA SQL, SQL EXEC, KAFKA PRODUCE, KAFKA CONSUME, REDIS, TRANSFORM
REMOTE SSH, SFTP, KUBECTL
CONTROL BRANCH, LOOP, POLL, SUB-FLOW
CHECK ASSERT, MANUAL
UI ROBOT, PLAYWRIGHT, DESKTOP APP, MOBILE APP
PERFORMANCE API LOAD, SQL LOAD, PAGE SPEED
NOTIFY EMAIL

The new step starts from a working template with a fresh id (call, query, …) and is selected so you can fill it in.

Control flow is drawn as structure: a branch splits into then and else lanes, a loop is a container of its steps, poll-until wraps the step it polls, and sub-flow / setup cards fold their flow's steps (5.6).

5.4 The inspector

Select a card to edit it in the side panel's Inspector. With no step selected, the inspector shows the test's settings: id, title, tags, data, timeout, requirements and the Triggers section.

A step's settings are split into tabs:

Tab Contents
Config The step's own fields, as a composer built for its type: the HTTP request builder with Send, the SQL editor with Run query, the Kafka produce/consume composer with Try it, the Redis composer, the email composer with Send test, the load and page-speed composers, and plain fields for the rest.
Assertions The step's expect list: add, edit and remove checks; after a run each shows its actual value.
Data Where this step's values come from, every earlier step's result to pick from, the payload library, and the step's data set (chapter 7).
Behavior timeout, retry, continue-on-failure, always, tags, documentation.
HTTP step composer
Figure 5.2 HTTP step composer

Fields save to the YAML when they lose focus (Enter, or ⌘↵ in multi-line fields). Mappings are YAML ({ key: value }); fields that hold JSON (an HTTP json body, Kafka values, Redis set values, MCP args) are a JSON editor with a Valid / Not valid bar (click it to jump to the problem), Format and Compact, and Shift+Alt+F.

A step's connection field lists the connections of the right type. Every field that takes a value has {x} Insert value, and a lens under it shows where each {{...}} comes from and what it was on the last run (7.7).

Renaming a step. Every step's Config tab starts with its id, the name on its card (the HTTP composer shows it above the URL bar). Type a new one and press Enter: the test's references to the step follow ({{steps.<id>...}} in any field, expect checks, auth: { use: <id> }), and so does the card's place on the canvas. An id another step already has, or one with spaces or dots, is refused.

⌘Z undoes and ⇧⌘Z / ⌘Y redoes any change, from the canvas, the inspector or the YAML view.

5.5 The YAML view

The YAML view shows the file with line numbers that stay put while it scrolls sideways. Click Edit YAML to edit it in a code editor (no soft wrap, highlighting, line numbers). The canvas and inspector follow as soon as the text parses; problems show in the breadcrumb bar's validation chip.

Edit YAML
Figure 5.3 Edit YAML

5.6 Flows, sub-flows and setup

A flow is a reusable piece of a test: signing in, creating a customer, cleaning up. It is a file with kind: flow:

# flows/login.yaml
kind: flow
id: FLOW-LOGIN
title: Sign in and return a token
steps:
  - id: token
    type: http
    request:
      method: POST
      url: "{{env.api}}/auth/token"
      json: { user: "{{vars.user}}", password: "{{vars.password}}" }
    expect: [ "status == 200", "body.token" ]

Use it in a test as a step, or as the test's setup:

setup: { use: flows/login, with: { user: "{{env.user}}", password: "{{env.password}}" } }
steps:
  - id: login
    type: sub-flow
    use: flows/login                 # .yaml optional; from the argus.yaml folder or beside the test
    with: { user: "{{env.user}}", password: "{{env.password}}" }
  - id: me
    type: http
    request:
      url: "{{env.api}}/me"
      headers: { Authorization: "Bearer {{steps.login.token.body.token}}" }
  • The flow sees env, the data row and its with values as {{vars.*}}, not the caller's steps.
  • The caller reads the flow's steps as {{steps.<sub-flow id>.<inner id>…}}.
  • Flows may nest (not recursively).
  • A flow only runs through a test; argus run never runs it on its own.

On the canvas, sub-flow and setup cards start folded: they show "N steps", and while running the elapsed time and the inner step running. Click the chevron to open the card and see the flow's steps (read-only, live during a run, clickable to see each step's result in the drawer). The palette has Collapse / Expand all flow cards on the canvas. Which cards are open is kept on your machine, not in the repository.

Flow card folded and open
Figure 5.4 Flow card folded and open

5.7 Selecting, copying and pasting steps

Keys / action
⌘A Select all steps
Shift / ⌘-click Add or remove a card from the selection
Shift / ⌘-drag on empty canvas Selection box (a plain drag pans)
Drag a selected card Move the whole group
Shift+arrows Nudge the selection
⌘D Duplicate (new ids end in _copy)
Delete / ⌘⌫ Delete the selected steps
Esc Clear the selection
⌘C / ⌘V Copy / paste steps, also between tests

A bar at the bottom of the canvas shows how many steps are picked, with Copy, Duplicate, Auto layout, Delete and Clear. Right-click a card for Copy / Paste / Duplicate / Delete; right-click empty canvas to paste at the end.

Multi-select bar
Figure 5.5 Multi-select bar

Pasting into another test puts the steps after the selected step (or at the end), in one undoable change:

  • Ids that already exist get _2, _3 …, and references between the pasted steps ({{steps.<old>…}}, expect paths, auth: { use: … }) follow.
  • File references (use:, data:, specs, scripts, payload files) are rewritten for the new folder.
  • References to steps the target test doesn't have are listed in a toast, and those cards show red until you fix them.

The clipboard holds plain YAML (a list of steps), so you can also paste steps from a text editor, or copy them out into one.

Pasted into another test
Figure 5.6 Pasted into another test

5.8 Moving, renaming and deleting tests

Move a test, flow or folder by dragging it in the explorer onto a folder (onto a test: into its folder; below the rows: to the top), or right-click → Move to… and pick a folder or type a new one (flows/shared). The Files tree moves any file the same way.

Move to…
Figure 5.7 Move to…

References that would break are rewritten: use: flows, data: files and the files steps name (scripts, specs, suites, payloads) in the moved tests and in every test that reads a moved file. The canvas layout moves with its test, and the open test and editor tabs follow. Commands inside run: text, argus.yaml, CI paths and Robot/Playwright imports are not rewritten.

Rename a test, flow or folder with right-click → Rename… or F2 on the selected explorer row. The name turns into a text box; type the new name and press Enter (Esc keeps the old one). Typing a name without .yaml keeps the extension. The same references are rewritten as for a move, the canvas layout file is renamed with its test, and the open test and editor tabs follow. Renaming in the Files tree (F2) rewrites them too. Renaming is refused while a run is live, or when the folder already has that name.

Delete with right-click → Delete… or the Delete key on a selected explorer row. The confirmation lists the tests that use what you are deleting (a flow other tests call, a data file); the test's .layout.json goes with it. Deleting is refused while a run is live.

Delete warns about users
Figure 5.8 Delete warns about users

5.9 Recording a browser test

Record a browser test… (palette, or the welcome screen) opens Chrome or Edge on an address, in its own window and a throwaway profile. Use the application; each click, typing, checkbox, select and key is recorded as you go, shown with the Playwright locator it will use (also on hover, in the page).

Recording
Figure 5.9 Recording
  • Assert visible, Assert text and Assert value make your next click a check instead of an action. Visual check adds a screenshot checkpoint (snapshot(page, 'checkpoint 1')) that later runs compare with its baseline (see 9.13).
  • Each recorded element also keeps up to four backup locators and a fingerprint (its role, name, label, text, id, test id, classes). The spec calls heal(page, …) for it, so a run can still find the element after a UI change and propose the new locator for review.
  • Typing becomes one fill; navigations the page caused itself are not recorded; pop-ups follow the click that opened them.
  • Passwords are never written down: the spec reads ARGUS_PASSWORD, which the step maps to the environment's password var.
  • Save as new test writes a Playwright spec with one test.step per action (so the run view and report list each action as a step) and an Argus test with a playwright step. Add to open test appends the step to the open test.
  • Without a package.json naming @playwright/test, one is written at the tests folder's root; run npm install and npx playwright install chromium once.

ARGUS_BROWSER picks the browser program; otherwise Chrome, Edge, Chromium, then Playwright's own Chromium are looked for.

Saved as a test
Figure 5.10 Saved as a test

5.10 What the assistant changed

When the assistant edits the open test, a banner Changed by the assistant marks new and changed cards with Undo and Keep. Each assistant reply also lists the files it changed, with a diff and Undo per file or for the whole reply (chapter 13).

5.11 Validation as you type

The breadcrumb chip shows Valid · 0 issues or the number of problems; click it to list them. Cards with a problem show red. Validation covers the schema, required fields per type, durations, {{steps.x}} references (unknown steps, later steps), unknown transforms, :name SQL parameters with no value, missing payloads and unknown connections or MCP servers for the selected environment. argus validate runs the same checks on the command line.

5.12 Committing and merge requests

Commit changed tests and open a merge request… (palette, or the Repository screen for full git) puts the changed test files on a branch, pushes it and opens a GitLab merge request. It needs GITLAB_URL, GITLAB_TOKEN (api scope) and GITLAB_PROJECT_ID (else the origin remote's project path) as local variables. A branch other than the checked-out one is committed through a private index, so your checkout and other edits stay as they are. GITLAB_TARGET_BRANCH overrides the target (else the project's default branch).

Commit dialog
Figure 5.11 Commit dialog
Chapter 6
06

Step reference

  1. 6.1http
  2. 6.2mcp
  3. 6.3script
  4. 6.4sql
  5. 6.5kafka
  6. 6.6redis
  7. 6.7transform
  8. 6.8ssh
  9. 6.9sftp
  10. 6.10kubectl
  11. 6.11branch
  12. 6.12loop
  13. 6.13poll-until
  14. 6.14sub-flow
  15. 6.15assert
  16. 6.16manual
  17. 6.17playwright
  18. 6.18robot
  19. 6.19load and page-perf
  20. 6.20email
  21. 6.21desktop-app
  22. 6.22mobile-app

Argus has 23 step types. This chapter lists each one: what it does, its YAML, its output (what later steps read as {{steps.<id>.…}} and what its own expect reads as bare paths), and what the desktop's inspector offers for it.

Group Types
Call http, mcp, script
Data sql, kafka, redis, transform
Remote ssh, sftp, kubectl
Control branch, loop, poll-until, sub-flow
Check assert, manual
UI playwright, robot
Apps desktop-app, mobile-app
Performance load, page-perf
Notify email

Two rules hold for every type:

  • A step fails when it fails to run or any expect fails. An infrastructure error (an unreachable host, a refused sign-in, a missing program such as robot or npx) is reported separately from a test failure, and makes argus run exit 2 rather than 1.
  • Secret-looking values (credential headers, passwords, tokens, anything from ${...}) are masked in the step's output and evidence.

6.1 http

Calls an HTTP API.

- id: create
  type: http
  request:
    method: POST                 # default GET
    url: "{{env.api}}/orders"    # required
    headers: { X-Request-Id: "{{now}}" }
    query: { page: 1 }           # added to the URL, encoded
    json: { sku: A1, qty: 2 }    # or json: "{{payload.orders/create}}", or json: "@payloads/order.json"
    # body: "raw text"           # instead of json
    # form: { a: 1 }             # instead of json: application/x-www-form-urlencoded
    auth: { type: bearer, token: "{{env.API_TOKEN}}" }
  expect: [ "status == 201", "body.id" ]

Output: status, headers (lower-case names), body (parsed JSON, otherwise text), and auth when the request signs in.

Signing in: request.auth

Keep credentials in {{env.NAME}} (a local or CI variable), never literal.

auth: { type: bearer, token: "{{env.API_TOKEN}}" }
auth: { type: basic, username: ann, password: "{{env.API_PASSWORD}}" }
auth: { type: api-key, name: X-Api-Key, value: "{{env.API_KEY}}", in: header }   # or in: query
auth:                            # OAuth 2 token endpoint (Duende, Entra, Keycloak…)
  type: oauth2
  grant: client-credentials      # or password (add username, password)
  token-url: "{{env.idp}}/connect/token"
  client-id: argus
  client-secret: "{{env.CLIENT_SECRET}}"
  scope: orders.read
  params: { audience: orders }   # optional extra form fields
auth:                            # call a sign-in endpoint first
  type: login
  request: { method: POST, url: "{{env.api}}/auth/login", json: { user: "{{env.API_USER}}", password: "{{env.API_PASSWORD}}" } }
  token: body.access_token       # where the token is in its { status, headers, body }
  # cookie: SESSION              # instead of token: send the session cookie it sets
  header: Authorization          # default; scheme: Bearer (default; "" sends the token alone)
  expires-in: 30m                # optional; else the response's expires_in
auth: { use: login }             # sign in like step `login` did (reuses its token)

oauth2 and login tokens are kept for the rest of the test: a later step with the same auth, or auth: { use: <step id> }, sends the kept token without signing in again, until it expires or the API answers 401 (then it signs in once more and resends). cache: false signs in every time. The auth output says type, from (given, sign-in or kept), via, expires_in and renewed, never the token.

In the desktop

The HTTP step's Config tab is a request builder like the API explorer's: the step's id (its name on the canvas; renaming it updates the steps that read it), a method menu and URL bar with Send, and tabs for Params, Headers, Body, Auth and Vars.

HTTP composer: OAuth 2 sign-in
Figure 6.1 HTTP composer: OAuth 2 sign-in
  • Send runs just this step and shows the response (Body, Headers, Request, Copy as cURL), even in a test that has never run.
  • The Auth tab picks the type with chips. Keep it in the OS keychain turns a secret you typed into a local variable and writes {{env.NAME}} instead. A kept token shows on a line with Forget token.
  • The Body tab has a Payload library… list (7.8).
  • From API explorer… lists the API explorer's saved requests and copies one in: {{x}} becomes {{env.x}}, and secrets become {{env.<STEP>_<FIELD>}} for you to set as local variables.
The next step reuses the kept token
Figure 6.2 The next step reuses the kept token

6.2 mcp

Calls a tool on an MCP server defined in argus.yaml (mcp:) or an mcpServers file (8.6).

- id: order
  type: mcp
  server: orders
  tool: get_order
  args: { id: "{{steps.create.body.id}}" }
  expect: [ "result.status == 'shipped'" ]

Output: result (the tool's structuredContent, else its text parsed as JSON), text, is_error. A tool answering isError fails the step; a server that can't start or be reached is an infrastructure error. A stdio server is started on first use and shared by every later step in the run; its stderr during the step is kept as evidence (<server>.stderr.log, masked).


6.3 script

Runs a script in the test's folder.

- id: token
  type: script
  lang: python                   # sh (default), bash, python, node, pwsh
  run: |
    import json, os
    print(json.dumps({"token": os.environ["SEED"]}))
  env: { SEED: "{{env.seed}}" }
  # file: tools/make_token.py    # instead of run; args: [..] for both

Output: stdout parsed as JSON when it is an object or list (so {{steps.token.token}}), otherwise stdout, stderr and exit_code. A non-zero exit fails the step. Interpreters: python3 (ARGUS_PYTHON), node (ARGUS_NODE), bash, pwsh, sh. Scripts get at least 10 minutes unless a limit is set for them (step, test, folder, timeouts.script; none for no limit). Files written to $ARGUS_EVIDENCE_DIR become evidence (pytest --output "$ARGUS_EVIDENCE_DIR").


6.4 sql

Queries or changes a database: PostgreSQL, SQL Server, Oracle or SQLite, each through its own native driver (no ODBC, no MCP server).

- id: invoice
  type: sql
  connection: orders-db          # a postgres, sqlserver, oracle or sqlite connection
  query: SELECT id, total FROM invoices WHERE order_id = :id
  params: { id: "{{steps.create.body.id}}" }
  expect: [ "count == 1", "row.total > 0" ]

- id: cleanup
  type: sql
  connection: orders-db
  exec: |
    DELETE FROM invoices WHERE order_id = :id;
    DELETE FROM orders WHERE id = :id;
  params: { id: "{{steps.create.body.id}}" }
  always: true
query: Returns rows (a list of objects), count, columns (names, also when nothing matched) and row (the first row, or null).
exec: One or more statements; returns rows_affected. Oracle scripts split at ; and / lines like SQL*Plus (PL/SQL blocks kept whole), then commit.
params: Every :name placeholder needs a value; they are bound as typed parameters. Validation names any that are missing.

Oracle names unquoted columns in upper case (row.TOTAL), or alias them (total AS "total"). Oracle needs Instant Client at run time (in the argus:full image; elsewhere ORACLE_CLIENT_LIB or the connection's client_lib_dir). Stop and the step's timeout cancel a running statement on the server (Postgres cancel, Oracle break, SQL Server dropped connection, SQLite interrupt), and the drawer shows progress lines while it waits.

In the desktop

SQL composer with result
Figure 6.3 SQL composer with result
  • DATABASE: the connections of SQL types, each with a badge and Test, and buttons to create a PostgreSQL, SQL Server, Oracle or SQLite connection.
  • STATEMENT: a Query / Execute switch, starter statements per engine and a SQL editor; Run query or ⌘↵ runs the step alone.
  • PARAMETERS: a row per :name, flagging missing and unused ones.
  • RESULT: a grid of the rows, with chips that copy a reference ({{steps.invoice.row.total}}) or add a check (count == 1).

6.5 kafka

Produces or consumes Kafka messages. One action per step, on a kafka connection. Argus never joins a consumer group or commits offsets, so reading never takes messages away from the application's consumers.

- id: publish
  type: kafka
  connection: events
  produce:
    topic: orders.created
    key: "{{steps.create.body.id}}"          # partition = hash of the key, as the Java client
    value: { orderId: "{{steps.create.body.id}}", status: NEW }   # or "{{payload.events/order-created}}"
    headers: { type: OrderCreated }
    # partition: 2, timestamp: "2026-10-29T08:00:00Z", compression: gzip|snappy|lz4|zstd
    # format: json|text|base64|avro (+ schema: a registry subject, an id, a .avsc file or the schema)
    # messages: [ { key, value, headers }, ... ]   # several at once
  expect: [ "count == 1" ]

- id: shipped
  type: kafka
  connection: events
  consume:
    topic: orders.shipped
    from: -5m                     # earliest | latest | an offset | 2026-10-29 08:00 (UTC) | RFC 3339 | -15m / 2h ago
    until: "value.orderId == steps.create.body.id"   # stop at the first message that matches
    wait: 30s                     # listen this long; `end` = only what the topic holds now
    max: 10                       # at most this many messages (default 100)
    where: [ "headers.type == 'OrderShipped'", "value.total > 0" ]
    # key: ORD-1, headers: { type: X }, partition: 0, offset: 1234, last: 5, to: 2026-10-29 09:00
    # format / key-format: auto | json | text | avro | base64
  expect: [ "matched.value.status == 'SHIPPED'" ]

Produce output: count, partition, offset, offsets, timestamp, messages. Consume output: messages (each with partition, offset, timestamp, key, value (parsed JSON or decoded Avro), headers, schema_id), count, read (before filters), first, last, matched (the until message), stopped (max, until, end, wait or to), next_offsets, problems.

An until that never matches within wait fails the step as a timeout. To check that nothing arrives: wait: 10s and expect: [ "count == 0" ]. Avro and JSON Schema values are decoded through a Confluent schema registry (schema-registry on the connection); local schemas are registered under <topic>-value.

In the desktop

The Config tab starts with a Consume / Produce switch, Start from recipes (newest 10, last 5 minutes, from a date, wait for a match), topic chips read from the cluster, Try it (runs the step alone) and a preview of the last try.

Kafka consume composer
Figure 6.4 Kafka consume composer

6.6 redis

Reads and checks what an application cached, and prepares cache data for a test.

- id: cart
  type: redis
  connection: cache
  get: "cart:{{steps.login.body.user_id}}"
  expect: [ found, "ttl > 0", "value.items[0].sku == 'A1'" ]
Action Output
get: <key> { key, found, type, ttl, ttl_ms, length, value }. ttl is seconds left, -1 when it never expires, null when the key doesn't exist (a missing key doesn't fail the step: check found). value: a string (JSON text parsed; decode: json or text), a hash as an object (field: / fields: read part of it), a list or set as an array, a sorted set as [{member, score}], a stream as [{id, fields}], a RedisJSON document (path: $.lines[0]). Large collections stop at max (1000).
keys: "session:*" keys (sorted), count, truncated, through SCAN (never KEYS), over every master of a cluster; key-type: hash keeps one type; values: true reads each into items.
set: <key> + value as: string (default), hash, list, set, zset or json; ttl: 10m; field: sets one hash field. Collections are replaced in one transaction. → { key, set, ttl }
delete: <key> (or a list) Entries with *, ? or [ are patterns. → { deleted, keys }
command: "XREVRANGE events + - COUNT 10" Anything else. → { value }

db: 2 overrides the connection's database. An error reply (WRONGTYPE, NOPERM) fails the step; an unreachable server or refused sign-in is infrastructure.

Redis: cached JSON with TTL
Figure 6.5 Redis: cached JSON with TTL

The composer has a CACHE picker, a Get / Keys / Set / Delete / Command switch with recipes, Run, the RESULT as pretty JSON or a table with "use" and "check" chips, and an OPTIONS section.


6.7 transform

Reshapes earlier steps' data for later ones, without calling anything. The reshaped data shows on the canvas and in the report.

- id: openIds
  type: transform
  input: "{{steps.query.rows}}"
  pipe: [ "where(state, OPEN)", "pluck(id)" ]       # or one text: "where(state, OPEN) | pluck(id)"
  set:
    csv: "{{steps.query.rows | pluck(id) | join(',')}}"
  expect: [ "value.length >= 1" ]

Output: value (the input after pipe) and each set name: {{steps.openIds.value}}, {{steps.openIds.csv}}. The transforms are listed in 7.3.


6.8 ssh

Runs a command on a host through the system's OpenSSH client, so your agent, ~/.ssh/config, jump hosts and keys work as they do in a terminal.

- id: batch
  type: ssh
  connection: batch-host
  command: /opt/batch/run.sh --date {{now}}
  expect: [ "exit_code == 0", "stdout contains 'done'" ]

Output: stdout, stderr, exit_code, and json when stdout is JSON. A non-zero exit fails the step unless an expect checks exit_code. Host-key questions are always answered no; see 8.3 for host keys in CI.


6.9 sftp

Copies files to and from a host.

- id: upload
  type: sftp
  connection: batch-host
  put: { local: data/in.csv, remote: /inbox/in.csv }      # or a list
- id: download
  type: sftp
  connection: batch-host
  get: { remote: /outbox/out.csv }                        # without local: kept as evidence

Output: transferred (direction, local, remote and bytes per file). The report previews downloaded text files.


6.10 kubectl

Runs your own kubectl against an AKS or Kubernetes connection. One action per step:

- { id: pods, type: kubectl, connection: qa2-aks, get: pods, selector: app=orders }
- { id: logs, type: kubectl, connection: qa2-aks, logs: deployment/orders, tail: 200, since: 10m }
- { id: env, type: kubectl, connection: qa2-aks, exec: "printenv FEATURE_X", pod: orders-7d9f }
- { id: rolled, type: kubectl, connection: qa2-aks, rollout: deployment/orders, timeout: 5m }
- { id: any, type: kubectl, connection: qa2-aks, args: [get, configmap, orders, -o, json] }

get returns json, items and count; every action returns stdout, stderr and exit_code. namespace overrides the connection's; container picks one for logs and exec. rollout waits for rollout status within the step's timeout. kubectl (and az / kubelogin when the connection uses them) must be on PATH.


6.11 branch

Runs one list of steps or another.

- id: if-invoiced
  type: branch
  if: "steps.order.body.state == 'INVOICED'"
  then: [ { id: fetch-invoice, type: http, request: { url: "{{env.api}}/invoices/{{steps.order.body.id}}" } } ]
  else: [ { id: no-invoice, type: assert, expect: [ "steps.order.body.total == 0" ] } ]

Output: taken (then or else). The canvas and report draw then and else lanes; the lane not taken shows as not run.


6.12 loop

Runs its steps once per item of a list.

- id: each-sku
  type: loop
  over: "{{steps.catalog.body.skus}}"   # or a literal list
  as: sku                               # default item; also vars.index
  steps:
    - id: stock
      type: http
      request: { url: "{{env.api}}/stock/{{vars.sku}}" }
      expect: [ "status == 200" ]

Output: a list with one object per iteration, keyed by the inner step ids: {{steps.each-sku[0].stock.body}}. The canvas draws a container; the run view and report show one section per iteration and open on the one that failed.


6.13 poll-until

Repeats one step until its output matches.

- id: wait-shipped
  type: poll-until
  until: "body.state == 'SHIPPED'"     # against the inner step's output
  every: 5s                            # default 2s
  max: 2m                              # default 30s
  step: { id: status, type: http, request: { url: "{{env.api}}/orders/{{steps.create.body.id}}" } }

Output: the inner step's last output. For a single step, retry: does the same with less nesting; use poll-until when the polled step should show as its own card.


6.14 sub-flow

Runs a flow file (5.6).

- id: login
  type: sub-flow
  use: flows/login
  with: { user: "{{env.user}}", password: "{{env.password}}" }

Output: the flow's step outputs by id: {{steps.login.token.body.token}}.


6.15 assert

Checks values from earlier steps without calling anything.

- id: totals-match
  type: assert
  expect: [ "steps.invoice.rows[0].total == steps.create.body.total" ]

6.16 manual

A step a person does: the test stops there as pending until a tester records the result.

- id: verify-letter
  type: manual
  action: Open the PDF letter sent to the customer
  expected: The letter shows the new total and the order number

Both action and expected are required. Record the result in the web app's execution player, or resume on the command line (9.9).


6.17 playwright

Runs a Playwright spec (TypeScript/JavaScript, or Python with pytest).

- id: checkout
  type: playwright
  spec: e2e/checkout.spec.ts     # relative to the test file (or to cwd)
  cwd: ../web-app                # optional: where to run npx playwright test
  grep: "(^|\\s)pays with a card(\\s@\\S+)*$"   # optional: one test by title
  project: chromium              # optional; config: playwright.config.ts; args: [..]
  env: { ARGUS_PASSWORD: "{{env.password}}", BASE_URL: "{{env.web}}" }

Each test.step becomes a sub-step in the run view; screenshots, videos and traces become evidence. Output: status, passed, failed, flaky, tests (title, ok, error …) and values: what the spec hands back to later steps as argus:value annotations:

test.info().annotations.push({ type: 'argus:value', description: JSON.stringify({ orderNo }) });

read as {{steps.checkout.values.orderNo}}. The step's timeout is Playwright's test timeout. While it runs, the card shows the current action and elapsed time, and the drawer streams Playwright's output.

Python (pytest-playwright). A spec that is a .py file or pytest node id (or runner: pytest) runs python -m pytest (the nearest .venv / venv, else ARGUS_PYTHON; ARGUS_PYTEST overrides the command), with each test as a sub-step:

- id: login
  type: playwright
  cwd: ../web-tests
  spec: tests/test_login.py::TestLogin::test_ok
  screenshot: only-on-failure    # default; on | off
  video: retain-on-failure       # optional: on | off | retain-on-failure
  trace: retain-on-failure       # optional

grep becomes -k and project becomes --browser. A limit set for the step (its timeout:, the test's, its folder's, or timeouts.pytest / timeouts.playwright) stops the whole pytest run; the run-wide step timeout does not. For a per-test limit use pytest-timeout through args.

Headed runs. argus run --headed, ARGUS_HEADED=1 or the desktop's Show the browser runs specs with --headed; on Windows the browser is brought in front of the app.


6.18 robot

Runs one Robot Framework test.

- id: login-page
  type: robot
  suite: .robot/ui/login.robot   # default .
  test: Login page opens         # required: the Robot test name
  variables: { BASE_URL: "{{env.web}}" }   # --variable NAME:value
  env: { TEST_ENV: qa }          # environment for the suite (%{NAME})

Each keyword becomes a sub-step carrying Robot's whole keyword tree (nested keywords, FOR and WHILE iterations, IF/ELSE, TRY/EXCEPT, setup and teardown, with arguments, messages, status and time); log.html and screenshots are evidence. Output: status, passed, failed, tests.

Argus finds Robot on PATH, in Python's Scripts folders, or as python -m robot for each Python it finds (ARGUS_ROBOT overrides). If it is missing, the step says Robot Framework is not installed and the desktop offers Install Robot Framework. Headed runs pass ${HEADLESS} = False and ${HEADED} = True and (Robot Framework 7+) force Browser library and SeleniumLibrary browsers visible.

Robot keyword tree in the drawer
Figure 6.6 Robot keyword tree in the drawer

6.19 load and page-perf

load runs many virtual users through nested steps and measures latency, throughput and errors; page-perf measures how fast a page loads in a real browser. Both are described in chapter 12.

- id: orders-load
  type: load
  users: 50
  ramp-up: 30s
  duration: 2m
  steps:
    - { id: list, type: http, request: { url: "{{env.api}}/orders" }, expect: [ "status == 200" ] }
  expect: [ "latency.p95 < 500", "error_pct < 1" ]

- id: home-speed
  type: page-perf
  url: "{{env.web}}/dashboard"
  runs: 5
  expect: [ "lcp.p75 < 2500" ]

6.20 email

Sends a message through an smtp, sendgrid or ses connection, filled from earlier steps, with attachments.

- id: notify
  type: email
  connection: mail
  to: "{{steps.owners.rows | pluck(email)}}"   # text (commas), a list, or rows with `email`
  cc: qa-leads@example.com                     # also bcc, reply-to; from: wins over the connection's
  subject: "{{steps.query.count}} open orders"
  body: |                                      # text; html: for an HTML version
    Open: {{steps.query.rows | pluck(id) | join(', ')}}
  attach:
    - results                    # this test so far: report.html, output.json, evidence (results.zip)
    - report                     # report.html alone
    - exports/daily              # a file, or a folder (zipped as daily.zip)
    - { evidence: ui }           # an earlier step's evidence files
    - { name: open.csv, content: "{{steps.query.rows | format('{id},{total}') | join('\n')}}" }
    - { file: [ a.csv, b.csv ], zip: exports }
  zip: nightly                   # optional: every attachment in one nightly.zip
  # send: false                  # build the message and zip as evidence, don't send
  # max-size: 30MB               # attachments allowed in total (default 18MB)
  expect: [ "sent == true" ]

Without body or html the body is a summary of the test's steps so far. Argus's own files are masked before they are attached. Output: sent, provider, message_id, from, to, cc, bcc, subject, attachments, bytes, and zip (the path of the zip it made), so the next step can use it: { type: sftp, put: { local: "{{steps.notify.zip}}", remote: /inbox/r.zip } }.

ARGUS_EMAIL_REDIRECT=me@example.com sends every message to that one address instead (subject [test] …, the real recipients named at the top), which is what you want on a shared QA environment.

In the desktop

Email composer
Figure 6.7 Email composer

The Config tab shows the message as it would go out with the last run's data, lets you add and remove attachments (results, report, files, an earlier step's evidence), Zip all, and Send test, which sends it to you only (your address is remembered).


6.21 desktop-app

Drives a native desktop app (a Windows program, a macOS app, a Linux GTK/Qt app) on the machine that runs the test, through its accessibility tree: UI Automation on Windows, the Accessibility API on macOS, AT-SPI and XTest on Linux (X11). Elements are found by what they show, not by screen position, and every action waits for its element.

- id: add-invoice
  type: desktop-app
  app: Billing                # its name as the system shows it (or a window title, or a pid)
  launch: '"C:\Program Files\Billing\billing.exe" --env qa'   # started when not running
  close: true                 # default: close it when this step started it
  wait: 10s                   # how long each action waits for its element
  screenshots: end            # end (default: last action and a failure) | each | failure | none
  actions:
    - click: { name: "New invoice", role: button }
    - type: { into: { name: Customer }, text: "{{env.customer}}" }
    - set: { name: Amount, to: "42.50" }   # sets a field's value directly
    - press: ctrl+s
    - expect: { name: Status, value: Saved }
    - expect: { name: "Unsaved changes", gone: true, timeout: 3s }
    - read: { name: "Invoice number", as: invoice }
    - check: { id: sendCopy }               # ticks it only if it is not ticked
    - expand: Advanced
    - click: Export…
    - window: Export                       # act in the Export dialog from here on
    - click: Cancel                        # back in the main window once the dialog closes
    - click: Copy number
    - expect: { clipboard: "{{steps.add-invoice.values.invoice}}" }
    - screenshot: saved

Finding elements. A plain string matches an element's name, description or id. A mapping narrows it: name (or text), role (or class), id, desc, value, contains (part of the name or value, any case), index (the nth match, from 0), or x/y relative to the window. A state narrows it further: checked, expanded, selected, enabled or focused (true or false); an element without that state never matches.

id is the id the app's developers gave the element for testing: the AutomationId on Windows, the accessibility identifier on macOS, the accessible id on Linux. It stays the same when a label is reworded or translated, so tests the assistant saves use it whenever the element has one. Ask the app's developers to set it on the controls your tests use.

Actions. click, double_click, right_click, type (text into the focused field, or { text, into }), press (Enter, ctrl+shift+p), set (to), scroll (down, or { dy, dx } over an element), drag (from, to), wait (an element, or a duration), expect (an element, with value or gone), read (as: keeps its value, else its name), screenshot (a name), sleep, and action (do: an accessibility action such as expand or toggle). Any action takes timeout and optional: true.

  • check and uncheck set a check box, radio button or switch and confirm it took; they click only when it is not already that way, so a rerun never flips it back. expand and collapse do the same for tree items, combo boxes and expandable sections, through accessibility first so the pointer does not move.
  • select chooses a list or table row, a tab, a radio button or a combo option through accessibility (a click when it cannot), and does nothing when it is already selected. scroll_to scrolls an element into view without the mouse wheel.
  • window waits for a window of the app whose title holds the text (a dialog such as Save As) and acts in it from then on. When that window closes, the actions go back to the app's front window by themselves. window: { title: Export, gone: true } waits for it to close. expect: { window: Export } (with gone: true too) only checks.
  • clipboard: text puts text on the clipboard; expect: { clipboard: text } checks what a Copy put there; read: { clipboard: true, as: name } keeps it. Desktop apps only.
  • resize: 1280x800 (or { width, height, x, y }), move: 0,0 (or { x, y }), maximize, minimize, restore and focus change the app's window and confirm it took. When the app or the screen will not allow the size, the step says what it got instead.

Ready to act. Before a click, a type into a field, a set or a check, the action waits (up to its timeout) for the element to be enabled, so a button that the app turns on a moment later is clicked once it can be. A disabled element fails with "… is disabled" rather than a click that did nothing. set reads the field back and fails when the app kept another value (a number field that rounds, a mask that drops characters); verify: false skips that check.

launch runs in the test's folder, so a path like ../shop.py works.

Output: values (what read kept, so {{steps.add-invoice.values.invoice}}), passed, actions (each with ms, ok, error, screenshot), screenshots, app, pid. Screenshots are the step's evidence.

Electron and Chromium apps. Many desktop apps are a web page in a window (Teams, Slack, VS Code and most in-house Electron tools). Their accessibility tree is thin and slow, but started with a debugging port they can be driven like a web page with Playwright. cdp: true (or a port number) adds the port switch to launch and keeps the app open after the step; the output's cdp is the address a playwright step connects to:

- id: app
  type: desktop-app
  app: Billing
  launch: '"C:\Program Files\Billing\Billing.exe"'
  cdp: true                    # needs launch: the app must start fresh
  actions: []
- id: invoice
  type: playwright
  spec: billing.spec.ts        # import { appTest as test } from './argus-playwright'
  cdp: "{{steps.app.cdp}}"
- id: close-app
  type: desktop-app
  app: Billing
  close: true
  actions: []

In the spec, appTest's page is the app's window; Playwright disconnects at the end and leaves the app running. Native parts (menus, file dialogs, other windows) stay with desktop-app steps. If the app is already running the step stops and says to close it first, since only a fresh start opens the port. The assistant writes all three steps when it drove the app with computer_launch_app and cdp: true.

A desktop-app step's screenshot in the evidence drawer
Figure 6.8 A desktop-app step's screenshot in the evidence drawer

The easiest way to write one is to let the assistant drive the app and save what it did (13.5). The step palette has DESKTOP APP under UI; the card's badge is APP.


6.22 mobile-app

Drives an app on an Android emulator or device (through adb) or an iOS simulator (on a Mac, through xcrun simctl and idb), with the same actions as desktop-app plus tap, long_press, swipe (up/down/left/right, { from, to }, or { until: <element>, direction, max }), back, home, launch, stop and open (a URL or deep link).

- id: sign-in
  type: mobile-app
  platform: android           # android | ios
  app: com.example.shop       # package, package/.Activity, or iOS bundle id; started fresh
  device: emulator-5554       # optional: default the only one running
  install: build/shop.apk     # optional: installed first (relative to the test)
  reset: true                 # optional: clear the app's data first
  actions:
    - type: { into: { id: email }, text: "{{env.user}}" }
    - type: { into: { id: password }, text: "{{env.password}}" }
    - tap: Sign in
    - expect: { contains: Welcome }
    - swipe: { until: { text: Orders }, direction: up }
    - tap: Orders
    - read: { id: order_count, as: orders }
    - screenshot: orders
  expect: [ "values.orders != '0'" ]

id matches an Android resource id with or without its package (email matches com.example.shop:id/email). Points are Android pixels or iOS points. restart: false keeps the app where it is, launch: false leaves it unstarted, close: true stops it afterwards.

Output: values, passed, actions, screenshots, device, platform, app.

What it needs. The emulator or simulator must be running on the machine that runs the test (start one in Android Studio's Device Manager, or with emulator -avd <name>). Argus finds adb through ARGUS_ADB, then ANDROID_HOME / ANDROID_SDK_ROOT, then the SDK's default folder (%LOCALAPPDATA%\Android\Sdk on Windows), then PATH. For iOS install idb (brew install idb-companion and pip install fb-idb); ARGUS_IDB points at it if it isn't on PATH. Android typing is ASCII only.

The step palette has MOBILE APP under UI; the card's badge is MOB.

Chapter 7
07

Passing data: expressions, transforms, data sets and payloads

  1. 7.1Expressions
  2. 7.2Where data comes from, on screen
  3. 7.3Transforms
  4. 7.4Assertions
  5. 7.5Retries
  6. 7.6Data sets
  7. 7.7Insert value and the reference lens
  8. 7.8The payload library

Steps hand data to each other through {{...}} expressions. This chapter covers what an expression can read, how to reshape a value on the way, how assertions work, how to run a step or a test once per row of data, and the project's payload library. It ends with the desktop tools that write expressions for you.

7.1 Expressions

{{...}} can appear in any string field of a step.

Passing data between steps
Figure 7.1 Passing data between steps
Root Reads
env.<name> The environment's vars in argus.yaml; if no environment defines it, the process / local / CI variable <name>.
steps.<id>.<path> Output of an earlier step: {{steps.create.body.id}}, {{steps.q.rows[0].total}}, {{steps.login.token.body.token}} (a sub-flow's inner step).
row.<column> The test's current data row (the test has data:).
data.<column> The step's current data row (the step, or a step it is nested in, has data:).
vars.<name> Step vars, a loop's item (as:, default item) and index, a flow's with.
payload.<name> A payload from the project's library: {{payload.orders/create}}.
input.*, trigger.*, error.* Automations (chapter 11).
load.user, load.iteration, load.user_iteration Inside a load step (chapter 12).
now, now - 30d, now + 2h RFC 3339 timestamps (units d, h, m, s).
  • Paths use dots and [n]; .length is the size of a list, string or object.
  • Arithmetic and comparisons work inside: {{steps.count.rows[0].n + 1}}.
  • A value that is exactly one expression keeps its JSON type: json: { ids: "{{steps.q.rows}}" } sends a list, not text. Otherwise values are spliced in as text ("Order {{steps.create.body.id}} created").
  • A reference to an unknown step or a later step is a validation error.

7.2 Where data comes from, on screen

With Data lines on, the canvas connects each step to the steps it reads from. The inspector's Data tab lists them in Where this step's values come from, and every field shows a lens under it (7.7).

7.3 Transforms

Add transforms after a | to fit one step's result to what the next needs. They apply left to right:

url: "{{env.api}}/orders?ids={{steps.query.rows | where(state, OPEN) | pluck(id) | join(',')}}"
json: { lines: "{{steps.order.body.lines | pick(sku, qty)}}" }
body: "{{steps.user.body | format('{first} {last}')}}"

Arguments are bare words (field names), quoted text or numbers; quote text that holds a comma, bracket or space (join(', ')). A missing value is null to transforms, so | default(0) fills it.

Transform Gives
first, last The first / last item of a list
at(index) The item at a position (0 is the first, -1 the last)
count How many items, characters or keys
get(path) A field inside the value: get(customer.id)
pluck(path) Each item's field, as a list: pluck(id)
pick(field, …) Keep only these fields (of an object, or of each item)
omit(field, …) Drop these fields
rename(from, to) Rename a field
where(path, [op,] value) Items whose field matches: where(status, OPEN), where(total, >, 100)
find(path, [op,] value) The first item whose field matches
sort([path]), reverse, unique, flatten List order and shape
slice(start, [end]) Part of a list or text
join([sep]), split([sep]) List ↔︎ text (default ,)
format(template) Text from a template: format('{first} {last}'); on a list, one per item
object(key, [value]) A list of rows as an object keyed by a field
keys, values An object's field names / values
sum([path]), min([path]), max([path]) Totals of numbers
number, text, json, parse Conversions (json writes JSON text, parse reads it)
upper, lower, trim, replace(from, to) Text
match(pattern, [group]) The part of the text a regular expression finds
default(value) This value when there is none
base64, urlencode Encodings

Unknown transforms and missing arguments are validation errors.

A transform step (6.7) does the same as a step of its own, so the reshaped value shows on the canvas and in the report: { id: openIds, type: transform, input: "{{steps.query.rows}}", pipe: [ "where(state, OPEN)", "pluck(id)" ] } gives {{steps.openIds.value}}.

7.4 Assertions

expect (and retry.until, branch.if, poll-until.until, a consume's until and where) take assertion expressions:

expect:
  - "status == 201"
  - "body.items.length >= 1"
  - "body.type == 'UNKNOWN'"
  - "body.id"                                         # a bare path must be truthy
  - "status == 200 && (body.state == 'OPEN' || body.retries < 3)"
  - "body.ids contains 42"
  - "body.ref matches '^ORD-[0-9]+$'"
  - "steps.invoice.row.total == body.total"
  • Bare paths read the step's own output; steps.<id>.<path> reads earlier steps (in an assert step that is all there is).
  • Operators: == != > >= < <=, contains (substring, list item, object key), matches '<regex>', && / and, || / or, ! / not, parentheses, + - * / %.
  • Strings in single or double quotes.
  • A number equals the same number written as text, and true / false equal the words, so CSV cells compare naturally (status == data.expected).
  • Each assertion records the actual value it compared (for && / ||, each comparison's left side). The run view's Assertions tab and the report show expected against actual.

7.5 Retries

retry: { until: "status == 200", every: 2s, max: 30s }   # poll until the condition holds
retry: { times: 3, every: 5s }                            # run again while the step fails (its expect included)

7.6 Data sets

On a test: the whole test per row

id: TC-1042
title: Orders in every currency
data: data/orders.csv          # CSV with a header row, a JSON list of objects, or a YAML list of mappings
steps:
  - id: create
    type: http
    request: { method: POST, url: "{{env.api}}/orders", json: { currency: "{{row.currency}}", qty: "{{row.qty}}" } }

The test runs once per row as TC-1042[1], TC-1042[2] …, reading {{row.<column>}}. The file is looked up from the argus.yaml folder, then beside the test. argus-server maps every row's result to test case TC-1042.

On a step: one step per row

- id: signIn
  type: http
  data: data/users.csv                 # or { file: … }, or rows in place:
  # data: [ { user: ann, expected: 200 }, { user: bob, expected: 403 } ]
  request: { method: POST, url: "{{env.api}}/login", json: { user: "{{data.user}}" } }
  expect: [ "status == data.expected" ]
  • The step runs once per row as signIn[0], signIn[1] …, with the row's values as {{data.<column>}} (in expect as data.<column>).
  • Every row runs, even after one fails; the step fails if any row did.
  • Later steps read the rows' outputs as {{steps.signIn[1].body}}, and steps.signIn.length is the row count.

In the desktop

The step's Data tab starts with a DATA SET section: the rows with a status dot per row after a run, column chips that insert {{data.<column>}}, New data file (creates data/<test>-<step>.csv with the columns the step reads), Use another file, Open in editor and Remove. Run step uses picks which row a single-step run uses.

Step data set
Figure 7.2 Step data set

In Files, data files open in a Table view to edit cells directly (4.8).

Each row ran
Figure 7.3 Each row ran

7.7 Insert value and the reference lens

You rarely need to type a {{...}} by hand.

{x} Insert value sits beside every field that takes a value (and the HTTP URL bar). It opens a picker of everything the step can read: earlier steps' outputs (with their last-run values), environment variables, data columns, payloads, vars. Pick one and the reference is inserted at the cursor.

Insert value picker
Figure 7.4 Insert value picker

The lens under each field shows, for every {{...}} in it, where it comes from (a step, env, data, a payload) and what it resolved to on the last run. Broken references are red: an unknown step, a later step, a missing field, an undefined variable. Click a reference to edit it: change the source, add transforms, and see the result update as you go, then Update in .

Where each value comes from
Figure 7.5 Where each value comes from

Change a value includes a field mapper: for a list of objects, tick the fields to keep and type new names to rename them; Argus writes the matching pick(...) and rename(...) transforms.

Keep and rename fields
Figure 7.6 Keep and rename fields

The Data tab's "Use a value" card

The Data tab lists every earlier step's result as a tree. Click a value to open Use a value: it shows what the value gives on the last run while you add transforms (suggested for the value's shape, such as pluck(id) for a list of rows, or typed), then either inserts it into the field you were editing or saves it as a transform step before the selected step and puts {{steps.<id>.value}} in the field.

Use a value: where, pluck, join
Figure 7.7 Use a value: where, pluck, join

7.8 The payload library

Request bodies, messages and documents the project reuses live in one folder, payloads/ beside argus.yaml (sub-folders are fine). A payload's name is its path there without the extension: payloads/orders/create.json is orders/create. When two files share a name, add the extension (orders/create.xml).

Use it in any field:

- id: create
  type: http
  vars: { qty: 3 }                         # the payload reads {{vars.qty}}
  request: { method: POST, url: "{{env.api}}/orders", json: "{{payload.orders/create}}" }
- id: getOrder
  type: http
  request: { method: POST, url: "{{env.api}}/soap", headers: { Content-Type: text/xml }, body: "{{payload.soap/get-order}}" }
- id: publish
  type: kafka
  connection: events
  produce: { topic: orders.created, value: "{{payload.events/order-created | omit(internal)}}" }
// payloads/orders/create.json
{ "sku": "A1", "qty": {{vars.qty}}, "customer": "{{env.customer_id}}" }
  • JSON and YAML payloads read as values (so json:, Kafka and Redis values keep their shape, and transforms work on them); CSV reads as a list of rows (usable as data: "{{payload.users}}"); XML and other text reads as text; binary files as base64.
  • {{...}} inside a payload resolves where it is used: the step's vars:, env, earlier steps, data rows, other payloads. In JSON a bare "qty": {{vars.qty}} is allowed and keeps the value's type.
  • A payload the library doesn't have stops the run before it starts, naming the closest names.
  • The older json: "@file.json" (a file beside the test) still works and also finds library payloads by name.
  • In CI the library is found from argus.yaml's folder, else the nearest payloads/ above the test, the working folder or $CI_PROJECT_DIR.

The Payloads screen

Payload library
Figure 7.8 Payload library

The Payloads screen (⌘8, the package in the rail) manages the library:

  • Upload files (button, or drop them on the screen), or New to write a JSON / YAML / XML / text / CSV payload.
  • Edit in place and save (⌘S); Format JSON.
  • Update from file replaces one with a newer file.
  • Rename / move: tests and payloads that read it are rewritten.
  • Remove, and Used by: which tests read it.
  • Insert into the step: put {{payload.<name>}} in the field being edited.

The HTTP composer's Body tab has a Payload library… list (XML adds the Content-Type), the Data tab shows the library as chips, and payloads are a group in every field's value picker.

From the command line and the assistant:

argus payloads list
argus payloads show orders/create
argus payloads add ./order.json --name orders/create
argus payloads rename orders/create orders/create-basic   # references follow
argus payloads remove orders/old

The MCP tools are list_payloads, read_payload and save_payload.

Chapter 8
08

Connections, MCP servers and tools

  1. 8.1Connection types
  2. 8.2Databases through SSH
  3. 8.3SSH hosts
  4. 8.4AKS and Kubernetes
  5. 8.5The Connections screen
  6. 8.6MCP servers
  7. 8.7Connections from the command line
  8. 8.8Connections in CI

A step that talks to a system names a connection; the connection's settings live in argus.yaml, and its secrets come from the OS keychain, local variables or CI/CD variables. This keeps tests portable: the same test runs against qa2 or uat by picking an environment.

connections:                       # shared by every environment
  batch-host: { type: ssh, host: batch01.qa, user: qa, key: "${BATCH_SSH_KEY}" }
environments:
  qa2:
    connections:                   # only qa2; wins over a shared one of the same name
      orders-db: { type: postgres, url: "${QA2_PG_URL}" }

8.1 Connection types

type Fields (secrets as ${...}) Used by
postgres url, or host, port, database, user, password; sslmode (disable, prefer (default), require, verify-full), ca-cert, trust_cert, via sql
sqlserver url (ADO string, e.g. server=tcp:db.qa,1433;database=orders;user id=qa;password=${QA_SQL_PW};trustservercertificate=true) or host fields; encrypt (required, on, off), ca-cert, trust_cert, via sql
oracle host, port (1521), service, user, password; or connect (TNS alias, host:port/service, descriptor); privilege (sysdba …), client_lib_dir, via sql
sqlite path (relative to argus.yaml, or :memory:), read_only, create sql
redis host, port (6379), db, user (ACL), password; tls, ca-cert, client-cert, client-key, trust_cert; mode: cluster or sentinel with nodes, master, sentinel-password; via; or url: redis[s]://user:pass@host:port/db redis
kafka brokers; security (plaintext, ssl, sasl_plaintext, sasl_ssl), sasl-mechanism (PLAIN, SCRAM-SHA-256, SCRAM-SHA-512), user, password; ca-cert, trust_cert, client-cert + client-key; schema-registry, schema-registry-user, schema-registry-password; client-id kafka
ssh host, port, user, auth (password, key, agent), password, key, passphrase, known_hosts, jump, identity-agent, keepalive, host-key-checking, insecure_ignore_host_key ssh, sftp, via: tunnels
aks kubeconfig (file or text) + context, namespace; or cluster, resource-group, subscription, admin, login (kubelogin), client-id, client-secret, tenant-id kubectl
kubernetes kubeconfig, context, namespace kubectl
smtp host, port (587 STARTTLS, 465 TLS), user, password, from, tls (starttls, tls, opportunistic, none), ca-cert email
sendgrid api-key, from (a verified sender) email
ses region, access-key-id, secret-access-key (else the AWS_* variables), from email

Examples:

connections:
  orders-db:
    type: sqlserver
    host: sql01.qa
    database: orders
    user: qa
    password: "${keychain:shared.orders-db.password}"
    encrypt: required
    via: bastion                  # tunnel through an ssh connection
  ledger:
    type: oracle
    host: ora01.qa
    service: LEDGER
    user: qa
    password: "${LEDGER_PW}"
  cache:                          # Azure Cache for Redis
    type: redis
    host: orders.redis.cache.windows.net
    port: 6380
    tls: true
    password: "${REDIS_ACCESS_KEY}"
  events:                         # Confluent Cloud
    type: kafka
    brokers: pkc-xxxx.westeurope.azure.confluent.cloud:9092
    security: sasl_ssl
    user: "${KAFKA_API_KEY}"
    password: "${KAFKA_API_SECRET}"
    schema-registry: https://psrc-xxxx.westeurope.azure.confluent.cloud
  eventhubs:                      # Azure Event Hubs (Kafka endpoint)
    type: kafka
    brokers: orders-ns.servicebus.windows.net:9093
    security: sasl_ssl
    user: $ConnectionString
    password: "${EVENTHUBS_CONNECTION_STRING}"
  mail:
    type: smtp
    host: smtp.office365.com
    port: 587
    user: qa@example.com
    password: "${keychain:shared.mail.password}"
    from: Argus QA <qa@example.com>

8.2 Databases through SSH

via: <ssh connection> on a postgres, sqlserver, oracle or redis (one server) connection opens ssh -N -L to the database through that host (and its jump hosts) for the step, like Switchyard's Connect via host.

8.3 SSH hosts

ssh and sftp steps run the system's OpenSSH client, so agents and ~/.ssh/config work.

connections:
  bastion:
    type: ssh
    host: bastion.example.com
    port: 2222
    user: ops
    auth: key                       # password, key or agent; empty: whatever OpenSSH tries
    key: ~/.ssh/ops_ed25519         # a file, or the key itself (e.g. "${BASTION_KEY}")
    passphrase: "${keychain:shared.bastion.passphrase}"
    known_hosts: "${KNOWN_HOSTS}"   # files, or the known_hosts lines themselves
  batch01:
    type: ssh
    host: batch01.internal
    user: qa
    auth: password
    password: "${keychain:shared.batch01.password}"
    jump: bastion                   # ProxyJump: ssh connections or user@host:port, commas for more hops
    keepalive: 30
  • Passwords and passphrases are answered through SSH_ASKPASS (OpenSSH 8.4+, Windows 10/11 included); argus is its own helper and passes the answer only through the ssh process's environment.
  • Host keys: host-key-checking is accept-new on a workstation and yes in CI (CI or GITLAB_CI set), unless you set it. In CI give known_hosts (the output of ssh-keyscan you checked); insecure_ignore_host_key: true turns checking off for lab hosts.
  • A key or known_hosts given as text is written to a private temporary file for the step; a key file others can read (a GitLab File variable) is copied to a private one, as OpenSSH refuses it.

8.4 AKS and Kubernetes

connections:
  qa2-aks:                          # a kubeconfig, e.g. from Key Vault
    type: aks
    kubeconfig: "${keyvault:qa2-aks-kubeconfig}"   # or a path: ~/.kube/qa2.yaml
    context: qa2-aks
    namespace: billing
  qa2-aks-ci:                       # or the cluster, through the Azure CLI
    type: aks
    cluster: qa2-aks
    resource-group: rg-qa2
    subscription: QA
    login: spn                      # kubelogin: azurecli, spn, msi or workloadidentity
    client-id: "${AKS_CLIENT_ID}"
    client-secret: "${AKS_CLIENT_SECRET}"
    tenant-id: "${AZURE_TENANT_ID}"

An inline kubeconfig is written to a private temporary file for the step and deleted after it. cluster + resource-group run az aks get-credentials into a temporary file (signed in as the Azure CLI is), and login converts a copy with kubelogin convert-kubeconfig, never your own file. The service principal reaches kubelogin through its environment, never its arguments.

8.5 The Connections screen

The Connections screen (⌘3, the plug in the rail) has four tabs: Connections, Variables, Jira and Tools on this computer. Tab badges are red when something fails and amber when something is missing.

Connections screen
Figure 8.1 Connections screen

Connections groups the project's connections (Databases, Cache and messaging, Servers and clusters, Email, Web) for the selected environment, with MCP servers and the environment's base URLs in their own card. Each row has an inline Test; a failure shows its message under the row. Test all checks everything, like argus check: Postgres connects and runs SELECT 1, SSH runs true, AKS asks the cluster for its version, Redis PINGs, MCP servers initialize and list their tools, and base URLs get a GET. Results show names and outcomes, never settings or secrets.

Add connection starts with a type picker:

Pick a type
Figure 8.2 Pick a type

then a form in sections: Target (where), Sign in (fields follow Auth), and More (folded unless something in it is set). Defined in chooses every environment or one. Each secret is either saved in the OS keychain (the file gets ${keychain:<scope>.<connection>.<field>}) or named as a variable for CI (${QA2_DB_PASSWORD}). Test connection checks the form before it is saved; the result shows as a banner above the buttons.

New PostgreSQL connection
Figure 8.3 New PostgreSQL connection

Saving writes argus.yaml for you, changing only that connection's lines; comments and the rest of the file stay as they are. Edit, Duplicate and Delete are on each row.

Variables is the local variables editor (3.6). Jira connects Jira for the desktop (chapter 19).

Tools on this computer checks what steps need on this machine (Robot Framework, Node.js / npx, Playwright browsers, formatters such as Prettier, Ruff and Robocop) and installs what is missing.

Tools on this computer
Figure 8.4 Tools on this computer

8.6 MCP servers

mcp steps call tools on MCP servers. Servers use the mcpServers format Claude Code, Cursor and AgentOps share, from a file, inline, or per environment:

mcp:
  config: mcp.json                 # the team's mcpServers file
  servers:                         # inline; these win over the file
    servicebus:                    # stdio: started by Argus
      command: npx
      args: [-y, "@acme/servicebus-mcp@1.4.2"]
      env: { SERVICEBUS_CONNECTION: "${keyvault:qa-servicebus}", LOG_LEVEL: info }
      cwd: tools                   # relative to argus.yaml
      startup_timeout: 120         # seconds to start and initialize (default 60)
environments:
  qa2:
    mcp:
      servers:
        orders:                    # streamable HTTP (or type: sse for HTTP+SSE)
          type: http
          url: https://orders-mcp.qa2.example.com/mcp
          headers: { Authorization: "Bearer ${ORDERS_MCP_TOKEN}" }
  • type is stdio, http or sse; without it, command means stdio and a url ending in /sse means SSE.
  • Override order: the mcp.config file (or ARGUS_MCP_CONFIG), then mcp.servers, then the environment's mcp.config and mcp.servers.
  • A stdio server starts the first time a step calls it and is shared by every later step and test in the run. A call that times out kills the server (and everything it started) and the next call starts a fresh one. At the end of the run each server is stopped with its whole process group, so npx launchers leave nothing behind.
  • group: core|full tells argus plan which CI image runs tests that use it (16.3).

8.7 Connections from the command line

argus connections list                       # names, targets, where each secret comes from
argus connections add orders-db --type postgres --env qa2 \
  --set host=pg.qa2.internal --set database=orders --set user=qa --set via=bastion \
  --secret password                          # read from stdin (no echo), saved in the keychain
argus connections add batch --type ssh --set host=batch01 --secret-var password=BATCH_PW
argus connections test [orders-db] --env qa2 # connect: SELECT 1, ssh `true`, ...
argus connections secret qa2.orders-db.password   # save or rotate one keychain item
argus connections remove orders-db --env qa2      # and its keychain items
argus connections env tests/orders --env qa2      # CI/CD variables those tests need (names only)

From Switchyard

argus connections import-switchyard lists what it can import from Switchyard's profile store on this machine (or --from team.switchyard-workspace.json, or --ssh-config for ~/.ssh/config hosts); give names or --all. Hosts, SFTP, PostgreSQL and SQL Server are imported with their jump and tunnel hosts. --secrets copy (default) copies passwords into Argus's keychain items; reference writes ${switchyard:<item>}; env writes ${VAR}s; skip leaves them out. A connection can also stay in Switchyard: orders-db: { from: switchyard } (this machine only, so not in CI).

8.8 Connections in CI

One argus.yaml serves the desktop and a GitLab Docker job, which has no keychain: every ${keychain:<item>} falls back to the variable ARGUS_SECRET_<ITEM>.

  1. argus connections env tests/ --env qa2 lists the variables the tests need (ARGUS_SECRET_… for keychain items, and any ${VAR}). Names only, never values.
  2. Add each in GitLab under Settings > CI/CD > Variables, masked (and protected where the branch allows). A private key or known_hosts can be a File variable: key: and known_hosts: take a path or the contents.
  3. Give SSH connections known_hosts.
connections:
  batch01:
    type: ssh
    host: batch01.internal
    user: qa
    key: "${keychain:shared.batch01.key}"     # CI: ARGUS_SECRET_SHARED_BATCH01_KEY (File or contents)
    known_hosts: "${KNOWN_HOSTS}"

argus ci gitlab lists all of them for you when it sets a repository up (chapter 16).

Part III

Running tests

Run tests and read the results, schedule automations and measure performance.

Chapter 9
09

Running tests and reading results

  1. 9.1Running from the desktop app
  2. 9.2Stopping a run
  3. 9.3Run options
  4. 9.4Running from the command line
  5. 9.5What a run writes
  6. 9.6The run view
  7. 9.7Running one step
  8. 9.8The evidence drawer
  9. 9.9Hybrid tests: manual steps
  10. 9.10Capturing evidence and filing defects
  11. 9.11Live results on the server
  12. 9.12Reading results on the command line
  13. 9.13Self-healing locators and visual checks

9.1 Running from the desktop app

To run Do
The open test Run locally in the top bar, or ⌘R
A folder Select it in the explorer and click Run N, or ⇧⌘↵
Every test Select the root folder and Run N
Only the selected step Run step in the inspector, or ⌘↵ (9.7)
The failed tests of the last run Re-run failed tests (palette, or the run view)
On GitLab instead Run on GitLab, or ⇧⌘R (16.5)

Runs use the environment picked in the top bar's environment menu. Folder runs and "run all" leave automations out.

While a test runs, the canvas lights up step by step: running (with elapsed time and, for Playwright and Robot, the current action), then passed, failed or skipped. The evidence drawer follows the step that is running and streams its output live (Playwright's and Robot's console, SQL progress such as still waiting every 10 s).

Live step status
Figure 9.1 Live step status

When the run ends, a toast says how it went with an Open report link.

9.2 Stopping a run

While a run is live, Run locally becomes Stop; the run view's header has Stop too. Stopping kills the step's whole process tree (Playwright, Robot, browsers, scripts; taskkill /T on Windows), cancels a running SQL statement on the database server, marks the step Skipped with "stopped", and the steps after it Not run. The run shows as Stopped.

Stopped
Figure 9.2 Stopped

9.3 Run options

The environment menu's Local runs section holds the options for runs from the app (saved in desktop.json):

Local runs options
Figure 9.3 Local runs options
Option Effect CLI equivalent
Step timeout How long a step may take when nothing more specific sets a limit (see Time limits below); none for no limit. --timeout 2m, ARGUS_TIMEOUT
Test time limit How long each test may take as a whole, unless the test or its folder sets its own. --test-timeout 30m, ARGUS_TEST_TIMEOUT
Show the browser Headed runs: playwright steps get --headed; Robot gets ${HEADLESS} = False and visible browsers; Windows brings the browser to the front. --headed, ARGUS_HEADED=1
Tests at once How many tests run in parallel (default, 1, 2, 4, 8, auto). --workers N, -j N, ARGUS_WORKERS, argus.yaml workers:

Time limits

Every limit is a duration (500ms, 30s, 2m, 1h30m, 1d) or none for no limit. A step's limit is the first of these that sets one; at each level a limit for the step's type counts before the general one:

  1. the step's own timeout: (inspector, Behavior tab);
  2. the loop, branch, poll-until or sub-flow step it sits in (its timeout: is the limit of each step inside), and the flow file's own timeout: / timeouts:;
  3. the test's timeouts.<type>, then its timeout: (inspector: step timeout, test time limit, timeouts by step type);
  4. argus.yaml timeouts.folders entries the test is under, deepest first;
  5. the environment's timeouts.<type>, then argus.yaml's timeouts.<type>;
  6. the run's Step timeout (--timeout);
  7. the environment's timeout:, then argus.yaml's timeout:;
  8. 30 s (script steps 10 minutes and email steps 2 minutes, which they also keep over a shorter limit from 6 and 7).
# argus.yaml
timeout: 45s                 # every other step
timeouts:
  test: 30m                  # each test as a whole (teardown aside)
  run: 3h                    # the whole run
  sql: 10m                   # step types: http sql ssh sftp kubectl kafka redis
  robot: 1h                  #   script mcp email robot playwright pytest page-perf
  folders:
    tests/batch: { step: 5m, test: 2h, sql: 1h }
    tests/batch/nightly: { kafka: none }
environments:
  perf:
    timeout: 2m              # this environment is slower
    timeouts: { http: 1m, test: 1h }
  • A playwright step's limit is Playwright's per-test --timeout (none is 0). When nothing sets one, Argus passes none and the project's playwright.config decides. pytest names playwright steps with a .py spec, so they can be set apart.
  • robot and pytest steps run whole suites, so they are only stopped as a whole by a limit set for them: on the step, test, folder or step type (not by the run-wide step timeout). Robot gets SIGTERM first and still writes its log.
  • A test's time limit (timeouts.test on the test, its folder, the run's Test time limit, the environment or argus.yaml) cuts the running step to what is left; the steps after it are skipped, always: teardown steps still run with their own limits, and an automation's on-error steps too. When the run's limit (--run-timeout, timeouts.run) runs out, tests that have not started are Not run.
  • A step that fails at its limit says how long it had and where that was set ([time limit 45s, from argus.yaml timeout:]), so you know what to change.
  • The Project settings screen has a Timeouts section: test and run limits, one field per step type, folders, and each environment's step timeout and test time limit.

Parallel runs. With more than one test at once, the run view lists Running now. Results, events and the report keep the usual order and masking. Tag a test serial when it shares state with others (one account, one row): it runs alone, after the rest. Each worker starts its own MCP servers.

Running now
Figure 9.4 Running now

9.4 Running from the command line

argus run tests/orders --env qa2              # files or folders
argus run tests/ --env qa2 --tags smoke,!slow --timeout 2m
argus run tests/ui --headed                   # show the browser
argus run tests/ --env qa2 --workers 4        # four tests at once (-j 4, auto = one per CPU)
argus run tests/ --note "retest after BUG-12" # why this run happened
argus report show --failed argus-results/output.json
Option
--env Environment from argus.yaml (else the test's env:).
--tags smoke,!slow Tests carrying any listed tag and none of the ! ones.
--timeout, --test-timeout, --headed, -j/--workers As in 9.3.
--run-timeout 2h The whole run's time limit (or ARGUS_RUN_TIMEOUT); tests not started by then are not run.
--note Why the run happened (or ARGUS_RUN_NOTE); shown in history, reports and the web app.
--out Results folder (default argus-results).
--no-history / --history Don't keep the run / earlier runs for the report.
--shard 2/4, --group core|full, --report-shard Split a suite across machines (chapter 16).
--push / --no-push Stream results to argus-server as tests finish (on when ARGUS_URL, ARGUS_PROJECT and ARGUS_API_KEY are set).
--trigger What started it: local, pipeline, schedule, merge_request, web, ai.
--remote [--wait] Start the tests repository's GitLab pipeline instead (16.5).
--resume, --manual, --manual-results Hybrid tests (9.9).
-v/--verbose More output.

Exit codes: 0 every test passed, 1 one or more tests failed, 2 configuration or infrastructure error (invalid YAML, unknown environment, unreachable connection, missing program).

ARGUS_REQUIREMENTS (e.g. EBM-412,EBM-415) records which requirements a run covers.

9.5 What a run writes

argus-results/
├── output.json        # the run's single source of truth (schema argus-results/1.0)
├── junit.xml          # for GitLab's Tests tab and other tools
├── report.html        # one self-contained file (chapter 10)
├── events.ndjson      # live events: start_run, start_test, start_step, end_step, end_test, end_run
├── evidence/          # per test and step: bodies, logs, screenshots, videos, traces, downloads
└── history/
    ├── 2026-10-31T09-12-03_<run id>/   # each local run (output.json, events, evidence under 25 MB)
    └── history.json                    # step records for baselines and "vs last pass"

The newest 30 runs are kept. Runs in CI (CI set) are never kept in history. The results format is described by a JSON Schema in docs/schema/argus-results-1.0.json.

Every run also records what it covered and why: its scope (paths, tag filter, and the mix of tests: tests/billing · tags smoke · 12 tests: 4 Robot, 8 Argus), its note, its trigger (Run all · desktop, Schedule (0 6 * * 1-5), pipeline), and commit and branch.

9.6 The run view

The Run view (⌘2, or Run view in the breadcrumb bar) shows the open run: the verdict and counts in the header, the tests grouped by status with failures first, and each test's steps. J / K move to the next and previous failure.

Run view
Figure 9.5 Run view
  • Groups (Failed, Passed, …) fold on click; Collapse all / Expand all in the Tests in run header. Folds survive live updates.
  • Select a step to open its evidence in the drawer below (9.8).
  • Re-run failed runs the failed tests again.
  • The header's run menu has Add a note…, which saves a note on the run.
  • A step re-run alone is marked rerun, and the run's counts are recomputed.

Run history

The run view lists the runs kept in argus-results/history/ (from argus run and the app alike), newest first. Each row has two lines: what ran (the scope) and how and why (trigger and note), with a tooltip for the details. Click one to open it.

Run history labels
Figure 9.6 Run history labels

When you are signed in to argus-server, Load runs from argus-server adds the project's server runs (CI pipelines, other people's runs) to the list.

9.7 Running one step

⌘↵ (or Run step, or a composer's Send / Run query / Try it) runs only the selected step. The outputs of the steps before it are taken from the run on screen, so you can fix and re-run a failing step without running the whole test. A test that has never run can be run step by step: each step's result feeds the next.

  • A step inside a loop sees the iteration you choose in Run step uses (each[0], each[1] …; the failing one by default).
  • A step with a data set uses the row chosen there.
  • A step inside a sub-flow sees the flow's with.

The result replaces the step's record in that run's output.json and history entry, marked rerun.

One step re-run
Figure 9.7 One step re-run

9.8 The evidence drawer

The drawer shows what the selected step did, in tabs that depend on its type:

Step Tabs
http Request (method, URL, headers, body), Response (status, headers, body), Assertions
sql, redis, kafka Result (the rows or messages as a sortable table), the statement / command, Assertions
ssh, script, kubectl Output (stdout and stderr as terminal tabs with colours), Assertions
sftp Transfers (sizes, text previews)
playwright Steps, screenshots, video, trace, output
robot Keywords (Robot's keyword tree, open along the failing path, with Expand all / Collapse all), Log
load, page-perf Results (chapter 12)
every step vs last pass: the step's status and output compared with the latest kept run where it passed
Diff vs last pass
Figure 9.8 Diff vs last pass

Copy as cURL / SQL / command on HTTP, SQL, Redis, SSH, script and SFTP steps (Copy input on MCP) copies a command you can paste into a terminal; masked credential headers become shell variables such as ${AUTHORIZATION}.

A flow card (sub-flow or setup) is clickable during and after a run: its drawer shows the flow's result, and its open card shows each inner step.

9.9 Hybrid tests: manual steps

A test with a manual step stops there as pending. When a tester has done the step, resume the run; the steps after it run, and the other tests keep their results:

argus run tests/ --resume argus-results/output.json \
  --manual 'TC-1042/verify-letter=pass' --manual 'TC-1043/check-ui=fail:wrong total'
# or --manual-results manual.json: [{ "test", "step", "status", "actual", "tester" }]

On argus-server, testers record manual steps in the execution player; the server's GET …/cycles/{id}/manual-results returns exactly the file --manual-results reads, so a pipeline can pick the results up.

9.10 Capturing evidence and filing defects

  • Capture (in the drawer, the defect dialog, or the palette's Capture the screen as evidence of the selected step…) takes a screenshot, lets you mark it up (arrows, highlights, text, blur to hide data), and saves the PNG into the step's evidence.
  • Clip (the palette's Record a screen clip…, or the status bar button) records the screen for up to 2 minutes (MP4 through ffmpeg, else GIF) as evidence. Run the command again, or press Stop, to end it.
  • Log defect (in the run header, or under a failed step's evidence) fills in the test, step, expected and actual, evidence and run.
    • Signed in with ARGUS_PROJECT set: the defect is filed in argus-server (and in Jira through it, with the captures attached).
    • Jira connected on the desktop and no server: it is filed straight in Jira, linked to the test's requirements.
    • Otherwise Copy as defect copies it as Markdown.
Defect filed
Figure 9.9 Defect filed

9.11 Live results on the server

With ARGUS_URL, ARGUS_PROJECT and ARGUS_API_KEY set (or --push), argus run streams start_run, each end_test and end_run to argus-server, so the web app shows the run live. A server that can't be reached is a warning, never a failed run. events.ndjson can also be sent as is to POST /api/projects/{p}/events.

Robot suites run outside Argus can stream the same way with the listener:

robot --listener tools/robot-listener/ArgusListener.py tests/

with ARGUS_URL, ARGUS_PROJECT, ARGUS_API_KEY and ARGUS_ENV set. Tests are matched to test cases by a TC-1042-style key in their tags or name. The listener masks secrets itself (variables whose names look secret, plus ARGUS_MASK_VARS and ARGUS_MASK_PATTERNS) before anything is sent.

9.12 Reading results on the command line

argus report show --failed argus-results/output.json     # failures as text
argus report diff before.json after.json                 # started failing, fixed, new
argus report diff 1842 1857                               # run ids read from argus-server
argus report render output.json --history history.json -o report.html
argus report export output.json --format robot            # a Robot-compatible output.xml

9.13 Self-healing locators and visual checks

Recorded Playwright specs import two helpers from argus-playwright.ts, which Argus writes next to the spec before each run (it never overwrites a copy you changed and that lacks its version marker).

import { heal, snapshot } from './argus-playwright';

const el = await heal(page, "getByLabel('Email')", {
  alts: ["locator('#email')", "getByRole('textbox', { name: 'Email' })"],
  fp: { tag: 'input', role: 'textbox', name: 'Email', id: 'email' },
});
await el.fill('qa@shop.test');
await snapshot(page, 'after save', { mask: ['.clock'], maxDiffRatio: 0.002 });

Healing. heal() waits for the main locator as usual. If it has not found exactly one element after 2 seconds, it tries the backups, then looks for the element most like the fingerprint (it must score at least 50% and clearly beat the next best). The step goes on with what it found and the run log says ⚕ healed locator. Nothing in the spec changes during the run: the healed locator goes to the review queue. When nothing matches, the step fails as before.

Variable Default
ARGUS_HEAL on off turns healing off (the main locator only)
ARGUS_HEAL_GRACE_MS 2000 How long the main locator gets before healing
ARGUS_HEAL_WAIT_MS 10000 When to give up
ARGUS_HEAL_MIN_SCORE 0.5 How alike a fingerprint match must be

Visual checks. snapshot() takes a stable screenshot (it repeats until two agree) and Argus compares it with the baseline in visual-baselines/<test>/<step>/<name>-<os>.png:

  • New (no baseline yet): the step passes and the screenshot waits for review (visual: { new: fail } on the step makes it fail instead).
  • Same, within the tolerance: passes.
  • Changed: the step fails with visual change: … (0.25% of pixels differ) and the evidence has the baseline, the new screenshot and a diff image with changed pixels in red.

The tolerance is a per-pixel colour threshold (0.1), maxDiffRatio (0.1% of pixels) and maxDiffPixels, set on the snapshot() call or for the whole step:

- id: ui
  type: playwright
  spec: save.spec.ts
  visual: { threshold: 0.15, max_diff_ratio: 0.002, new: fail }

mask takes selectors to paint over (dates, ads, avatars) before comparing.

Reviewing. Healed locators and new or changed screenshots wait in the review queue (argus-results/review/review.json). A step that passes later with its own locator drops its heals from the queue.

Review screen in the desktop app
Figure 9.10 Review screen in the desktop app
  • Desktop: the Review button in the rail (with a count) shows each healed locator as Was and Now, and each screenshot as baseline, this run and difference. Accept new locator rewrites the spec: the new locator comes first and the old one stays as a backup. Approve copies the screenshot into visual-baselines/. Commit both.
  • Command line: argus review lists the queue; argus review accept <id>, argus review accept-all and argus review reject <id> decide.
  • Web app: Runs → Review (see 18.7). An approved screenshot becomes the baseline on the server at once, for every run that reports to it, until the repository gets the file: argus review pull (or argus visual pull for baselines only) writes the approved baselines into visual-baselines/ and the accepted locators into the specs.
  • Assistant and MCP: review_list, review_accept and review_reject, the fix_broken_locator prompt (it looks at the live page with browser_suggest_locator and proposes a fix), and browser_visual_check to add a checkpoint while recording.

Baselines are per operating system, because fonts render differently; approve the screenshots from the machine type your pipeline uses.

Chapter 10
10

The report

  1. 10.1Reading it top-down
  2. 10.2Workflow replay
  3. 10.3Evidence viewers
  4. 10.4Using it
  5. 10.5Making reports by hand

Every run writes argus-results/report.html: one file with its styles, script, fonts and the run's data inlined, so it opens from a GitLab artifact, a Jira attachment or an email with no network access. argus-server serves the same report for each stored run (GET …/runs/{run}/report), with history from the 20 earlier runs on the same environment.

The report
Figure 10.1 The report

10.1 Reading it top-down

The report reads from verdict to cause:

  1. Banner: verdict, pipeline, commit, branch, duration, shards and counts, with the run's scope and note.
  2. What changed: new failures, fixed, still failing and flaky, against the last run on the same environment.
  3. Failures by root cause: failures grouped by error signature and labelled Environment, Product bug, Test bug, Flaky or Needs triage (categories QA set on the server carry over to later runs with the same error).
  4. Drill-down for a test: its workflow replay, the failed step's evidence, step timing against baseline, data lineage, and its last 20 runs.
  5. All tests: search, with facets for status, suite (a folder selects everything under it), tag and requirement. The list is virtualized, so very large runs stay fast.
  6. Timeline: a waterfall across shards; each test is a bar of its steps at its start time. Steps at least twice as slow as in the test's last passing run (and 250 ms slower) are striped and listed under Slowest against baseline.
  7. Requirement coverage, linked to the requirement facet.
Drill-down
Figure 10.2 Drill-down

10.2 Workflow replay

The replay draws the test as the desktop canvas does: a branch splits into yes / no lanes (the lane not taken is shown empty), a loop is a container with one section per iteration, and sub-flows and poll-until expand in place. A failed loop opens on the iteration that failed.

Workflow replay
Figure 10.3 Workflow replay

10.3 Evidence viewers

Step Viewer
HTTP Request and response, expected against actual
SQL, Redis, Kafka The statement or command and the rows or messages
SSH, script stdout and stderr as separate terminal tabs, with ANSI colours
SFTP Transfers with sizes and a preview for text files
MCP Input and output
Robot The keyword tree, nested as Robot ran it, with arguments, status, time and messages (open along the failing path; Expand all / Collapse all)
Playwright Steps, screenshots, the video and the trace (Open trace to trace.playwright.dev when served over HTTP, or the npx playwright show-trace command when opened from disk)
assert, manual Expressions and actual values; the action and expected result
load, page-perf Charts and tables (chapter 12)

Copy as cURL / SQL / command is on HTTP, SQL, SSH, script and SFTP steps (Copy input on MCP); masked credential headers become shell variables, so the command never carries a secret. Diff vs last pass compares the step's output with the same step in the test's last passing run.

Robot keyword tree in the report
Figure 10.4 Robot keyword tree in the report
Playwright evidence
Figure 10.5 Playwright evidence

10.4 Using it

  • Light and dark themes follow the system and can be toggled.
  • J / K move between failures.
  • Every test and step has a deep link: report.html#TC-1042/processECS.
  • All text from the run is placed as text, never as HTML.
  • A 5,000-test run renders to about 17 MB and opens in about a second.

10.5 Making reports by hand

argus report render output.json --history history.json -o report.html
argus report render output.json --history-from "$ARGUS_URL"     # trends from the server's last runs
argus report merge --from-pipeline "$CHILD_PIPELINE_ID" --run "$CI_PIPELINE_ID" \
  --history-from "$ARGUS_URL" --push "$ARGUS_URL/api/projects/EBM/runs"

merge joins shard results into one run and report (16.3).

Ad-hoc checks the assistant drives in a browser and suites run directly with run_suite also get a report (browser_report), kept in the run history and opened from the chat's Open report button (13.5).

Chapter 11
11

Automations and triggers

  1. 11.1Tests and automations compared
  2. 11.2An automation
  3. 11.3Triggers
  4. 11.4Who fires triggers
  5. 11.5Running automations by hand
  6. 11.6In the desktop app
  7. 11.7Not yet built

Argus's steps (HTTP with sign-in, SQL, Kafka, Redis, email, scripts, MCP, transforms, branches and loops) are the same building blocks a workflow automation needs. So besides tests, a project can hold automations: workflows that start on a schedule, at an interval or on a webhook, do their work, and hand back a result instead of a pass or fail. And any test can have triggers too, for example a nightly run on a QA machine.

11.1 Tests and automations compared

Test Automation
File kind: test (default) kind: automation
Steps, connections, {{...}} data same same
branch, loop, poll-until, sub-flow, data: same same
retry, continue-on-failure, always same same
triggers: yes yes
Run by argus run <folder> yes no (run it on its own)
Verdict pass / fail, in QA results and reports succeeded / failed, kept apart from QA results
input:, {{input.*}}, {{trigger.*}} readable yes
on-error: steps no (use always: true) yes
output: no yes: the run's result, and a webhook's reply

11.2 An automation

kind: automation
id: AUTO-order-digest
title: Summarise new orders
env: qa2
triggers:
  - schedule: "30 7 * * mon-fri"        # weekdays at 07:30, local time
  - webhook: orders/digest              # POST /hooks/orders/digest {"limit": 50}
    secret: ${ORDERS_HOOK_SECRET}
    respond: with-output
input:
  limit: 500
steps:
  - id: orders
    type: http
    request: { url: "{{env.api}}/orders?since={{now - 1d}}&limit={{input.limit}}" }
    retry: { times: 3, every: 5s }
  - id: big
    type: transform
    input: "{{steps.orders.body}}"
    pipe: [ "where(total, >=, 100)" ]
  - id: tell
    type: email
    connection: mail
    to: sales@example.com
    subject: "{{steps.big.value | count}} orders over 100 since yesterday"
    attach: [ { name: orders.json, content: "{{steps.big.value}}" } ]
on-error:
  - id: alert
    type: email
    connection: mail
    to: ops@example.com
    subject: "Order digest failed at {{error.step}}"
    body: "{{error.message}}"
output:
  checked: "{{steps.orders.body | count}}"
  sent: "{{steps.big.value | count}}"
  started_by: "{{trigger.kind}}"
  • {{input.*}} is the run's input: the input: defaults, then the trigger's input:, then a webhook's JSON body (or --input).
  • {{trigger.*}} describes what started it: kind (schedule, every, webhook, manual), name, time, and for webhooks method, path, query, headers and body (JSON when it parses, text otherwise).
  • on-error: steps run after a failure and read {{error.step}}, {{error.message}} and {{error.status}}: post to a chat, send an email, roll back. The run still counts as failed.
  • output: is evaluated when the steps end: it is the run's result, printed by argus automate run and returned by a respond: with-output webhook.
  • An expect: in an automation is a guard: it stops the automation when its data looks wrong.

An automation run succeeded when no step failed, failed otherwise.

11.3 Triggers

triggers:
  - at: ["07:30", "13:15"]       # times of day (24-hour); one time can be just at: "07:30"
    days: mon-fri                # weekdays: mon-fri, sat,sun, mon,wed,fri; every day if left out
    timezone: Europe/Paris       # optional: an IANA time zone; this computer's time if left out
  - every: 15m                   # a fixed interval (at least 10s)
    between: "08:00-18:00"       # optional: only in these hours (22:00-02:00 runs past midnight)
    days: mon-fri                # optional: only on these weekdays
    input: { limit: 50 }         # optional: input for this trigger's runs
  - schedule: "0 6 1 * *"        # cron: minute hour day-of-month month weekday
    timezone: UTC
    env: qa2                     # optional: the environment this trigger runs in
  - webhook: orders/new          # POST/PUT/GET http://<host>:<port>/hooks/orders/new
    secret: ${ORDERS_HOOK_SECRET}
    respond: with-output         # reply with the run's output; default at-once (202)
    enabled: false               # keep it, but don't fire it

A trigger has exactly one of at, every, schedule or webhook. days goes with at and every (a cron has its own weekday field), between with every, and timezone with any timed trigger. An every with between or days fires at the start of each window, then every interval while it is open.

Cron fields accept *, numbers, lists (1,15), ranges (1-5), steps (*/10, 8-18/2) and names (jan, mon-fri); weekday 0 and 7 are Sunday. When both day-of-month and weekday are restricted, either one matching is enough, as in standard cron.

Webhook secrets are checked in one of three ways: the X-Argus-Secret header equals the secret; X-Argus-Signature: sha256=<hex> is the HMAC-SHA256 of the body with the secret as key (GitHub style; X-Hub-Signature-256 works too); or ?secret= in the URL for senders that can do neither. A webhook without secret: is open, and argus validate warns about it.

Tests started by a trigger are ordinary QA runs (results, report, history), labelled with the trigger, e.g. Mon–Fri at 07:30, 13:15, Every 15m 08:00-18:00, Schedule (0 6 1 * *) UTC or Webhook orders/new.

11.4 Who fires triggers

Where Fires Notes
The desktop app Schedules and intervals of the open project's tests and automations, while Argus runs, also with its window closed to the tray (4.13). A firing that comes due during another run waits for it. Pause in the Triggers section, or Pause all schedules on the tray icon, stops it on this machine (schedules in desktop.json). Quitting Argus stops it. Webhooks need the service.
argus triggers serve Schedules, intervals and webhooks for every test and automation under the paths. The long-running service: on a QA machine, a server, or next to argus-server in Docker.
argus triggers list .                    # every trigger and when it fires next
argus triggers serve . --port 8686       # fire them; POST /hooks/<path> for webhooks

triggers serve rescans the files every few seconds, so edits take effect without a restart. It runs one run per test or automation at a time: a schedule that comes due while the last run is still going is skipped (and logged); webhook calls queue (up to 20). GET / lists every trigger with its next time and last run.

11.5 Running automations by hand

argus automate run automations/order-digest.yaml --input '{"limit": 50}' --env qa2
argus automate history automations/order-digest.yaml

Automation runs are kept per automation under argus-results/automations/<id>/ (newest 30), apart from QA results, and are never pushed to argus-server.

11.6 In the desktop app

  • The explorer marks automations auto and anything with an enabled trigger with a clock. New automation (the lightning button, or the palette) starts one from a template.

  • Open an automation and Run it like a test. Its run says Succeeded or Failed and shows its output under the run's name.

  • Above the canvas of an automation (and of a test with triggers) a Starts bar lists every trigger in words with when it fires next, for example Mon–Fri at 07:30 · next Mon 07:30. Click one to edit it, + Schedule to add one, and Run now to run it at once. The bar stays whatever step is selected.

  • The test's settings (click a trigger in the Starts bar, or select no step) have a Triggers section with the schedule editor. Pick how it starts:

    • At times: one or more times of day (07:30, 13:15), the weekdays (or Every day, Weekdays, Weekends) and a time zone.
    • Every…: an interval (15m, 1h, 1d), optionally only between two times of day and on some weekdays, and a time zone.
    • Cron: any cron expression, with common ones one click away.
    • Webhook: the path, the secret and whether to reply with the output.

    The editor shows the trigger in words and its next three firings before you save (Enter in any field saves). Each trigger can be turned off or removed; triggers, and an automation's input and output, can also be edited as YAML below.

  • Run step (and running up to a step) works in automations as in tests; the step reads the automation's input: defaults.

The Starts bar and the schedule editor
Figure 11.1 The Starts bar and the schedule editor
An interval kept to working hours
Figure 11.2 An interval kept to working hours
Triggers section
Figure 11.3 Triggers section
A schedule fired while the app was open
Figure 11.4 A schedule fired while the app was open
An automation with a webhook
Figure 11.5 An automation with a webhook
Succeeded, with its output
Figure 11.6 Succeeded, with its output

11.7 Not yet built

Chapter 12
12

Performance testing

  1. 12.1The load step
  2. 12.2The page-perf step
  3. 12.3In the desktop app
  4. 12.4The Performance page in the web app
  5. 12.5Performance tests in GitLab CI
  6. 12.6Journeys: flows, sign-in per user, feeds and pacing
  7. 12.7Watching the system during the load (monitors)
  8. 12.8More load than one machine: remote generators
  9. 12.9UI under load: Playwright and Robot
  10. 12.10The performance/ folder and making a load test from a test
  11. 12.11How many users a machine runs
  12. 12.12Compared with LoadRunner, JMeter, k6 and Gatling

Two step types measure performance, and the web app's Performance page keeps their results (and JMeter, k6 and Lighthouse uploads) over time.

Step Measures
load An API or database under load: many virtual users run nested steps over and over; latency percentiles, throughput, errors.
page-perf How fast a page loads in a real browser: TTFB, FCP, LCP, DCL, load, TBT, CLS, requests, transfer size.

12.1 The load step

Each virtual user is its own thread with its own kept-alive HTTP connection (keep-alive: false opens one per call). An iteration runs the steps in order; later ones read earlier ones as {{steps.<id>…}}; a failed step ends that iteration. Steps that can run under load: http, sql, redis, kafka, script, mcp, ssh, kubectl, assert, transform, sub-flow (a whole journey, timed as a transaction), branch, loop, poll-until, and playwright / robot for real browsers (12.9). Steps can carry data: as anywhere else; a load-wide feed (12.6) is usually what a load needs.

- id: login
  type: http
  request:
    url: "{{env.api}}/me"
    auth: { type: oauth2, token-url: "{{env.idp}}/connect/token", client-id: argus, client-secret: "{{env.secret}}" }

- id: orders-load
  type: load
  users: 50                 # virtual users at full strength (1-2000)
  ramp-up: 30s              # 0 → 50 users over 30s (default: all at once)
  duration: 2m              # then 2 minutes at 50 users
  ramp-down: 10s            # optional
  think: 500ms              # each user's pause between iterations
  rate: 200/s               # optional cap on iterations a second, all users (or 600/m)
  steps:
    - id: list
      type: http
      request: { url: "{{env.api}}/orders?user={{load.user}}", auth: { use: login } }
      expect: [ "status == 200" ]
  expect:                   # thresholds: the load fails when one does not hold
    - "latency.p95 < 500"
    - "error_pct < 1"
    - "rps > 80"
    - "by_step.list.latency.p99 < 1500"

How long. duration (after ramp-up), or iterations: 1000 (stop after that many in all; with duration too, whichever comes first), or stages instead of users / ramp-up / duration / ramp-down:

- id: spike
  type: load
  stages: [ { users: 10, for: 30s }, { users: 200, for: 10s }, { users: 200, for: 1m }, { users: 0, for: 20s } ]
  steps:
    - { id: home, type: http, request: { url: "{{env.api}}/home" } }
  expect: [ "error_pct < 2" ]

Inside, {{load.user}} is the virtual user (from 1), {{load.iteration}} the iteration across all users and {{load.user_iteration}} this user's own count. auth: { use: login } reuses a token signed in once before the load.

Output (times in ms): requests, failed, error_pct (0–100), rps, latency {min, mean, p50, p75, p90, p95, p99, max} over every call, iterations, iteration (the same for whole iterations), users (most at once), duration_ms, statuses ({"200": 9812, "503": 4}), errors ([{step, message, count}]), by_step.<id> with the same per step, and:

  • latency.stddev (standard deviation) next to the percentiles;

  • apdex (0–1) with apdex_t_ms (T, set with apdex: 300ms, default 500ms): satisfied under T, tolerating under 4T;

  • distribution: how many calls answered within each band ([{le, count}]);

  • bytes and bytes_per_sec received;

  • by_step.<flow step> with transaction: true for a sub-flow (the whole journey, like a LoadRunner transaction), and <flow step>/<id> for each call inside it; user-setup/<id> for the sign-ins;

  • monitors (12.7) and generators (12.8) when the load has them.

  • Without expect, the load fails when any call failed (failed == 0).

  • A load where nothing answered at all stops early as an infrastructure error.

  • timeout: on the load step caps its whole run.

  • The run view lists one iteration's step records under the load (the first failed one when the load failed), and keeps load.svg (throughput, users, p50/p95 over time), timeline.csv (with p99 and bytes a second) and load-report.html as evidence. The load report is a page of its own: the headline numbers and Apdex, the thresholds, the chart over the run, every step and transaction with p50/p90/p95/p99/max/std dev, the response-time spread, status codes, grouped errors, each monitor's graph and each generator's share. It opens offline and can be sent on as it is.

12.2 The page-perf step

Playwright (installed in the project, or globally) opens the page runs times and measures each load.

- id: home-speed
  type: page-perf
  url: "{{env.web}}/dashboard"
  runs: 5                     # default 3 (1-50)
  device: mobile              # desktop (default, 1366×768) | mobile (Pixel 5)
  cache: cold                 # cold (default, a fresh profile each load) | warm (after a first load)
  wait: load                  # load (default) | domcontentloaded | networkidle
  throttle: { network: fast-3g, cpu: 4 }   # Chromium only: none | 4g | fast-3g | slow-3g | offline; cpu ×1-20
  settle: 1s                  # wait after loading for late paints (default 1s)
  browser: chromium           # chromium (default) | firefox | webkit
  headers: { Authorization: "Bearer {{steps.login.body.token}}" }
  storage-state: auth/user.json   # Playwright storage state, to start signed in
  expect: [ "lcp.p75 < 2500", "cls.max < 0.1", "tbt.median < 200", "ttfb.p95 < 800" ]

Output: url, status, title, runs, and for each metric {min, median, p75, p95, max, mean} over the loads: ttfb, fcp, lcp, dcl, load, tbt, cls, requests, transfer_kb; each lists every load's numbers. A page that answers 400 or more fails the step. Evidence: a screenshot of the last load and page-perf.json; the run view lists the slowest requests. WebKit reports no LCP.

12.3 In the desktop app

The step palette's PERFORMANCE group has API LOAD, SQL LOAD and PAGE SPEED. Their composers offer shape presets (Smoke, Load, Stress, Spike, Soak), page setups (Desktop, mobile and throttled), threshold chips for common checks, and the drawer's Results tab shows the numbers and charts after a run.

Load composer
Figure 12.1 Load composer
Load thresholds
Figure 12.2 Load thresholds
Load results in the run view
Figure 12.3 Load results in the run view
Page speed composer
Figure 12.4 Page speed composer

12.4 The Performance page in the web app

Performance in the web app's navigation (#/<project>/performance) keeps every performance result of the project:

  • A series table: the same step of the same test (or the same upload name) over time, with the latest numbers, the change against the previous result and a sparkline.
  • An all results table.
  • A detail page per result: KPI tiles, Compare with any other result of the series (deltas and a dashed p95 overlay), the load timeline chart, a per-step table, page metrics, each load and the slowest requests, a trend chart (click a point to open it), and the run's evidence.
  • A run's detail page has a Performance card.
Performance list
Figure 12.5 Performance list
Comparing two load results
Figure 12.6 Comparing two load results

Uploading results from other tools

Upload on the page (or the API, which a CI job can call with a project API key) takes:

  • Argus output.json, a step's output, or a load's timeline.csv;
  • JMeter results (JTL as CSV);
  • k6 summary JSON (--summary-export or handleSummary);
  • Lighthouse JSON.
curl -X POST -H "Authorization: Bearer $ARGUS_API_KEY" \
  --data-binary @results.jtl \
  "$ARGUS_URL/api/projects/EBM/perf/uploads?file=results.jtl&name=checkout-load&env=qa2&note=release%2026.5"
JMeter upload
Figure 12.7 JMeter upload

Results of runs are read live from the stored runs; uploads are kept until a lead deletes them.

12.5 Performance tests in GitLab CI

Tests with a load or page-perf step count as tagged perf by themselves, so argus run tests --tags perf runs only them and --tags '!perf' leaves them out. argus ci gitlab keeps them out of the usual merge request and push jobs and adds an argus-perf job that runs them, one at a time, in pipelines started with ARGUS_PERF=1 (a nightly schedule, say). With ARGUS_URL, ARGUS_PROJECT and ARGUS_API_KEY set, results and their charts land on the Performance page (12.4), and a threshold that does not hold fails the job. Details are in 16.8.

12.6 Journeys: flows, sign-in per user, feeds and pacing

A realistic load is a user's journey, not one call. A load step has four parts, run in this order:

Part Runs For
setup once, before any user starts preparing data; outputs reach everyone as {{steps.<id>…}}
user-setup once per virtual user signing in; outputs stay for that user's iterations
steps every iteration the journey being measured
teardown once, after the last user stops (even when the load failed) clean-up
- id: checkout-load
  type: load
  users: 100
  ramp-up: 1m
  duration: 10m
  think: 1s..3s              # a random pause in that range, like a person
  pacing: 10s                # each user starts an iteration at most every 10s
  apdex: 400ms
  feed: { file: performance/data/users.csv, pick: per-user }
  user-setup:
    - { id: signin, type: sub-flow, use: flows/login, with: { user: "{{data.email}}", password: "{{data.password}}" } }
  steps:
    - id: browse
      type: sub-flow
      use: flows/browse
      with: { token: "{{steps.signin.token.body.token}}" }
    - id: order
      type: http
      request:
        method: POST
        url: "{{env.api}}/orders"
        headers: { Authorization: "Bearer {{steps.signin.token.body.token}}" }
        json: { sku: "{{data.sku}}", ref: "load-{{load.user}}-{{load.user_iteration}}" }
      expect: [ "status == 201" ]
    - { id: read-back, type: http, request: { url: "{{env.api}}/orders/{{steps.order.body.id}}" } }
  teardown:
    - { id: clean, type: http, request: { method: DELETE, url: "{{env.api}}/test-orders?prefix=load-" } }
  expect: [ "error_pct < 1", "by_step.browse.latency.p95 < 2000", "apdex > 0.85" ]

Correlation is plain {{steps.<id>…}}: the id an earlier call returned, a token, a CSRF value or an ETag is read from that call's response in the same iteration, so nothing captured at authoring time is replayed.

Feeds give each iteration a row as {{data.<column>}}, from a CSV, JSON or YAML file (file:) or inline rows:. pick says how:

pick Each iteration gets
next (default) the next row, starting again after the last
random any row
unique a row no other iteration had; the load ends when they run out
per-user the same row for all of a user's iterations (accounts)

feed: performance/data/users.csv alone means pick: next. Keep feeds in performance/data/ (12.10).

Think and pacing. think: 500ms or think: 1s..3s pauses between iterations. pacing: 10s starts each user's iterations at most that often, waiting for the rest when an iteration was quicker; rate: 200/s caps the iterations a second over all users.

12.7 Watching the system during the load (monitors)

monitors sample numbers from the system under test while the load runs: CPU and memory of the pods in AKS, queue depth, the rate a service takes data in. Each is read every every (default 5s, at least 500ms):

  monitors:
    - name: cpu
      type: prometheus          # Prometheus, or Grafana's proxy to it
      url: "{{env.grafana}}/api/datasources/proxy/uid/prometheus"
      headers: { Authorization: "Bearer {{env.grafana_token}}" }
      query: sum(rate(container_cpu_usage_seconds_total{namespace="shop"}[1m])) * 100
      unit: "%"
    - name: memory
      type: prometheus
      url: http://prometheus.monitoring:9090
      query: container_memory_working_set_bytes{namespace="shop", container="api"} / 1e6
      aggregate: max            # sum (default), avg, max or min over the series
      unit: MB
    - name: ingest
      type: http                # any endpoint answering JSON
      url: "{{env.api}}/admin/stats"
      value: body.ingest.per_second   # where the number is; body for a bare number
      every: 2s
      unit: msg/s
  expect: [ "monitors.cpu.max < 80", "monitors.ingest.mean > 500" ]
  • type: prometheus runs the query against <url>/api/v1/query, so it works with Prometheus itself, Azure Monitor managed Prometheus, or Grafana's data source proxy (/api/datasources/proxy/uid/<uid>) with a Grafana token.
  • type: http GETs the URL (with headers) and reads the number at value.
  • Output monitors.<name>: min, max, mean, last, samples, failed, unit, source, series (time and value), and error when it could not be read. Thresholds can check them; the load report draws each one over the run beside the load's own chart.
  • A monitor that cannot be read never fails the load by itself; check monitors.<name>.samples > 0 in expect when it must be there.
  • Only the controller samples (12.8), so a monitor is read once however many generators share the load.
  • For Grafana, give a service account token with the Viewer role. The runner reads Grafana over its HTTP API as above; Grafana's MCP server is for the assistant (chapter 13), which can browse dashboards and write the queries for these monitors.

12.8 More load than one machine: remote generators

One machine runs a few thousand API users (12.11). For more, or for load from inside the cluster, the load is split over generators: argus load agent processes on other machines, AKS pods or GitLab CI job services. The machine running the test (the controller) sends each generator the project's files and what the test has so far, each generator runs its share of the users, iterations and rate, and their numbers merge into one result as if one machine had run it.

- id: checkout-load
  type: load
  users: 2000
  duration: 15m
  generators: [ "dns://argus-load.argus-perf.svc.cluster.local:7071" ]
  local: false                 # this machine only controls (default: it runs a share too)
  steps: [ ... ]

generators takes http://host:7071, host:7071, or dns://<name>:7071 (also k8s://), which is every address the name has: a Kubernetes headless service reaches each pod. ARGUS_LOAD_GENERATORS (the same list, commas) applies to every load step without one.

  • Controller and generators share a token in ARGUS_LOAD_TOKEN (at least 8 characters); a generator answers nothing else.
  • Each generator reads secrets from its own environment, so {{env.*}} values that are secrets are set on the generators, not sent.
  • Files sent leave out node_modules, argus-results, build folders and folders with a .argusignore file; at most 5 MB a file and 64 MB in all.
  • A generator that cannot be reached, refuses or fails its part fails the load as an infrastructure error; the output's generators list shows each one's requests and failed.
  • A generator runs one load at a time.

On AKS

argus load k8s --replicas 4 --memory 8Gi --cpu 4 --out performance/generators/aks.yaml
kubectl create namespace argus-perf
kubectl -n argus-perf create secret generic argus-load-env \
  --from-literal=ARGUS_LOAD_TOKEN=... --from-literal=API_SECRET=...
kubectl apply -f performance/generators/aks.yaml

The manifest is a Deployment of argus load agent pods on the argus:core image, each ready once /v1/health answers, and a headless Service argus-load. Every variable in the argus-load-env secret reaches the pods. Load steps then use generators: [ "dns://argus-load.argus-perf.svc.cluster.local:7071" ], and the test runs in a pod or a CI job inside the cluster (or with kubectl port-forward to each pod). Scale with kubectl scale.

In GitLab CI, without a cluster

Job services start generators beside the job that runs the load, so the same tests run before any pods exist:

argus ci gitlab --load-generators 3      # the argus-perf job gets three generators
argus load gitlab --generators 3         # or print the services to paste in

The job gets ARGUS_LOAD_GENERATORS (gen-1:7071,gen-2:7071,gen-3:7071) and a ARGUS_LOAD_TOKEN made from the pipeline and job ids unless the project sets one. Services share the runner's machine, so this spreads the work over processes, not machines; give the runner the memory (12.11).

Results go to argus-server like any other run: with ARGUS_URL, ARGUS_PROJECT and ARGUS_API_KEY set, the merged numbers, load.svg, timeline.csv and load-report.html reach the project's Performance page (12.4), whichever machines produced them.

12.9 UI under load: Playwright and Robot

playwright and robot steps run under load too: each virtual user drives its own real browser, and each test (Playwright) or test case is timed as a transaction (by_step.<step>/<test title>), next to the step itself.

- id: shop-ui-load
  type: load
  users: 10                 # browsers at once: at most 50
  duration: 5m
  think: 2s..5s
  apdex: 3s                 # a page is slower than a call
  steps:
    - { id: buy, type: playwright, cwd: .., spec: e2e/checkout.spec.ts }
  expect: [ "error_pct < 2", "by_step.buy.latency.p95 < 15000" ]

A browser takes 150–300 MB and about a CPU core when busy, so a load with browsers is capped at 50 users per machine; use generators (12.8) for more, or load the APIs behind the pages with http steps and keep a few browsers for what users see. Each user's screenshots, videos and traces land in its own evidence folder (u1, u2, …).

12.10 The performance/ folder and making a load test from a test

argus load init (and the first load test made) creates beside argus.yaml:

performance/
  README.md                    how the folder is used
  data/users.example.csv       feeds (12.6)
  generators/aks.yaml          the AKS manifest (12.8)
  generators/gitlab-services.yml

Load tests go in performance/ (by area, like tests/); data/ and generators/ are not tests, so runs skip them.

Make a load test from this test. Any working test can become a load test:

  • Desktop: right-click a test in the explorer, Make a load test (or Make a load test with the assistant, which also asks the assistant to review it).
  • CLI: argus load make tests/orders/checkout.yaml --profile load --users 50.
  • MCP / assistant: the make_load_test tool and prompt.

What it does:

  • Calls repeat in steps. A sign-in before the first other call (a step whose id or URL says login, signin, authenticate, oauth or /token, or a sub-flow of a login flow) and the test's setup: move to user-setup, once per user.
  • The test's data set becomes a feed, and {{row.x}} becomes {{data.x}}.
  • Steps that cannot run under load (manual, email, sftp, page-perf, desktop-app, mobile-app; browsers unless --browsers) are left out and listed with the reason; deletes (HTTP DELETE, SQL DELETE or DROP) are noted, since every iteration repeats them.
  • --profile sets the shape: smoke (up to 5 users for 1 minute), load (1m ramp-up, 5m at --users, 30s down), stress (stages from half to twice the users), spike (a fifth, then three times the users for a minute, and back), soak (1 hour at --users). --think (default 1s..3s) and --expect (repeatable) set the rest; without --expect it checks errors under 1% and p95 under 1s.
  • It writes performance/<the test's folder>/<name>-load.yaml with id <test id>-LOAD, checks it the way argus validate does, and keeps an existing file unless --force. --dry-run prints it, --prompt prints what to ask the assistant.

The assistant review covers what a conversion can't decide: values that must come from an earlier response (correlation), data that must be unique per iteration, what is per user and what per iteration, pacing for a target throughput, thresholds from the service's targets, and whether anything left out needs replacing. Then a smoke run with 2 users for 30 seconds before the real profile.

12.11 How many users a machine runs

An API virtual user is a thread with its own kept-alive connection. Measured on Linux, a user takes about 0.2 MB besides what the responses it keeps take, so memory is rarely the limit: 2,000 users (the most a load step takes) need about 400 MB plus the process. The limits are CPU and network, the system under test, and the operating system's open connections. As a guide for one 32 GB, 8-core machine:

Load Users on one machine
API calls (http, sql, redis, kafka) up to 2,000 per load step; several steps or generators for more
Browsers (playwright, robot) 50 at most; 20–30 is more realistic on 8 cores

Watch the generator's own CPU during a first run: above about 80%, its latencies include its own waiting, so add a generator rather than users.

12.12 Compared with LoadRunner, JMeter, k6 and Gatling

Argus
Scripts Argus tests and flows (YAML), Playwright and Robot; made from existing tests
Transactions each sub-flow and each Playwright test, with percentiles
Correlation {{steps.<id>…}} from earlier responses, transforms (7.3)
Parameters feeds with next, random, unique, per-user
Think time, pacing think (fixed or a range), pacing, rate
Scenarios users with ramp-up/down, stages, iterations, duration
Load generators argus load agent on machines, AKS pods, GitLab services
Monitors Prometheus / Grafana queries and JSON endpoints
Results p50–p99, std dev, Apdex, distribution, bytes/s, errors, statuses, per transaction, load-report.html, the web Performance page with comparisons; JMeter/k6 results can be uploaded
Not built a recorder for load scripts (use the browser recorder or a test, then Make a load test), protocols other than those Argus steps speak, network virtualization, IP spoofing
Part IV

Help with the work

Let the assistant write, run and triage tests, and bring the tests you already have.

Chapter 13
13

The assistant

  1. 13.1Setting it up
  2. 13.2The conversation
  3. 13.3The composer
  4. 13.4What to ask
  5. 13.5Ad-hoc UI checks from the chat
  6. 13.6Writing and reviewing Robot and Playwright tests
  7. 13.7Jira in the chat
  8. 13.8Running the assistant outside the app
  9. 13.9The Argus skill

The desktop app has an AI assistant in the side panel (⌘J). It drives a coding CLI you already have, Claude Code, Codex or Gemini CLI, in the open project folder, with Argus's own tools through argus mcp. It writes and edits tests, runs them, reads results and evidence, checks a web app in a real browser, converts Robot Framework and pytest suites, and files Jira issues. Like the chat in AgentOps, it is an ongoing conversation that edits files directly, and every change can be undone.

The assistant beside the canvas
Figure 13.1 The assistant beside the canvas

13.1 Setting it up

  1. Install one of the CLIs and sign in to it as usual (claude, codex or gemini on PATH, or the program in ARGUS_CLAUDE, ARGUS_CODEX or ARGUS_GEMINI).
  2. Open a project folder and press ⌘J.
  3. Pick the CLI in the composer's footer (the Runs on menu). Without a pick, Argus uses ARGUS_ASSISTANT, else the settings file's assistant, else the first CLI it finds.

The assistant runs argus (or ARGUS_BIN) as its MCP server, falling back to argus-desktop mcp. It always works in the project folder (the one with argus.yaml).

What each CLI may do:

CLI Tools
Claude Code The Argus MCP tools, plus its file tools (Read, Edit, MultiEdit, Write, Glob, Grep, LS). No shell: it runs suites through Argus's run_suite tool.
Codex Argus MCP tools, in a workspace-write sandbox
Gemini CLI Argus MCP tools

The Argus skill (13.9) is preloaded on every turn, whichever CLI runs, so the assistant knows the test format, every step type and every tool.

The assistant never sees connection secrets: tests run through the runner, and list_connections returns only names and types.

13.2 The conversation

  • Each reply streams its text and tool calls as they happen ("Working… 12s · tool"). Three or more tool calls in a row fold into one summary line once done; click to open. The model's thinking shows as Thought.
  • When a reply changed files, it ends with the files it changed, each with a diff and Undo, and Undo all for the whole reply. ⌘Z works too. Undo is refused for a file you changed since.
  • Each reply's footer shows time, cost and model, with Copy and Retry.
  • File paths in replies are links: a test opens in the test editor, any other file in Files at the line.
  • The conversation is kept per project and survives restarts. New conversation (the icon in the panel header, ⇧⌘J, or /new) stops a reply still running, clears the chat and starts a fresh CLI session.
A reply's changed files with diffs
Figure 13.2 A reply's changed files with diffs

13.3 The composer

Enter Send. While a reply is running (Claude Code), Enter steers it: your message is delivered at once and Claude answers it, then carries on.
Ctrl+Enter Queue the message to send after the current reply (always the case for Codex and Gemini). The queue sends itself unless you pressed Stop.
↑ Earlier messages
Paperclip, drop, paste Attach files and images. Pasted images are sent to Claude inline.
@ Mention a file in the project (fuzzy search).
/ Commands: /new, /test, /write-test, /review, /upgrade-robot, /attach, /model, /stop, /help.
Model chip Claude Code's model: Opus, Sonnet or Haiku.
UI test toggle Treat the message as an ad-hoc check of the web app, a desktop app or a mobile app; pick which under the hint (13.5).
New test toggle Treat the message as "write a new test" in the project's framework (13.6).
Jira issue Put a Jira issue in the message (13.7).
Steering while it works
Figure 13.3 Steering while it works
Slash commands
Figure 13.4 Slash commands

13.4 What to ask

  • "Write a test that creates an order, checks the invoice row in orders-db and cleans up." It creates the YAML, validates it and runs it.
  • "Why did TC-1043 fail?" It reads the run report and the failing step's output, tells you whether it is the test, the product or the environment, and offers a fix or a defect.
  • "Add a Kafka check that the order shipped event arrives within 30 seconds."
  • "Move the login steps of these three tests into a flow."
  • "Convert robot/ui/login.robot to Argus steps" (chapter 15).
  • "Set up GitLab CI for this repository" (it calls setup_gitlab_ci).

On an empty chat, buttons offer Write a test…, Review and Upgrade Robot Framework.

13.5 Ad-hoc UI checks from the chat

Turn on UI test (or /test, or the palette's Test the web app from the chat) and describe what to check: "check login and checkout on staging". The assistant:

  1. Calls find_tests to find tests and flows that already cover it (by title, tags, step ids, Playwright spec titles, locators and Robot test names), and the environment's web addresses. A Jira key in the message also fetches the issue and its linked tests.
  2. If one test covers it all, runs it. If tests cover part of it, it follows their flow in a real browser with the same locators and checks the rest. If nothing matches, it drives the browser from the environment's address.
  3. Reports a verdict (passed, failed, blocked) with one line per check and screenshots (thumbnails under the tool cards), and writes a report (browser_report) you can open from the chat.
  4. Offers Save as a test: a Playwright spec of what it did (passwords read from the environment, never written down) and an Argus test that runs it, or a test that reuses the tests it ran.

The browser (Chrome or Edge, a throwaway profile, visible so you can watch) stays open between turns and closes on New conversation, when you close the project, or when the assistant calls browser_close.

Reused an existing test
Figure 13.5 Reused an existing test
Screenshots in the chat
Figure 13.6 Screenshots in the chat
Saved as a test
Figure 13.7 Saved as a test

Desktop and mobile apps (computer use)

The same check works on a native desktop app or a mobile app on an emulator. With UI test on, pick the target under the hint: Web, Desktop app or Mobile. The empty chat's Test a desktop or mobile app… button and the palette's Test a desktop app from the chat (computer use)… and Test a mobile app on an emulator from the chat… do the same.

Desktop app check in the chat
Figure 13.8 Desktop app check in the chat

Say which app and what to check: "start Billing and check a new invoice totals its lines", or "sign in to com.example.shop and open Orders". The assistant (through the adhoc_app_test prompt):

  1. Looks for tests that already cover it, as for the web.
  2. Starts the app (or finds it running, by its name or window title) and reads its window as a list of elements before every action, with a screenshot. On a phone it uses the running emulator or simulator; start one first.
  3. Clicks, types, presses keys and swipes by element, and records each check.
  4. Reports a verdict per check with the screenshots, changes no files, and offers Save as a test: a test of desktop-app or mobile-app steps (6.21, 6.22) that finds elements by name and role, so it survives a moved window. Text typed into a password field is saved as {{env.password}}.

Everything it did in an app, across turns, goes into the saved test. Desktop apps are driven through their accessibility tree, so the app must run in your signed-in session; on macOS allow the terminal or Argus under Privacy & Security → Accessibility and Screen Recording. What a mobile check needs is in 6.22.

13.6 Writing and reviewing Robot and Playwright tests

Not everything has to be Argus YAML. For teams on Robot Framework and Playwright (TypeScript or Python), the assistant writes in the project's own framework and style:

  • test_stack tells it what the project uses per folder: Robot libraries, resources, tags and deprecated syntax; Playwright config, page objects and fixtures; pytest conftest; pinned and installed versions; the newest example files to copy the style from.
  • run_suite runs a suite for real (robot, npx playwright test, python -m pytest) or only checks it (--dryrun, --list, --collect-only), and writes a report.
  • Review suggests changes, then asks which to apply.
  • Upgrade Robot Framework (robot_upgrade_scan) finds deprecated syntax (singular headers, Force Tags, WITH NAME, [Return], :FOR, Run Keyword If, old RequestsLibrary keywords, Selenium2Library, .txt suites) and fixes what can be fixed automatically.

In Files, right-click a file or folder for Write a test here with the assistant, Review with the assistant and similar; the assistant can sit beside the Files editor, and editors reload files it changed.

Robot test written and dry-run
Figure 13.9 Robot test written and dry-run
Playwright test written and passed
Figure 13.10 Playwright test written and passed

13.7 Jira in the chat

Jira issue in the composer footer opens a picker (project → sprint → issue). Picking an issue adds it to the message with its description, images and the tests that already cover it, ready for "test this story". Pick several to put several issues in one message; later ones read "Also test KEY the same way". The picker stays open until Close. See chapter 19.

13.8 Running the assistant outside the app

The same tools work from any AI CLI: see chapter 14.

13.9 The Argus skill

skills/argus is an agent skill (a SKILL.md with reference files, the format Claude Code, Codex and Gemini CLI load) that teaches a coding CLI how Argus works: argus.yaml, the test format with every step type, expressions and assertions, local variables, running and triage, every MCP tool and prompt, and guides for converting Robot and Playwright suites and migrating an application.

argus skill install                         # ~/.claude/skills/argus and ~/.agents/skills/argus (Codex, Gemini CLI)
argus skill install --project               # .claude/skills/argus and .agents/skills/argus in this folder, to commit
argus skill install --project --agents-md   # also a pointer in AGENTS.md and GEMINI.md
argus skill install --cli claude            # only one CLI's folder
argus skill status | uninstall | export <dir> | show [references/convert-robot.md]

The Windows installer installs it for you and refreshes it on every update. Installing again replaces what an earlier install wrote; a folder named argus that something else wrote is left alone unless --force.

Chapter 14
14

The command line and the MCP server

14.1 The argus command

argus is installed with the desktop app (and in the CI images). Every command has --help. The complete, generated reference with every flag is skills/argus/references/cli.md; this section is the map.

Command What it does Chapter
argus validate <paths> [--env] Check tests and argus.yaml without running anything. Exit 2 on problems. 3.8
argus check [--env] [--name] Reach every connection, MCP server and base URL. 3.8
argus run <paths> Run tests; write argus-results/. 9.4
argus report render | merge | export | show | diff Make, join, convert, print and compare results. 10.5
argus plan <paths> -o child.yml Write a sharded GitLab child pipeline. 16.3
argus ci gitlab Set a repository up to run its tests in GitLab CI/CD. 16.1
argus connections list | add | remove | test | env | secret | import-switchyard Manage connections in argus.yaml and their keychain secrets. 8.7
argus payloads list | show | add | rename | remove Manage the payload library. 7.8
argus automate run | history Run an automation by hand; list its runs. 11.5
argus triggers list | serve List triggers; fire schedules, intervals and webhooks. 11.4
argus import robot <folder> Import a Robot Framework project as Argus tests. 15.1
argus import app <folder> [--convert] Copy an application into the project (and wrap its tests). 15.3
argus upgrade plan | apply | status | pytest | undo | finish Upgrade a project to Argus in place. 15.4
argus compare robot <output.xml> argus <output.json> Prove a conversion: match a Robot run and an Argus run test by test. 15.2
argus review list | accept | accept-all | reject | pull Decide on healed locators and new or changed screenshots; pull writes the server's decisions into the repository. 9.13
argus visual pull Write the visual baselines approved on argus-server into the repository. 9.13
argus sync <paths> Send each test's flow to argus-server, which draws it on the test case. 18.4
argus gate <release> Ask argus-server whether a release passes its gate. Exit 0 / 1. 20.3
argus login | logout Sign in to argus-server with single sign-on. 2.6
argus mcp Serve Argus as an MCP server on stdio. 14.2
argus skill install | uninstall | status | export | show Install the Argus skill for AI CLIs. 13.9

Exit codes (every command that checks or runs): 0 success, 1 a test failed (or the gate failed, or a comparison disagreed), 2 configuration or infrastructure error.

Talking to argus-server. Commands that read or write the server use ARGUS_URL, ARGUS_PROJECT and one of ARGUS_TOKEN (a personal token from My tokens), ARGUS_API_KEY (a project API key, for CI) or the sign-in from argus login.

14.2 The MCP server

argus mcp serves Argus over MCP on stdio, so any AI CLI can author, run and triage tests. In Claude Code:

claude mcp add argus -- argus mcp --root path/to/argus-tests

Paths are relative to --root. Connections are exposed by name only, and tests always run through the runner, so secrets never reach the agent. The desktop assistant runs the same server (with --keep-browser, so the browser survives between turns).

The full, generated list of tools and arguments is skills/argus/references/mcp-tools.md.

Tools

Group Tools
Tests and runs list_tests (filter by tags), find_tests (rank tests and flows covering a UI check), validate, create_test, edit_step (change one step at any depth and validate), run_test (locally, or where: gitlab), get_remote_run, get_run_report, get_step_output
The team's own suites test_stack, run_suite (robot / playwright / pytest, real or check-only), robot_upgrade_scan
Bringing tests in outline_robot, import_robot_project, convert_app, upgrade_project
Project list_payloads, read_payload, save_payload, list_connections (names and types only), setup_gitlab_ci
argus-server (with ARGUS_URL, ARGUS_PROJECT and a token) create_cycle, assign_tests, get_coverage, log_defect (jira: true also files it in Jira), summarize_cycle
Jira jira_get_issue (with the tests that cover it), jira_search (JQL), jira_create_issue (with evidence files), jira_comment, jira_link_test (adds the key to a test's requirements)
Browser (45 tools) below
Desktop and mobile apps (26 tools) below
Review review_list, review_accept, review_reject: healed locators and visual changes waiting for a decision (9.13)

Browser tools

The browser tools drive a real Chrome or Edge in a throwaway profile, visible so the person can watch (ARGUS_BROWSER_HEADLESS=1 hides it; Linux without a display is headless; ARGUS_BROWSER picks the program).

Pages and tabs browser_navigate (newTab opens another), browser_back, browser_forward, browser_reload, browser_tabs, browser_tab_select, browser_tab_close, browser_status
Checking browser_visual_check (a visual checkpoint: a screenshot each run compares with its approved baseline; saved as snapshot(page, name)), browser_suggest_locator (for a locator that no longer finds its element: the most similar elements on the page now, each with the locator to use)
Reading browser_snapshot (the page as an outline of headings, landmarks and controls, each with a ref and the Playwright locator that finds exactly that element), browser_screenshot (fullPage too), browser_find, browser_inspect, browser_get_console_logs, browser_network_log, browser_wait_for
Acting browser_click, browser_hover, browser_type, browser_type_env (types an argus.yaml environment var such as password without the value reaching the model), browser_fill, browser_clear, browser_select_option, browser_check, browser_uncheck, browser_focus, browser_press_key, browser_scroll, browser_scroll_into_view, browser_drag, browser_mouse, browser_upload, browser_dialog
The browser browser_downloads, browser_evaluate, browser_cookies, browser_storage, browser_emulate (viewport, mobile, user agent, location, offline, colour scheme, time zone, locale, headers, basic login), browser_block_urls, browser_pdf, browser_highlight
Saving browser_save_test (a Playwright spec of everything done since the browser opened, one test.step per action, plus an Argus test that runs it), browser_report (a report.html of an ad-hoc check), browser_close

Elements are named by a snapshot ref, a Playwright locator or a CSS selector. Upload and PDF paths stay inside the tests repository.

Desktop and mobile app tools

computer_* drive native apps on the machine through their accessibility tree (UI Automation on Windows, the Accessibility API on macOS, AT-SPI and XTest on Linux). mobile_* drive an Android emulator or device through adb, and on a Mac an iOS simulator through xcrun simctl and idb.

Desktop: finding computer_capabilities, computer_list_apps, computer_list_windows, computer_launch_app (start the app under test; cdp: true for an Electron/Chromium app hands its window to the browser_* tools, and browser_save_test then writes a test that starts the app, drives it with Playwright and closes it), computer_get_app_state (the window's elements, one a line with an id, and a screenshot; interactiveOnly and maxDepth for a quick map of a dense window; open menus and popups follow the window), computer_find (elements by role, name or text without the whole tree), computer_wait (until an element or window appears or goes)
Desktop: acting computer_click (on an element it presses it through accessibility, without moving the pointer; physical: true clicks with the mouse), computer_check, computer_expand, computer_select, computer_scroll_to (each does nothing when the element already is that way), computer_secondary_action (expand, toggle, show menu), computer_scroll, computer_drag, computer_type_text, computer_press_key, computer_paste_text, computer_set_value, computer_window (resize, move, maximize, minimize, restore or focus the app's window)
Desktop: checking and saving computer_expect (an element holding a value, checked/expanded/enabled, or gone; a dialog by title; the clipboard), computer_clipboard (read it, or put text on it), computer_save_test (a test of desktop-app steps of everything done in apps, across turns)
Mobile mobile_devices, mobile_launch_app (install, reset), mobile_stop_app, mobile_get_state, mobile_tap (long too), mobile_type_text, mobile_press_key (back, home, enter…), mobile_swipe, mobile_open_url (deep links), mobile_expect, mobile_save_test (a mobile-app step)

Element ids last until the next read of the state. Screenshots are saved under argus-results/apps/. Text typed into a password field is saved as {{env.password}}.

Prompts

Prompt Walks the agent through
convert_robot Converting one Robot suite: outline, one test per Robot test (keeping its key), validate, run, compare.
import_robot_project A whole Robot project: the direct import, then converting by hand what it left in Robot.
migrate_app Turning a migrated application's wrapped tests into native flows, never touching app/.
upgrade_project Upgrading the project in place, a few tests at a time.
adhoc_ui_test An ad-hoc UI check that reuses existing tests (13.5).
adhoc_app_test The same for a desktop app (target: desktop) or a mobile app on an emulator (target: mobile).
write_test Writing a new test in the project's framework and style.
review_test Reviewing a test, then asking which suggestions to apply.
upgrade_robot Upgrading Robot Framework syntax in a file or folder.
fix_broken_locator A Playwright test broke after a UI change: find the element now, propose the new locator, make the spec self-healing; or (test: review) go through the review queue (9.13).

Configuration the MCP server reads

Variable For
GITLAB_URL, GITLAB_PROJECT_ID, GITLAB_TRIGGER_TOKEN run_test with where: gitlab
ARGUS_URL, ARGUS_PROJECT, ARGUS_TOKEN get_remote_run and the test management tools (or the sign-in from argus login)
ARGUS_JIRA_URL, ARGUS_JIRA_EMAIL, ARGUS_JIRA_TOKEN, ARGUS_JIRA_PROJECT, ARGUS_JIRA_ISSUE_TYPE The Jira tools (the desktop's Connections → Jira sets them)
ARGUS_BROWSER, ARGUS_BROWSER_HEADLESS The browser tools
ARGUS_ADB (or ANDROID_HOME), ARGUS_IDB The mobile tools
Chapter 15
15

Bringing existing tests into Argus

  1. 15.1Importing a Robot Framework project
  2. 15.2Converting Robot files to native steps
  3. 15.3Migrating an application from another folder
  4. 15.4Upgrading a project in place

Argus is built to take over from what a team already has: Robot Framework suites, Playwright specs (TypeScript or Python), pytest, and QMetry. There are four routes, from lightest to heaviest:

Route Use it when Result
Convert one Robot file (15.2) You want a suite as native Argus steps now Native http, sql, kafka, redis, email, Playwright steps …, with a preview
Import a Robot project (15.1) A whole Robot project should become Argus tests Native steps where they map, a robot step for the rest
Migrate an application (15.3) Tests live in another folder or repository A copy under <name>/app, plus Argus tests that run it
Upgrade in place (15.4) The project folder is the old test project Old files moved to legacy/, Argus tests wrapping them, migrated a few at a time

Every route follows the same rule: move everything as far as it goes into native Argus.

  • What Argus reaches directly becomes native steps and flows (http, sql, ssh, kafka, redis, email, expect, loop, branch, retry, flows).
  • Request bodies, Kafka messages, Redis values and the files tests send go into the payload library (payloads/, read with {{payload.<name>}}), one file per payload, shared by every test that sends it.
  • Robot Framework becomes native steps; only what has no equivalent (AppiumLibrary, SikuliLibrary, custom Python libraries) keeps a robot step.
  • pytest API tests that use requests become native http steps.
  • pytest-playwright becomes Playwright TypeScript in playwright/.

The converters do this by default and list what they could not convert, with the reason, in IMPORT.md, MIGRATION.md or UPGRADE.md; the assistant finishes the rest. --inline-payloads keeps payloads in the tests and --keep-pytest keeps pytest-playwright tests in Python.

QMetry test cases, cycles and executions move to argus-server with its import (17.8).

15.1 Importing a Robot Framework project

argus import robot path/to/robot-project            # writes robot-import/<project>
argus import robot path/to/robot-project --dry-run  # only list what it would write
argus import robot path/to/robot-project --out tests/robot --id-prefix EBM

Every .robot suite and .resource file is read. What maps directly becomes native steps:

Robot Argus
RequestsLibrary (GET On Session, POST On Session, sessions from Create Session) http steps
DatabaseLibrary (Query, Execute Sql String, Row Count …) sql steps on the db connection, with :name parameters
SSHLibrary ssh / sftp steps
Run Process script steps
BuiltIn assertions (Should Be Equal, Should Contain, Length Should Be, Should Be True …) expect on the step they check
User keywords kind: flow files under flows/, called with sub-flow and their arguments as with:
Suite variables {{env.*}} references
A test's TC-1042-style key Its id (else RF-0001, --id-prefix); tags are kept

Request bodies (json=, XML data=), Kafka messages, Redis values and files read with Get File / Load JSON From File go into the payload library (payloads/ beside argus.yaml) and the steps read them as {{payload.<name>}}; IMPORT.md lists each payload and the tests that use it.

A test that uses anything else still runs: it gets one robot step running the original test, with the reason at the top of its file. When the project is outside the tests repository, its sources are copied to robot-import/<project>/.robot so those steps run in CI too. IMPORT.md lists every test and how it runs, the variables to add to argus.yaml and the connections to set up. Running the import again keeps files already there (--force replaces them).

In the desktop: the palette's Import a Robot Framework project…, or … with the assistant…, which imports into the open folder and then has the assistant convert the wrapped tests by hand (FOR → loop, IF → branch, retries → retry), validating and running them. Its edits can be undone.

Palette import commands
Figure 15.1 Palette import commands

15.2 Converting Robot files to native steps

The converter goes further than the project import: control flow and UI tests become native too.

Robot Argus
FOR, IF / ELSE, WHILE, TRY / EXCEPT, Wait Until Keyword Succeeds loop, branch, poll-until, retry, continue-on-failure
Teardowns Steps with always: true
SeleniumLibrary / Browser library A Playwright TypeScript spec and playwright steps; values the spec reads come back as {{steps.<id>.values.*}}
Kafka, Redis, RPA.Email.ImapSmtp Send Message kafka, redis (connection cache), email (connection mail) steps
CSV / DataDriver data Data sets, pointed out as the place to add new cases

Dependencies. The converter follows Resource, Variables (YAML/JSON files become argus.yaml variables), Library <file>.py and DataDriver files from the file's folder and the folders above it, and any --search folder. What it can't find is listed as missing; the assistant searches for those and then asks you to upload them.

In the desktop: the palette's Convert a Robot Framework file to Argus steps…, Files right-click → Convert to Argus steps…, or the welcome screen. A preview shows each file it would write (new / changed / same, with a diff), where to change data, and what is missing.

Robot conversion preview
Figure 15.2 Robot conversion preview
Missing files
Figure 15.3 Missing files

Proving parity. Run both and compare:

robot --output robot.xml robot/                        # the old suite
argus run robot-import/orders --env qa2                # the converted one
argus compare robot robot.xml argus argus-results/output.json   # exit 1 when they disagree

Tests are matched by a TC-1042-style key in their tags or names. argus report export output.json --format robot writes an Argus run as a Robot-compatible output.xml (which rebot reads), for teams whose tooling expects one.

15.3 Migrating an application from another folder

argus import app path/to/old-project              # copy into ./old-project/app
argus import app path/to/old-project --convert    # and wrap its tests as Argus tests
argus import app old-project/app --convert        # convert a copy made earlier

The copy leaves out .git, installed packages (node_modules, venv), caches and results; the original folder is only read. The copy gets an .argusignore, so Argus never reads its own YAML as tests.

With --convert, the app's tests become Argus tests beside the copy:

  • Robot suites through the Robot import (into robot/): native steps, payloads into the payload library;
  • pytest tests that call the API with requests converted to native Argus tests under api/ (http steps, their asserts as expect); tests with helpers, other fixtures or loops keep running in pytest, with the reason in MIGRATION.md;
  • pytest-playwright files converted to Playwright TypeScript in the project's playwright/ (tests/<name>/), their Argus tests running the TypeScript version (--keep-pytest leaves them in Python);
  • each Playwright spec as a test under playwright/ with one playwright step per test(...) (matched by its full title);
  • each pytest file as a test under pytest/ with one step per test function.

Steps carry continue-on-failure, as each was a test of its own. MIGRATION.md lists what was found, what was not copied and what to install.

Variables. Migrating works out what the app needs: .env files (.env for defaults, .env.qa and the like per environment), what its code reads (process.env.X, os.getenv("X", "default"), os.environ["X"], Robot %{X}, System.getenv, ${X} placeholders, pytest-env), and config files kept per environment (config/qa.json beside config/uat.json, appsettings.Staging.json, application-uat.properties) with the variable that picks one (TEST_ENV, APP_ENV …). They are added to argus.yaml, one environment per environment found, and migrated steps hand them to the app (env: on the step), so choosing an environment in Argus chooses the app's config. Secrets never go into argus.yaml: the desktop saves the values it finds as secret local variables; the rest are listed in MIGRATION.md.

In the desktop: the folder menu and palette have Migrate an application from another folder… (copy only), Migrate and convert to Argus flows… (copy, convert, then the assistant turns the wrapped tests into native flows, never touching app/), and Convert a migrated application to Argus flows.

Migrated, with its variables
Figure 15.4 Migrated, with its variables

15.4 Upgrading a project in place

When the project folder itself is the old test project, upgrade it without losing anything:

argus upgrade plan                     # preview: what moves to legacy/, what stays
argus upgrade apply                    # do it (refuses uncommitted changes unless allowed)
argus upgrade apply --keep-pytest      # leave pytest-playwright tests in Python
argus upgrade status                   # progress: what still runs old code
argus upgrade pytest tests/ui          # convert pytest-playwright files to TypeScript any time
argus upgrade undo                     # put everything back (files changed since are kept)
argus upgrade finish                   # remove legacy/ once nothing uses it
  • Every top-level file of the old project moves, unchanged, into legacy/ (with .argusignore). Git, CI files, README, editor settings, a root virtual environment, argus.yaml, flows/ and folders that already hold Argus tests stay; --keep and --move adjust that.
  • Argus tests under tests/ run the old tests from legacy/ (tests/robot, tests/playwright, tests/pytest), so everything runs as before.
  • Robot tests become native steps where they map, their payloads go into the payload library, and pytest-playwright tests are converted to Playwright TypeScript in playwright/ (the clean ones run from there at once).
  • The project's variables go into argus.yaml environments (never secrets).
  • UPGRADE.md describes the layout, what moved, CI files that name moved paths, set-up steps, variables and progress.
  • Then migrate a few tests at a time, with the assistant (Migrate the next tests to Argus with the assistant) or by hand; legacy/ shrinks out of use.

pytest-playwright → Playwright TypeScript. Tests become playwright/tests/<path>/<name>.spec.ts; classes become test.describe; marks become tags; parametrize becomes a loop of tests; Playwright calls become TypeScript with await; assert becomes expect(...). Page objects and conftest fixtures are converted too. What can't be translated gets a // TODO(argus): comment and its wrapper keeps running pytest.

In the desktop: folder menu Upgrade this project to Argus in place…, the palette's Upgrade progress, Migrate the next tests to Argus with the assistant, Undo the upgrade… and Remove the upgrade's backup folder…, and Files right-click → Convert to Playwright TypeScript.

Upgrade preview
Figure 15.5 Upgrade preview
Upgrade progress
Figure 15.6 Upgrade progress
Part V

CI and the server

Run in GitLab, set up argus-server, and manage test cycles, Jira, releases and roles in the web app.

Chapter 16
16

Running in GitLab CI/CD

  1. 16.1Setting up a repository
  2. 16.2CI/CD variables
  3. 16.3Images and the parallel pipeline
  4. 16.4MCP servers in CI
  5. 16.5Starting pipelines and following them
  6. 16.6Gating a deploy on test results
  7. 16.7Nightly runs
  8. 16.8Load and page-speed tests

Tests run in GitLab the same way they run on a laptop: the argus CLI in a job. Argus writes the jobs for you, picks the right image per test, can split a large suite into parallel shards, merges the results into one report and sends them to argus-server.

16.1 Setting up a repository

argus ci gitlab --dry-run                    # show what it would write, write nothing
argus ci gitlab --env qa2 --project EBM      # Docker runner, argus:core / argus:full image
argus ci gitlab --runner windows --runner-tag win-qa   # Windows shell runner (PowerShell)
argus ci gitlab --parallel                   # argus plan + child pipeline + merged report
argus ci gitlab --no-merge-requests --path qa/tests --stage verify

The same setup is in the desktop's folder menu and palette (Set up GitLab CI/CD…), and the assistant's setup_gitlab_ci tool. The dialog shows the files before writing them and marks which variables are already set on your computer, so their values can be copied to GitLab.

Set up GitLab CI/CD: Docker
Figure 16.1 Set up GitLab CI/CD: Docker

What it writes, at the repository's root:

  • .gitlab/argus.gitlab-ci.yml: the Argus jobs. Rewritten on every setup; a later setup (and the dialog) reads your choices back from it.
  • .gitlab-ci.yml: created when missing (with workflow: rules so a branch with an open merge request runs one pipeline). An existing one only gets an include: entry and the stage in its stages: list; nothing else changes.

The jobs:

  • run on pushes to the default branch, merge requests, schedules, Run pipeline, and argus-server's Run on GitLab; ARGUS_SKIP=1 leaves them out;
  • take pipeline variables ARGUS_ENV, ARGUS_TAGS (smoke,!slow), ARGUS_TESTS (test ids) and ARGUS_PATHS;
  • run argus validate, then argus run; keep argus-results/ (report.html, output.json, evidence) as an artifact; feed junit.xml to GitLab's Tests tab;
  • stream results to argus-server when ARGUS_URL, ARGUS_PROJECT and ARGUS_API_KEY are set.

Runners.

--runner How it runs
docker (default) image: $ARGUS_IMAGE with entrypoint: [""]. argus:full when a test needs Robot Framework, Playwright or a stdio MCP server started with node/npx/uvx/python, else argus:core. A migrated app's requirements.txt makes the job argus:full with a before_script that pip-installs it.
windows tags: [<runner tag>], PowerShell. Installs AgentOps Argus from https://agentops.build/argus/install.ps1 (--install-url / ARGUS_INSTALL_URL change it) when the runner has no argus.exe or an older one. Robot and Playwright must be on the runner.
--parallel (Docker) argus-plan (.pre) writes a child pipeline of shards, argus-tests triggers it, argus-report (.post) merges the shards into one run and report. On a merge request only the tests the branch changed run.

16.2 CI/CD variables

The setup prints the variables to add (Settings > CI/CD > Variables; the CLI prints a link from the origin remote). Values are never read or printed.

Variable Why
ARGUS_URL, ARGUS_PROJECT, ARGUS_API_KEY (masked) Send results to argus-server
Every ${NAME} argus.yaml and its MCP files read Passwords, tokens, URLs
ARGUS_SECRET_<ITEM> for each ${keychain:item} What Argus reads where there is no keychain
AZURE_KEYVAULT_URL, AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET ${keyvault:…}
{{env.NAME}}s no environment defines Settings of migrated apps
KNOWN_HOSTS, SSH keys (File variables) SSH connections

Names that read like secrets are marked masked. ${switchyard:…} can't work in CI: the setup warns, and you should import the connection instead. argus connections env --env qa2 tests/ lists only what one environment's connections need.

16.3 Images and the parallel pipeline

Image Contents
argus:core The argus CLI and what native steps need: HTTP, SQL (PostgreSQL, SQL Server, SQLite), Redis, Kafka, SSH/SFTP, email, kubectl / kubelogin / Azure CLI. No Node.js.
argus:full Everything in core, plus Robot Framework (SeleniumLibrary, RequestsLibrary, DatabaseLibrary, JSONLibrary, pabot), Google Chrome and chromedriver, Playwright for Node.js and Python (pytest, pytest-playwright) with Chromium, Node.js and uv for stdio MCP servers, postgres-mcp, Oracle Instant Client, and Python database clients. Robot runs under a virtual display, so headed suites work.

Build them from the Argus repository's Dockerfile (--target core / --target full); scripts/build-images.ps1 builds, checks and pushes both and argus-server to acrdeve1.azurecr.io/agentops/…. Point ARGUS_IMAGE_CORE / ARGUS_IMAGE_FULL at your registry. Behind a TLS-inspecting proxy, put the CA certificates in certs/ before building; everything in the images then trusts them.

The parallel GitLab pipeline
Figure 16.2 The parallel GitLab pipeline

argus plan writes a child pipeline:

argus plan tests/ --tags nightly -o child.yml                # every test, sharded per image
argus plan tests/ --changed origin/main -o child.yml         # only what this branch touched

Tests with robot or playwright steps, an Oracle connection or a stdio MCP server needing Node.js or Python go to argus:full jobs, the rest to argus:core; an MCP server's group: core|full overrides the guess. Each group is split into shards of --shard-size tests (at most --max-shards, default 8, per image); each job runs argus run --group core|full --shard i/n --report-shard k/total. With --changed, a test is selected when the test, its data file, a flow it uses or a file a step names changed; a change to argus.yaml selects every test.

A final job joins the shards:

argus report merge --from-pipeline "$CHILD_PIPELINE_ID" --run "$CI_PIPELINE_ID" \
  --history-from "$ARGUS_URL" \
  --push "$ARGUS_URL/api/projects/EBM/runs"   # ARGUS_API_KEY is sent as a bearer token

It downloads each job's output.json and evidence, writes output.json, junit.xml and report.html (with trends from the server's last runs), then uploads the run and each evidence file. In a sharded pipeline only shard 1 opens the live run and report merge closes it.

16.4 MCP servers in CI

  1. Keep the configuration in the tests repository (mcp.json or inline mcp.servers), with ${NAME} for secrets.
  2. Add each secret as a masked CI/CD variable.
  3. Stdio servers run inside the job; npx -y and uvx download the server on first use (pin versions, allow for it with startup_timeout). HTTP and SSE servers must be reachable from the runner, or run as a service container.
  4. A job running argus check --env "$ARGUS_ENV" on the right image shows whether each server starts, with its tool count.

16.5 Starting pipelines and following them

From How
Desktop Run on GitLab (⇧⌘R) runs the open test or folder on the chosen environment. The status bar follows the pipeline (triggered → running → finished) and opens its results when done; the stop icon beside it cancels the pipeline (needs GITLAB_TOKEN with api scope). Needs the GITLAB_* local variables below.
CLI argus run tests/orders --env qa2 --remote starts the pipeline for those paths on --ref (default GITLAB_REF, else main). --wait follows it (GITLAB_TOKEN) and downloads its results from argus-server into --out, giving up after --wait-minutes (default 60).
Assistant run_test with where: gitlab, then get_remote_run.
Web app Run on GitLab on the Runs page; Re-run failed on a run (18.7).
After a deployment argus-server starts the tests when GitLab reports a successful deployment (20.5).

Variables for the desktop, CLI and assistant: GITLAB_URL, GITLAB_PROJECT_ID, GITLAB_TRIGGER_TOKEN (a pipeline trigger token from the tests repository), and for following GITLAB_TOKEN; for results ARGUS_URL and ARGUS_TOKEN.

Run on GitLab, following the pipeline
Figure 16.3 Run on GitLab, following the pipeline

16.6 Gating a deploy on test results

An application repository can run the smoke flows after its deploy and promote only when they pass; examples/gitlab/app-repo.gitlab-ci.yml shows it. A release job can also ask argus-server whether the release passes its quality gate with argus gate (20.3).

16.7 Nightly runs

Add a GitLab pipeline schedule on the tests repository with ARGUS_CYCLE=nightly (and ARGUS_TAGS, ARGUS_ENV as needed). With the project's Nightly cycle turned on in argus-server, each night's results land in one cycle (18.6).

16.8 Load and page-speed tests

Tests with a load or page-perf step are performance tests, and they count as tagged perf without anyone tagging them: argus run tests --tags perf runs only them and --tags '!perf' everything else.

When a repository has both kinds, argus ci gitlab keeps them apart, so a merge request or a push does not put load on an environment:

  • The usual jobs leave the performance tests out (unless ARGUS_TAGS or ARGUS_TESTS pick them).
  • A pipeline with ARGUS_PERF=1 runs only the argus-perf job, which runs only the performance tests, one at a time, with up to two hours for the job and one such job per environment at once (resource_group). Add a pipeline schedule with ARGUS_PERF=1 (nightly, say), or start one from Run pipeline.

page-perf steps load the page in Chromium with Node.js Playwright, which only the argus:full image has: the setup picks argus:full for them, and argus plan sends their tests to the full image's jobs. load steps run on argus:core.

With ARGUS_URL, ARGUS_PROJECT and ARGUS_API_KEY set, each result goes to argus-server as its test finishes, and the evidence (the load chart load.svg, timeline.csv, the page screenshot) is uploaded at the end of the run. The numbers appear on the project's Performance page, in the same series as earlier runs of the same step, so a slower build shows up as a change from the last result (18). A threshold (expect) that does not hold fails the step, the test and the job (exit 1), which fails the pipeline.

argus ci gitlab --load-generators 3 gives the argus-perf job three load generators as job services, so a load bigger than one process runs before any AKS pods exist (12.8).

Results from other tools (JMeter, k6, Lighthouse) can be sent from a job too, with the same API key: curl -H "Authorization: Bearer $ARGUS_API_KEY" --data-binary @results.jtl "$ARGUS_URL/api/projects/$ARGUS_PROJECT/perf/uploads?file=results.jtl&name=Checkout&env=$ARGUS_ENV".

Chapter 17
17

Setting up argus-server

  1. 17.1The quickest way: Docker Compose
  2. 17.2The database: SQLite or PostgreSQL
  3. 17.3HTTPS and reverse proxies
  4. 17.4Sign-in with OpenID Connect
  5. 17.5Storage
  6. 17.6Run retention
  7. 17.7Integrations configured on the server
  8. 17.8Migrating from QMetry
  9. 17.9Health

argus-server is the team's system of record: test cases, cycles, executions, requirements, releases and defects, every run's results and evidence, and the web app. It is a single Rust program on SQLite or PostgreSQL 15 or later, with evidence on disk or in Azure Blob Storage. This chapter is for whoever runs it; using the web app is chapter 18.

The complete reference with every setting and API route is docs/server.md.

17.1 The quickest way: Docker Compose

deploy/server/ is all a machine needs: docker-compose.yml runs the argus-server image and a PostgreSQL 17 for it.

cp .env.example .env                 # set POSTGRES_PASSWORD and ARGUS_DB_PASSWORD
cp server.env.example server.env     # optional: sign-in, GitLab, Jira, storage
az acr login --name acrdeve1         # or docker login to your registry
docker compose up -d --wait
docker compose run --rm server token --email you@example.com --admin   # the first admin's token

Open http://<machine>:8080 (ARGUS_PORT) and sign in with that token.

Every day
Logs docker compose logs -f server
Settings changed in server.env docker compose up -d
New version docker compose pull server && docker compose up -d (migrations run at start-up)
Back up the database docker compose exec db pg_dump -U postgres -Fc argus > argus-$(date +%F).dump
Back up evidence The argus_argus-data volume, unless ARGUS_STORAGE=azure

17.2 The database: SQLite or PostgreSQL

DATABASE_URL picks the database. Leave it unset and the server keeps everything in one SQLite file, argus.db in ARGUS_DATA_DIR, created on first start; a postgres:// URL puts it in PostgreSQL. Both hold the same data and run the same features, and you can move from one to the other at any time (17.2.2).

SQLite PostgreSQL
Set-up None: a file next to the evidence A database and a login (below)
DATABASE_URL unset, sqlite:/var/lib/argus/argus.db, or a path ending in .db postgres://argus:<password>@<host>:5432/argus
Suits One server for a team; trying Argus; a laptop Many users at once; more than one server on one database; managed backups
Back up Copy the file while the server is stopped, or sqlite3 argus.db ".backup argus-copy.db" while it runs pg_dump

17.2.1 SQLite

argus-server token --email lead@example.com --admin      # creates data/argus.db
argus-server serve --listen 0.0.0.0:8080 --data-dir /var/lib/argus

or with Docker, keeping the database on the data volume:

docker run -d --name argus-server -p 8080:8080 -v argus-data:/var/lib/argus/data acrdeve1.azurecr.io/agentops/argus-server
docker exec argus-server argus-server token --email you@example.com --admin

Only one server may use a SQLite file, and the file must be on a local disk, not a network share. The server writes it in WAL mode, so argus.db-wal and argus.db-shm next to it are part of the database while it runs.

17.2.2 Switching between SQLite and PostgreSQL

argus-server copy-db copies everything from one database into a new, empty one, in either direction. Ids are kept, so links, tokens, API keys and test keys keep working; evidence and attachments stay where they are (ARGUS_STORAGE).

# Stop the server first, so nothing changes during the copy.
argus-server copy-db --from /var/lib/argus/argus.db --to postgres://argus:<password>@<host>:5432/argus
# then start it with DATABASE_URL set to the new database

Back to SQLite is the same with --from and --to swapped. Each database gets this server's tables first; the copy refuses a target that already has data, and runs in one transaction, so a failed copy leaves the target empty. The old database is left as it was, so you can switch back.

17.2.3 Your own PostgreSQL

Make the argus login and database once:

psql -h <host> -U postgres -d postgres -v argus_password='<password>' -f deploy/server/server-database.sql

then run the server (Docker or the binary from the Windows install):

export DATABASE_URL=postgres://argus:<password>@<host>:5432/argus   # ?sslmode=require for Azure Database for PostgreSQL
argus-server token --email lead@example.com --admin
argus-server serve --listen 0.0.0.0:8080 --data-dir /var/lib/argus

serve reads ARGUS_LISTEN (default 0.0.0.0:8080) and ARGUS_DATA_DIR (default data). The web app is built into the server; --web-dir serves a folder's copy instead.

Migrations run at start-up, which needs a login that may create tables. When it may not, the database owner runs the script argus-server schema sql --grant <login> prints, and the server starts with --migrate check (ARGUS_MIGRATE=check): it only checks that every migration is there and otherwise refuses to start, naming the script to run. (schema sql is for PostgreSQL; a SQLite file is always created by the server itself.)

17.3 HTTPS and reverse proxies

The server speaks plain HTTP. Put a reverse proxy with the site's certificate in front (IIS, nginx, a load balancer) and set ARGUS_PUBLIC_URL to the HTTPS address; single sign-on and Jira links need it. Under a path of another site (https://tools.example.com/argus/), set ARGUS_PUBLIC_URL to that address and the server serves from /argus (or set ARGUS_BASE_PATH); the proxy may keep or strip the path.

location /argus/ { proxy_pass http://<machine>:8080/; }

CI and the CLI then use ARGUS_URL=https://tools.example.com/argus.

Behind an SSO proxy (Azure App Service authentication, oauth2-proxy), --trust-proxy (ARGUS_TRUST_PROXY_AUTH=1) accepts the user the proxy signed in (X-MS-Client-Principal-Name, X-Auth-Request-Email, X-Forwarded-Email). Only enable it behind such a proxy.

17.4 Sign-in with OpenID Connect

Argus signs people in with your identity provider directly (authorization code with PKCE). The documented setup is Duende IdentityServer; Microsoft Entra ID is a preset, and other providers with a discovery document work as generic. Personal tokens and API keys keep working alongside it.

Duende IdentityServer

Register three things: a confidential web client for the browser, a public device-flow client for argus login (CLI, MCP server, desktop), and an API resource the access tokens are for.

new ApiScope("argus.api", "Argus");
new ApiResource("argus.api", "Argus") {
    Scopes = { "argus.api" },
    UserClaims = { JwtClaimTypes.Name, JwtClaimTypes.Email, JwtClaimTypes.Role },
    ApiSecrets = { new Secret("<introspection secret>".Sha256()) },   // only for reference tokens
};
new Client {                                     // the browser
    ClientId = "argus-web",
    ClientSecrets = { new Secret("<client secret>".Sha256()) },
    AllowedGrantTypes = GrantTypes.Code, RequirePkce = true,
    RedirectUris = { "https://argus.example/auth/oidc/callback" },
    PostLogoutRedirectUris = { "https://argus.example/" },
    AllowedScopes = { "openid", "profile", "email", "roles" },
};
new Client {                                     // argus login, the desktop, argus mcp
    ClientId = "argus-cli",
    RequireClientSecret = false,
    AllowedGrantTypes = GrantTypes.DeviceFlow,
    AllowOfflineAccess = true,
    RefreshTokenUsage = TokenUsage.OneTimeOnly,
    AllowedScopes = { "openid", "profile", "email", "roles", "argus.api" },
};
// plus an IdentityResource "roles" carrying the `role` claim
ARGUS_PUBLIC_URL=https://argus.example
ARGUS_OIDC_PROVIDER=duende
ARGUS_OIDC_PROVIDER_NAME="Company SSO"           # the button: "Sign in with Company SSO"
ARGUS_OIDC_ISSUER=https://idsrv.example
ARGUS_OIDC_CLIENT_ID=argus-web
ARGUS_OIDC_CLIENT_SECRET=...                     # environment / secret store only
ARGUS_OIDC_SCOPES="openid profile email roles"
ARGUS_OIDC_CLI_CLIENT_ID=argus-cli
ARGUS_OIDC_DEFAULT_ROLE='*:viewer'
ARGUS_OIDC_ROLE_MAP='argus-admins=admin; qa-leads=EBM:lead; testers=EBM:tester,PAY:tester'
  • Users are matched by issuer + sub, so a changed email follows the same user.
  • The role map reads the role claim at every web sign-in (and at each new API token's first use): admin makes a server admin, EBM:lead a lead in project EBM. Mapped roles only ever raise a role, never lower one an admin set.
  • ARGUS_OIDC_DEFAULT_ROLE grants a new user's first roles.
  • Access tokens from argus login must be typ: at+jwt with aud and scope argus.api (ARGUS_OIDC_REQUIRE_AT_JWT=false relaxes the type). Reference tokens are checked at the introspection endpoint with ARGUS_OIDC_INTROSPECTION_SECRET.
  • ARGUS_LOCAL_SIGNIN=false makes the web app sign in only with single sign-on.
  • Web sessions last ARGUS_OIDC_SESSION_HOURS (default 12).

If argus login or the desktop says the provider "has no public client argus-cli" (invalid_client), that client is missing, needs a secret, or is disabled.

Microsoft Entra ID

Register an app (App registrations → New registration), redirect URI (Web) https://<ARGUS_PUBLIC_URL>/auth/oidc/callback, a client secret, optional app roles (Argus.Admin, Argus.Lead, Argus.Tester), and for the CLI and desktop Expose an API with a delegated scope access_as_user and Allow public client flows. Then ARGUS_OIDC_PROVIDER=entra and ARGUS_OIDC_ISSUER=https://login.microsoftonline.com/<tenant id>/v2.0. Servers configured with the older ARGUS_ENTRA_* variables keep working.

All OpenID Connect settings are listed in docs/server.md.

17.5 Storage

Evidence and attachments are stored by key (<project>/runs/<run>/evidence/<path>, <project>/attachments/<id>-<name>). ARGUS_STORAGE picks where:

  • local (default): files under ARGUS_DATA_DIR.
  • azure: block blobs in an Azure Blob Storage container. The server proxies every read and write, so access stays governed by Argus roles.
Variable
ARGUS_AZURE_STORAGE_ACCOUNT, ARGUS_AZURE_STORAGE_CONTAINER Required
ARGUS_AZURE_STORAGE_ENDPOINT Sovereign clouds, private endpoints, Azurite
ARGUS_AZURE_STORAGE_AUTH auto (default), managed-identity, workload-identity, client-secret, key or sas

The identity needs Storage Blob Data Contributor on the container. argus-server storage check tests the configuration; argus-server storage migrate --to azure --data-dir /var/lib/argus/data [--dry-run] copies existing files (run it before switching, and again after). Uploads are streamed, up to 64 MB each.

17.6 Run retention

Admin → Project → Run retention ({"days": 90, "keep_last": 20}) deletes a run when it started more than days ago and is not among the newest keep_last runs of its environment. Running runs and runs that a cycle execution or a defect links to are never deleted. The server applies it every ARGUS_RETENTION_INTERVAL_MINUTES (default 360); argus-server retention run [--dry-run] applies it now. Each deletion is in the audit log.

17.7 Integrations configured on the server

Integration Server settings Per project
GitLab (Run on GitLab, following pipelines, deployments) ARGUS_GITLAB_URL, ARGUS_GITLAB_PROJECT, ARGUS_GITLAB_REF, ARGUS_GITLAB_TRIGGER_TOKEN, ARGUS_GITLAB_API_TOKEN, ARGUS_GITLAB_WEBHOOK_SECRET (defaults), ARGUS_GITLAB_POLL_SECONDS (60), ARGUS_RUN_TIMEOUT_HOURS (12) Admin → GitLab
Jira ARGUS_JIRA_URL (the Jira for every project; people add their own email + token in My Jira), ARGUS_JIRA_USER, ARGUS_JIRA_TOKEN (shared sign-in, optional), ARGUS_JIRA_SYNC_MINUTES (60), ARGUS_JIRA_WEBHOOK_SECRET Admin → Jira
Assistant (web app) ARGUS_ASSISTANT_PROVIDER (anthropic, openai, openrouter, azure, azure-anthropic, openai-compatible), ARGUS_ASSISTANT_MODEL, ARGUS_ASSISTANT_BASE_URL, ARGUS_ASSISTANT_API_KEY, ARGUS_ASSISTANT_AUTH, ARGUS_ASSISTANT_API_VERSION; see 17.7.1 Admin → Assistant (server admins)
QMetry import ARGUS_QMETRY_API_KEY, ARGUS_QMETRY_URL Admin → QMetry import

Tokens and webhook secrets are read from the environment and are never stored, returned, logged or sent to the browser. The one exception is a project's own GitLab or Jira sign-in saved in the web app: kept in the database so the server can use it, never returned.

17.7.1 The assistant's model

The web app's assistant uses one model provider for the whole server. Pick it with ARGUS_ASSISTANT_PROVIDER:

  • anthropic: Claude from Anthropic. ANTHROPIC_API_KEY; the model defaults to claude-opus-5-5. A server with only ANTHROPIC_API_KEY set keeps using this.
  • azure: Azure OpenAI, or any model deployed in Azure AI Foundry. ARGUS_ASSISTANT_BASE_URL is the resource as the portal shows it (https://<name>.openai.azure.com or https://<name>.services.ai.azure.com), ARGUS_ASSISTANT_MODEL the deployment name. Sign in with a key (ARGUS_ASSISTANT_API_KEY) or, with no key, Microsoft Entra ID: the server's managed identity, or AZURE_TENANT_ID + AZURE_CLIENT_ID + AZURE_CLIENT_SECRET (ARGUS_ASSISTANT_AUTH picks one). Give the identity Cognitive Services OpenAI User (Azure AI User in Foundry). ARGUS_ASSISTANT_API_VERSION switches from the v1 API to the dated one.
  • azure-anthropic: Claude deployed in Microsoft Foundry, same sign-in.
  • openai, openrouter: their API key and a model (OpenRouter defaults to anthropic/claude-opus-5-5).
  • openai-compatible: any server with the OpenAI Chat Completions API (LiteLLM, vLLM, Ollama): its URL, a model and, if it needs one, a key.
ARGUS_ASSISTANT_PROVIDER=azure
ARGUS_ASSISTANT_BASE_URL=https://qa-ai.openai.azure.com
ARGUS_ASSISTANT_MODEL=gpt-5-argus        # the deployment name
# no key: the server's managed identity signs in

Restart the server after a change, then open Admin → Assistant (server admins): it shows the provider, model, endpoint and sign-in (never a key) and Test connection sends one short request. If the settings cannot be used, the assistant stays off and the tab says why. All variables are in docs/server.md.

17.8 Migrating from QMetry

QMetry is retired after a one-time import (Admin → QMetry import, admins). Three ways in, all writing through the same code:

  • REST pull (preferred): give the Jira project (key, name or id) and optionally the API URL; the API key comes only from the server's ARGUS_QMETRY_API_KEY. The job runs in the background, paging through folders, test cases (every version, with steps and requirements), cycles, executions (with history, step results, defects and attachments). Progress is saved after every page; a failed or interrupted job resumes where it stopped.
  • Excel export (.xlsx) as a fallback.
  • JSON upload shaped like QMetry's REST responses.

Running an import again is always safe: everything matches on its QMetry key, and unchanged versions are not written again. The acceptance check shows, for folders, test cases, steps, versions, requirements, cycles, executions, defects and attachments, how many QMetry had and how many Argus holds, with ok where they match.

17.9 Health

GET /api/health answers ok without signing in, for load balancers and probes.

Chapter 18
18

Using the web app

  1. 18.1Signing in
  2. 18.2Finding your way
  3. 18.3Home: what needs you today
  4. 18.4Repository and test cases
  5. 18.5Requirements
  6. 18.6Planning: cycles, plans and the player
  7. 18.7Runs and triage
  8. 18.8Defects
  9. 18.9Reports
  10. 18.10Performance
  11. 18.11The assistant
  12. 18.12Admin

The web app is argus-server's front end: open the server's address in a browser. It is where testers execute manual tests, leads plan cycles and releases, and everyone reads runs, defects and reports. Some screenshots in this chapter come from earlier builds and may differ in small details (newer builds have a Performance item in the navigation, for example).

Home
Figure 18.1 Home

18.1 Signing in

  • Sign in with when the server has single sign-on.
  • Otherwise paste a personal token (the first admin gets one from argus-server token; everyone can make more under My tokens).
  • Everyone who signs in can view every project; what more you can do depends on your role (chapter 21).

18.2 Finding your way

The left navigation has the project picker and: Home, Repository, Cycles (with Releases and Test plans), Runs (with Deployments), Requirements, Defects, Reports, Performance and Admin. The search box (Ctrl+K) searches test cases, cycles, runs, requirements and defects. The theme button switches light and dark. Help (or F1, or User manual in your account menu) opens this manual at help/ on the same server, in a new tab. The account menu also has My tokens and My Jira, where you add your own Jira sign-in so what you do in Jira is done as you (19.1). On a phone the navigation folds into a menu and tables scroll.

18.3 Home: what needs you today

  • The latest run, with its new failures and a Triage button.
  • My assigned executions, most urgent first, with due date, steps done, time spent and last result, grouped by cycle with your progress in each.
  • Retest queue: your failed or blocked executions whose defect was resolved after they last ran.
  • Cycles in progress and flaky tests to triage.

18.4 Repository and test cases

Repository lists the project's test cases in a folder tree and a fast table (it stays fast with 50,000 tests). Filter by folder, status, kind (manual, automated, hybrid), priority, owner, requirement (or none), tag, or last result failed; pick columns; select rows for bulk changes (priority, status, owner, folder, kind). New test case and Import are at the top.

Repository
Figure 18.2 Repository

A test case has tabs:

Tab
Steps Manual steps (action, expected result, data), edited in place; for automated tests, the YAML flow drawn as the desktop does (sent by argus sync), with a link to the file in GitLab.
Details Owner, priority, status, kind, tags, folder and the project's custom fields.
Links Requirements it covers.
History Every result, from runs and executions (QMetry history included).
Versions Every change, with a snapshot (each change bumps the version).

Buttons: Log defect, Add to cycle, and Assistant, which can propose better manual steps as a diff that a lead applies or discards.

A test case with the assistant's proposal
Figure 18.3 A test case with the assistant's proposal

Automated tests appear in the repository by themselves: a result for an unknown key creates the test case in the Unlinked folder. argus sync tests/ sends each test's YAML so its Steps tab shows the flow:

argus sync tests/            # ARGUS_URL, ARGUS_PROJECT and ARGUS_API_KEY or ARGUS_TOKEN

18.5 Requirements

Requirements are the stories tests prove, with their coverage (tests linked, and how they last did). They come from Jira: the project's requirements_jql keeps them in sync, and Import from Jira picks stories by project, type, sprint and words, optionally creating a draft manual test case for each uncovered story (chapter 19). Requirements can also be made by hand. Leads and project admins can Remove one from its page: its test links go, the test cases and runs stay, and Jira sync and CI uploads leave it out until it is imported or linked again (19.3).

Import stories from Jira
Figure 18.4 Import stories from Jira

18.6 Planning: cycles, plans and the player

Test plans are reusable sets of tests: static (a list of keys) or filter (a saved repository filter, so new matching tests join automatically).

Cycles are rounds of testing: a sprint's regression, a release candidate, a nightly. Create one from scratch or from a test plan, with environments and platforms (e.g. QACore × Chrome, QAStage × Edge), dates and a release. The cycle page shows progress and an environment × platform matrix; Add tests (also "add a plan's tests") assigns executions to testers. Automated results land in the cycle when a run is recorded against it (?cycle= on upload, or record in a cycle on Run on GitLab).

A cycle with its matrix
Figure 18.5 A cycle with its matrix

The nightly cycle (Admin → Project → Nightly cycle) collects each night's scheduled runs in one cycle, created on first use (Nightly {date}), optionally starting with a plan's tests so the ones that didn't run show as not run.

The execution player walks a tester through a manual test step by step: the action, expected result and test data; actual result (required for Fail or Blocked); paste a screenshot (Ctrl+V), drop a file or click to upload, then mark up screenshots in the browser (arrows, boxes, text, blur); Pass, Fail, Blocked or Skip per step, with keyboard shortcuts. The automated part of a hybrid test is shown above the manual steps. Time spent is recorded, each final result is kept as an attempt (running it again loses nothing), and Log defect files a defect with the step's evidence.

The execution player
Figure 18.6 The execution player

18.7 Runs and triage

Runs lists every run from every source (CI pipelines, the CLI, the Robot listener, JUnit and output.xml uploads, the web app's own Run on GitLab), with environment filters, verdict, trigger, build, counts, new failures and duration. Each row says what ran and why (scope and note).

Runs
Figure 18.7 Runs
  • Run on GitLab starts the tests pipeline: environment, tags, paths, a run name, and whether to record results in a cycle. The run shows as queued, then running, then fills in. It needs Admin → GitLab set up.
  • A run's page has the results, the HTML report, evidence, and a Performance card when it has load or page-speed steps.
  • Re-run failed starts the pipeline for just the failed tests.
  • Cancel stops a running run and its GitLab pipeline.
  • Delete (project admins) removes a run and its evidence; a run that executions or defects link to asks again (Delete anyway and unlink).
Run on GitLab
Figure 18.8 Run on GitLab

Review (a button on Runs, with a count) lists what recent runs changed on their own: healed locators and new or changed screenshots, with the baseline, this run's screenshot and the difference side by side. People who can run tests approve or reject them. An approved screenshot is the project's baseline from then on, on the server; argus review pull brings approved baselines and accepted locators into the repository. See 9.13.

Review in the web app
Figure 18.9 Review in the web app

Triage by root cause. A run's failures are grouped by error signature, with errors nobody has categorised first. For each group set a category (Product bug, Test bug, Environment, Flaky), a note, or a defect. The decision is kept for that error, so later runs with the same error arrive already categorised (in their results and report). A failure whose defect is open shows it; one that fails again after its defect was resolved says so, with Reopen.

18.8 Defects

Defects are logged from a run, a failed step, an execution, the desktop app or the assistant, with the test, step, expected and actual, evidence and run. With Jira set up, file in Jira creates the issue, attaches the evidence and links back; Jira's status comes back by sync and webhook (chapter 19). To point at a bug that is already in Jira, pick it in the dialog's Existing Jira issue field: it lists the project's open bugs, finds any key, and searches by words (19.4). The Defects page lists them by status and severity.

A defect filed in Jira
Figure 18.10 A defect filed in Jira

18.9 Reports

Report Shows
Traceability Requirements → tests → latest results
Release readiness A release's verdict and reasons by its gate: requirements passing, failing, not run or uncovered; its cycles; open defects by severity; the latest sign-off (chapter 20)
Cycle progress By tester, environment × platform and priority; executed per day
Pass-rate trend Pass rate over time per environment
Flaky tests Tests that flip between pass and fail
Tester workload Open executions per tester

Every report downloads as CSV or Excel, and prints to PDF.

Release readiness
Figure 18.11 Release readiness

18.10 Performance

The Performance page keeps load and page-speed results over time, with comparisons and uploads from JMeter, k6 and Lighthouse (12.4). GitLab pipelines send load and page-speed results there as they run (16.8).

18.11 The assistant

The Assistant button opens a chat about what you are looking at (a cycle, a test case, the project): "which stories have no passing test?", "summarize the failures and blocked executions". It reads test cases, runs, cycles and requirements as you, and can only propose new manual steps, which a lead applies. Connection settings and secrets are never part of what it sees. It uses the model the server is set up with: Anthropic, OpenAI, OpenRouter, Azure OpenAI / Azure AI Foundry or any OpenAI-compatible server (17.7.1).

18.12 Admin

Tab Who
Members Project admins Give people tester or lead (pick from everyone Argus knows, or add an email).
API keys Project admins Keys for CI to upload results (shown once).
Environments Project admins The project's environments and platforms, and restricted environments (who may run or upload results there).
Connections Project admins Connection names the project knows.
Jira Project admins Site (read-only when the server sets ARGUS_JIRA_URL), project, issue type, requirements JQL, an optional shared Jira sign-in, Everyone uses their own Jira sign-in, your own sign-in (Your Jira), Test connection, Sync now (19.2).
GitLab Project admins GitLab URL, tests project, ref, trigger and API tokens, webhook secret, Test connection, Tests after a deployment.
Custom fields Project admins Extra test case fields: text, number, select, date or user, optionally required.
Project Project admins Name, nightly cycle, run retention.
QMetry import Project admins The one-time import and its acceptance check (17.8).
Audit log Everyone Every change in the project.
Project admins Server admins Every project with its admins.
Server admins Server admins Who administers the whole server.
Assistant Server admins The assistant's model provider, model, endpoint and sign-in, and Test connection.
Sign-in audit Server admins Sign-ins, failed sign-ins and sign-outs.
Admin → GitLab
Figure 18.12 Admin → GitLab
Chapter 19
19

Jira

  1. 19.1Your own Jira sign-in
  2. 19.2Connecting a shared Jira on the server
  3. 19.3Importing stories
  4. 19.4Defects in Jira
  5. 19.5Jira versions as releases
  6. 19.6Jira in the desktop app

Argus works with Jira in two places, set up separately:

Where What it does Sign-in
argus-server Requirements from Jira stories (sync and import), defects filed as Jira issues with evidence, Jira status coming back, Jira versions as releases. Jira URL set once on the server (ARGUS_JIRA_URL); each person's own email + API token (My Jira, no admin needed), else the project's shared sign-in (Admin → Jira), else the server's ARGUS_JIRA_USER / ARGUS_JIRA_TOKEN. Jira Cloud (email + API token) or Jira Server / Data Center (a personal access token).
Desktop app and argus mcp Jira issues in the assistant chat, the jira_* tools, defects filed straight in Jira when no server is used. Connections → Jira (site, account email, API token), kept as ARGUS_JIRA_* local variables, the token in the keychain. Jira Cloud.

When the desktop is signed in to argus-server with ARGUS_PROJECT set, its defects go through the server (and to Jira from there).

19.1 Your own Jira sign-in

The Jira URL is set once on the server by whoever runs it (ARGUS_JIRA_URL, 17); nobody else sets or changes it. Each person then adds only their own account: click your name (top right) → My Jira and enter

  • your email and API token (Jira Cloud), or only a personal access token (Jira Server / Data Center),
  • a default Jira project for the defects you file when the Argus project names none.

No admin step is needed. Save and test signs in and says as whom. The token is stored write-only and never shown again. From then on what you do in Argus happens in Jira as you: stories you import (the requirement also shows imported by you), defects you file and the evidence attached to them, Jira versions you import or release, and Sync from Jira when you press it. The import dialog says whose account it uses.

A shared sign-in (19.2) is optional: people who haven't added their own use it, and background syncs and the webhook always do. A project admin can tick Everyone uses their own Jira sign-in in Admin → Jira, so nobody acts in Jira through the shared account.

Without ARGUS_JIRA_URL, the Jira URL a server admin set on a project is used instead, and My Jira offers those.

19.2 Connecting a shared Jira on the server

Admin → Jira (project admins):

  1. Site (https://<company>.atlassian.net; shown read-only when the server sets ARGUS_JIRA_URL), project and issue type for defects. A project admin may set a Jira Cloud URL; a self-hosted Jira URL has to be set by a server admin, since the server's own Jira credentials could reach it.
  2. Shared connection (optional): the project's own sign-in. With an email the token is sent as Basic auth (Jira Cloud: email + API token); without one it is a Bearer personal access token (Server / Data Center). It is stored write-only and never shown again.
  3. Test connection signs in and says as whom.
  4. Requirements JQL (project = EBM AND type = Story): issues it finds become requirements (key, summary, status, link, first fix version as the release).
Connect Jira
Figure 19.1 Connect Jira

Sync runs every ARGUS_JIRA_SYNC_MINUTES (default 60), and on demand with Sync from Jira now.

Jira's webhook. Point a Jira webhook at POST /api/hooks/jira, signed with ARGUS_JIRA_WEBHOOK_SECRET (X-Hub-Signature, or ?secret=), with issue events (and Version events for 19.5). Then status changes arrive at once: done or resolved resolves the defect, closed closes it, in progress reopens work on it.

19.3 Importing stories

Requirements → Import from Jira lists a Jira project's issues, filtered by type, sprint, open/all and words or a key, and imports the ones you pick as requirements with their type and description. With create tests, each story no test covers yet gets a draft manual test case linked to it. Imported stories stay in sync.

Imported story
Figure 19.2 Imported story

To take a story back out, open it under Requirements and press Remove (leads and project admins). The confirm lists the test cases linked to it and how many runs have results for them: the links go, the test cases and runs stay. Nothing changes in Jira. Sync from Jira and CI uploads that name the story leave it out from then on, and the import list marks it Removed; importing it again, creating it by hand or linking a test to it by hand brings it back.

19.4 Defects in Jira

Logging a defect with file in Jira creates the issue, attaches the evidence (the execution's attachments or the run's evidence files, up to 20 MB each, and desktop screen captures) and adds a remote link back to the run or execution in Argus (which needs ARGUS_PUBLIC_URL). The defect shows its Jira key and status.

To link a Jira issue that already exists instead, use Existing Jira issue in the Log defect dialog. Opening it lists the open bugs of the project's Jira project (else your default one from My Jira), newest first; typing a key finds that issue anywhere, typing words searches Jira, closed issues included. The picked or typed issue is shown under the field with its summary, status, priority and assignee and a link to it. The search runs with your own Jira sign-in when you saved one. Without Jira on the server the field takes a typed key and says why it can't search.

19.5 Jira versions as releases

Releases → Import from Jira lists the project's versions and imports the ones you pick (or every unreleased, unarchived one) as releases, with description, dates and status. A release with the same name is linked rather than duplicated. Linked releases follow their version on every sync and on the webhook's version events: a rename renames the release everywhere it is used, a deleted version unlinks it. Mark released in Jira on the release page sets the Jira version released.

Import Jira versions
Figure 19.3 Import Jira versions

19.6 Jira in the desktop app

Connections → Jira (or the palette's Connect Jira…): site, account email and API token. Behind a proxy, the system proxy is used.

Jira settings
Figure 19.4 Jira settings
  • Jira issue in the assistant composer: project → sprint → issue. The issue goes into your message with its description, images and the tests that already cover it (tests whose requirements list the key). Pick several for one message (13.7).
  • Log defect files straight in Jira when no argus-server is used, linked to the test's requirement keys.
  • The assistant's jira_get_issue, jira_search, jira_create_issue, jira_comment and jira_link_test tools use the same settings; the token never reaches the agent.
A Jira issue in the chat
Figure 19.5 A Jira issue in the chat
Chapter 20
20

Releases, gates and deployments

  1. 20.1Releases
  2. 20.2The quality gate
  3. 20.3Asking the gate from a pipeline
  4. 20.4Restricted environments
  5. 20.5Deployments and tests after a deploy
  6. 20.6GitLab set-up for the server

A release in argus-server is a version you ship (26.5). Cycles, requirements, test plans and deployments name it, and its page answers one question: can we ship?

From requirements to a release
Figure 20.1 From requirements to a release

20.1 Releases

Cycles → Releases lists releases as planned, active or released, including names only used on cycles or requirements. Create one by hand or import Jira versions (19.5). Renaming a release renames it everywhere it is used.

A release's page shows its gate verdict and reasons, readiness, cycles, deployments per environment, open defects and sign-offs.

A release with its gate and sign-offs
Figure 20.2 A release with its gate and sign-offs

20.2 The quality gate

Each release has a gate (Gate… on the release page, leads):

{ "min_passing_pct": 95, "min_coverage_pct": 90,
  "max_open_defects": { "blocker": 0, "critical": 0, "major": 2 },
  "cycles_done": true, "require_signoff": true }
Rule Passes when
min_passing_pct At least this share of the release's requirements pass
min_coverage_pct At least this share of requirements have tests
max_open_defects Open defects per severity stay within the limits (a severity not named is not limited)
cycles_done Every cycle of the release is finished
require_signoff The newest sign-off is go

Anything left out keeps the default, and {} is the default gate: every requirement covered and passing, no open blocker or critical defect.

Editing the gate
Figure 20.3 Editing the gate

Sign-off. Leads record Sign off: go… or No-go… (a no-go says why), kept with the readiness it was made on.

Releasing. Marking a release released while it fails its gate is refused with the reasons, unless you give an override reason, which goes into the audit log with them. released_at records when it shipped.

Release anyway, with a reason
Figure 20.4 Release anyway, with a reason

The Release readiness report shows the same judgment with its details, and prints or downloads (18.9).

20.3 Asking the gate from a pipeline

A deploy job can ask before shipping:

export ARGUS_URL=https://argus.example ARGUS_PROJECT=EBM ARGUS_API_KEY=…   # a project API key may ask
argus gate "$RELEASE"          # exit 0 when it passes, 1 when not
argus gate "$RELEASE" --json   # the full answer

or GET /api/projects/{p}/gate?release=26.5, which answers {pass, reasons, gate, summary, open_defects, signoff, url}.

# .gitlab-ci.yml of the application
release-gate:
  stage: deploy
  image: acrdeve1.azurecr.io/agentops/argus:core
  script:
    - argus gate "$CI_COMMIT_TAG"
  rules:
    - if: $CI_COMMIT_TAG

20.4 Restricted environments

Admin → Environments can restrict an environment to some roles, such as {"prod": ["lead", "api_key"]} (api_key lets CI keys upload). Project and server admins are always allowed. It is enforced on Run on GitLab, on every results upload (output.json, JUnit, Robot, events, evidence) and on recording manual executions and attachments.

20.5 Deployments and tests after a deploy

Add Deployment events to the GitLab webhook of each application repository whose deployments matter (/api/hooks/gitlab, with the project's webhook secret; with the server's secret add ?project=KEY). Each deployment is recorded with its environment, status, ref, commit, job and who deployed it, on the Deployments page (Runs → Deployments). A ref that names a release (26.5, v26.5, release/26.5) puts the deployment on that release's page.

Deployments
Figure 20.5 Deployments

Tests after a deployment (Admin → GitLab) starts the tests pipeline after a successful deployment:

"deploy_runs": [
  { "environment": "staging", "env": "qa2", "tags": "smoke", "paths": ["tests/api"], "cycle": "nightly" },
  { "environment": "review/*", "env": "qa2", "tags": "smoke" }
]

environment is the GitLab environment (a trailing * matches a prefix), env the Argus environment the tests run against. The run is named After deploy to staging, and the deployment links to it. A deployment event GitLab sends again starts nothing new.

Tests after a deployment
Figure 20.6 Tests after a deployment

20.6 GitLab set-up for the server

Admin → GitLab (per project): the GitLab URL, the tests project, the ref (default main), a pipeline trigger token (from the tests repository's Settings → CI/CD → Pipeline trigger tokens), an API token (read_api to follow pipelines, api to cancel them) and a webhook secret.

  • Following pipelines: the server polls GitLab every ARGUS_GITLAB_POLL_SECONDS (default 60), or takes Pipeline events on /api/hooks/gitlab. A pipeline that fails without results marks the run failed; one that is canceled marks it canceled. Runs still running after ARGUS_RUN_TIMEOUT_HOURS (12) are closed as failed.
  • Test connection checks the settings, the API token (as whom it signs in), the tests project and the ref, one by one.
GitLab test connection
Figure 20.7 GitLab test connection
Chapter 21
21

Roles and access

  1. 21.1Project roles
  2. 21.2Who gives which role
  3. 21.3Tokens and keys
  4. 21.4Restricted environments
  5. 21.5Audits
  6. 21.6What the desktop and CLI can see

21.1 Project roles

Role May
viewer Read everything in the project; change nothing. Everyone who signs in is a viewer of every project.
tester Execute tests, upload results, start runs on GitLab, cancel runs, file defects, triage failures.
lead Everything a tester may, plus author test cases, folders, requirements, plans, cycles and releases, assign work, sign off releases, set gates.
admin (project admin) Everything, plus members, API keys, environments, integrations (Jira, GitLab), custom fields, project settings, QMetry import, deleting runs.
Roles in a project
Figure 21.1 Roles in a project

A project's members are the people given more than viewer. GET /projects lists every project, with member: false where you hold no role.

21.2 Who gives which role

  • Server admins are admin everywhere. Only they make, change or remove a project admin: in Admin → Project admins (every project with its admins) or in the project's Members tab.
  • Project admins give people tester or lead in Admin → Members, picking from everyone Argus knows or adding a new email; taking it back leaves them a viewer.
  • Server admins make or remove other server admins in Admin → Server admins, for people who have signed in at least once. Nobody removes their own, and the last one stays.
  • The first user is a server admin. With single sign-on, the role map (ARGUS_OIDC_ROLE_MAP) can grant roles and server admin from the provider's groups or roles; mapped roles only ever raise a role (17.4).
Server admin: project admins
Figure 21.2 Server admin: project admins
Project admin: members
Figure 21.3 Project admin: members

Every change is in the project's audit log.

21.3 Tokens and keys

Credential Acts as Use it for
Personal token (My tokens) You, with your roles The CLI (ARGUS_TOKEN), scripts, signing in to the web app without SSO
Single sign-on (argus login, the desktop's Sign in) You The CLI, argus mcp and the desktop; refresh token kept in the OS keychain
Project API key (Admin → API keys, shown once) The project's CI Uploading results and evidence, reading runs, reports, history and manual results (for argus report diff, --history-from, argus gate). Nothing else.

Send them as Authorization: Bearer <token>.

21.4 Restricted environments

An environment can be limited to some roles ({"prod": ["lead", "api_key"]}); see 20.4.

21.5 Audits

  • Project audit log (Admin → Audit log, everyone in the project): every change, including retention deletions and gate overrides.
  • Sign-in audit (Admin → Sign-in audit, server admins): sign-ins (OIDC, first use of an API access token, personal-token sign-ins), sign-outs, and failed sign-ins with a reason (expired, state_mismatch, token_rejected, revoked_token …). Tokens, codes and secrets are never recorded.

21.6 What the desktop and CLI can see

The desktop app and the CLI run on your machine with your files; they need no role.

Part VI

Reference

Variables, files, shortcuts and what to do when something goes wrong.

Chapter 22
22

Reference

  1. 22.1Environment variables
  2. 22.2Files and folders
  3. 22.3Exit codes
  4. 22.4Keyboard shortcuts
  5. 22.5Statuses

22.1 Environment variables

Running tests (CLI, desktop, CI)

Variable Effect
ARGUS_TIMEOUT Default step timeout for the run (2m, none)
ARGUS_TEST_TIMEOUT Each test's time limit (30m, none)
ARGUS_RUN_TIMEOUT The whole run's time limit (2h, none)
ARGUS_HEADED=1 Headed browsers for Playwright and Robot
ARGUS_WORKERS Tests at once (4, auto)
ARGUS_RUN_NOTE Why the run happened
ARGUS_TRIGGER What started it (local, pipeline, schedule, merge_request, web, ai)
ARGUS_REQUIREMENTS Requirement keys the run covers (EBM-412,EBM-415)
ARGUS_VERBOSE More output
ARGUS_EMAIL_REDIRECT Send every email step's message to this one address
ARGUS_MCP_CONFIG An mcpServers file to use instead of mcp.config
ARGUS_SECRET_<ITEM> The value of ${keychain:<item>} where there is no keychain (CI)
ARGUS_KEYCHAIN_SERVICE Keychain service name (default argus)
ARGUS_SECRET_TOOL Another program instead of secret-tool on Linux
AZURE_KEYVAULT_URL / AZURE_KEYVAULT_NAME, AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_AUTHORITY_HOST ${keyvault:…}
ARGUS_EVIDENCE_DIR Set for script steps: files written there become evidence

Programs Argus runs

Variable Overrides
ARGUS_PYTHON, ARGUS_NODE, ARGUS_NPX Interpreters for script steps and MCP servers
ARGUS_PLAYWRIGHT Playwright command (default npx playwright test)
ARGUS_PYTEST pytest command for Python Playwright specs
ARGUS_ROBOT Robot Framework command
ARGUS_SSH, ARGUS_SFTP OpenSSH programs
ARGUS_KUBECTL, ARGUS_AZ, ARGUS_KUBELOGIN Kubernetes tools
ARGUS_PATH Extra folders to look for programs in
ORACLE_CLIENT_LIB Oracle Instant Client folder
ARGUS_BROWSER, ARGUS_BROWSER_HEADLESS=1 Browser for the recorder and browser tools; hide it
ARGUS_HEAL=off, ARGUS_HEAL_GRACE_MS, ARGUS_HEAL_WAIT_MS, ARGUS_HEAL_MIN_SCORE Self-healing locators in recorded specs (see 9.13)
ARGUS_ADB (else ANDROID_HOME, ANDROID_SDK_ROOT, PATH) adb for mobile-app steps and the mobile tools (6.22)
ARGUS_IDB idb for iOS simulators (macOS)

argus-server from clients

Variable
ARGUS_URL The server's address (with its path when served under one)
ARGUS_PROJECT The project key (EBM)
ARGUS_TOKEN A personal token
ARGUS_API_KEY A project API key (CI)
ARGUS_ENV Environment name (Robot listener, CI jobs)
ARGUS_RUN_ID, ARGUS_TAGS, ARGUS_PATHS, ARGUS_TESTS, ARGUS_CYCLE Passed to pipelines by Run on GitLab
ARGUS_MASK_VARS, ARGUS_MASK_PATTERNS More masking for the Robot listener

GitLab from clients

Variable
GITLAB_URL, GITLAB_PROJECT_ID, GITLAB_TRIGGER_TOKEN Start the tests pipeline (desktop Run on GitLab, argus run --remote, run_test where: gitlab)
GITLAB_TOKEN Follow and cancel pipelines; Commit… merge requests (api scope)
GITLAB_REF, GITLAB_TARGET_BRANCH Branch to run on; merge request target
ARGUS_IMAGE_CORE, ARGUS_IMAGE_FULL, ARGUS_IMAGE Runner images in generated pipelines
ARGUS_SKIP=1 Leave the Argus jobs out of a pipeline

Desktop app

Variable
ARGUS_DESKTOP_SETTINGS Path of desktop.json
ARGUS_DESKTOP_DATA The app's data folder
ARGUS_THEME=light Start in the light theme
ARGUS_LOW_POWER=1 / 0 Force low-power drawing on / off
ARGUS_KEEP_RUNNING=0 / 1 Closing the window quits / keeps Argus running in the tray (default on; keep_running in desktop.json)
ARGUS_UPDATE_URL Update feed; off disables update checks
ARGUS_UPDATE_TRACE=1 Explain quiet update checks
ARGUS_CA_CERTS Extra PEM CA files (;-separated)
ARGUS_PROXY direct, system, or the proxy URL for every connection; else HTTPS_PROXY/NO_PROXY, then the Windows proxy (PAC/WPAD)
ARGUS_ASSISTANT Which CLI the assistant drives
ARGUS_CLAUDE, ARGUS_CODEX, ARGUS_GEMINI Paths of the CLIs
ARGUS_BIN The argus program the assistant runs as its MCP server
ARGUS_JIRA_URL, ARGUS_JIRA_EMAIL, ARGUS_JIRA_TOKEN, ARGUS_JIRA_PROJECT, ARGUS_JIRA_ISSUE_TYPE Jira for the desktop and argus mcp
ARGUS_PERF, ARGUS_PERF_FRAMES, ARGUS_PERF_IDLE Performance measurement of the app itself

Installer

ARGUS_INSTALL_SILENT, ARGUS_INSTALL_VERSION, ARGUS_INSTALL_BASE_URL, ARGUS_INSTALL_ROOT, ARGUS_INSTALL_NO_LAUNCH (2.1).

argus-server

The server's settings (DATABASE_URL, ARGUS_LISTEN, ARGUS_DATA_DIR, ARGUS_PUBLIC_URL, ARGUS_OIDC_*, ARGUS_STORAGE, ARGUS_AZURE_STORAGE_*, ARGUS_GITLAB_*, ARGUS_JIRA_*, ARGUS_QMETRY_*, ARGUS_ASSISTANT_* …) are in chapter 17 and docs/server.md.

22.2 Files and folders

Path What
argus.yaml Project configuration (3.3)
*.yaml with id + steps Tests, flows (kind: flow), automations (kind: automation)
<test>.layout.json Canvas positions, beside its test (commit it)
payloads/ The payload library
data/ Data sets (any folder works)
.argusignore Skip this folder when looking for tests
argus-results/ Run output: output.json, junit.xml, report.html, events.ndjson, evidence/, history/ (don't commit)
argus-results/automations/<id>/ Automation runs
argus-results/browser/, argus-results/checks/, argus-results/suites/ Assistant browser sessions, ad-hoc check reports, run_suite reports
argus-results/apps/ What the assistant did in desktop and mobile apps, with screenshots (computer_save_test reads it)
argus-results/review/review.json Healed locators and visual changes waiting for review (9.13)
visual-baselines/ Approved screenshots for visual checks (commit it)
.gitlab/argus.gitlab-ci.yml Generated CI jobs
IMPORT.md, MIGRATION.md, UPGRADE.md, legacy/ Written by imports, migrations and upgrades
desktop.json Desktop settings (2.7)
local-env.json Local variables (plain values) in the desktop's data folder

22.3 Exit codes

Code Meaning
0 Success: every test passed, the gate passes, the comparison agrees
1 A test failed; the gate fails; the runs disagree
2 Configuration or infrastructure error: invalid YAML, unknown environment or connection, unreachable system, missing program

22.4 Keyboard shortcuts

⌘ is Ctrl on Windows and Linux.

Anywhere

Keys
F1 This manual, in your browser (works offline)
⌘K Command palette
⌘O Open a folder
⌘1 … ⌘8 Explorer · Run view · Connections · API explorer · Repository · Files · Project settings · Payloads
⌘J Toggle the assistant
⇧⌘J New assistant conversation
⌥⌘B Collapse or open the side panel
⇧⌘L Light / dark theme
⌘R Run the open test locally
⇧⌘↵ Run the selected folder
⇧⌘R Run on GitLab
Ctrl+` / Ctrl+Shift+` Terminal / new terminal
Esc Close a menu or dialog, clear the selection

Canvas and inspector

Keys
/ Step palette
⌘↵ Run the selected step
⌘\ Canvas / Split / YAML
⌘Z, ⇧⌘Z / ⌘Y Undo, redo
⌘A Select all steps
⌘C, ⌘V Copy, paste steps
⌘D Duplicate steps
Delete, ⌘⌫ Delete steps
⌥↑, ⌥↓ Move the step up, down
Shift+arrows Nudge the selection
⌘+, ⌘-, ⌘0, ⌘9 Zoom in, out, 100 %, fit
Shift+Alt+F Format JSON in a field

Run view: J / K next / previous failure.

Explorer: Delete / ⌘⌫ deletes the selected test or folder.

Files and editors: Ctrl+F find, Ctrl+H replace, Ctrl+G go to line, Alt+Z wrap, Ctrl+S save, Ctrl+W close, Ctrl+Shift+T reopen, Ctrl+Space complete, Shift+Alt+F format, F2 rename, ⌘C / ⌘X / ⌘V / ⌘D on tree rows, ⇧⌘F search in files.

Assistant composer: Enter send (steer while running), Ctrl+Enter queue, ↑ history, @ files, / commands.

Terminal: Ctrl+Shift+C / Ctrl+Shift+V copy / paste.

22.5 Statuses

Where Statuses
Test and step results PASS, FAIL, FLAKY, SKIP, PENDING_MANUAL, BLOCKED, NOT_RUN, RUNNING
Runs on the server as above, plus CANCELED
Executions NOT_RUN, IN_PROGRESS, PASS, FAIL, BLOCKED, SKIP, PENDING_MANUAL
Automations Succeeded, Failed
Failure categories Product bug, Test bug, Environment, Flaky, Needs triage
Chapter 23
23

Troubleshooting

Symptom Cause and fix
environment variable QA2_PASSWORD is not set argus.yaml reads ${QA2_PASSWORD} and nothing sets it. On the desktop set it on Connections → Variables (the tab lists every missing name); on the command line export it; in CI add it as a masked CI/CD variable. argus connections env --env qa2 tests/ lists them all.
{{env.dev_password}} is undefined No environment defines dev_password and no variable of that name is set. Set a local variable dev_password, or add it to the environment's vars.
A ${keychain:…} secret is missing in CI CI has no keychain: add ARGUS_SECRET_<ITEM> (upper case, other characters _). The error names it.
Robot Framework is not installed Click Install Robot Framework under the step, or Connections → Tools on this computer, or python -m pip install robotframework. ARGUS_ROBOT points at a specific robot.
A Playwright step says npx / @playwright/test is missing Run npm install and npx playwright install chromium in the tests folder (the recorder writes the package.json).
Test timeout of 30000ms exceeded in Playwright The step's limit is Playwright's test timeout. Raise it on the step, the test, its folder, argus.yaml timeouts.playwright, or the run (Step timeout, --timeout); none turns it off (9.3).
Headed run shows no browser Turn on Show the browser (or --headed). For Robot, Robot Framework 7+ is needed to force browsers visible; Robot 6 only gets ${HEADLESS}.
A step "ran past its timeout" / Stop did nothing SQL statements are cancelled on the server, and HTTP, Kafka and Redis steps stop waiting on Stop. Robot and pytest steps are only ended by a limit set for them (step, test, folder or timeouts.robot).
A step failed with [time limit …, from …] It ran into its limit; the message says where that limit was set. Change it there, or closer to the step (9.3).
desktop-app: "No running app with a window matches …" / "The app is not running" Give the step a launch: command, or use the name the system shows (on Linux often the process name, such as Shop.py) or the window's title. computer_list_apps in the assistant lists what it can see.
desktop-app on macOS: "Allow Argus in System Settings → Privacy & Security → Accessibility" Allow Argus (or the terminal that runs argus) under Accessibility and Screen Recording, then run again.
desktop-app on Linux: "The accessibility bus is not running" Run in a desktop session with AT-SPI (at-spi2-core); native Wayland windows can't be driven, XWayland and X11 ones can.
mobile-app: "No emulator or device is running" / adb not found Start the emulator first (Android Studio → Device Manager, or emulator -avd <name>). Install the SDK platform-tools, or set ARGUS_ADB / ANDROID_HOME.
SSH step fails with a host key error in CI CI uses host-key-checking: yes. Give known_hosts (from ssh-keyscan you checked), or insecure_ignore_host_key: true for lab hosts.
An mcp step's server doesn't start The message names the server, the command line and its last stderr lines. argus check --env qa2 shows whether each server starts. In CI, stdio servers needing Node.js or Python need argus:full.
{{payload.x}} not found The library is payloads/ beside argus.yaml. The error lists the closest names. Names have no extension unless two files share one.
Closed the window but schedules still run, or Argus "won't start" Closing the window keeps Argus running in the tray, and starting it again shows that window. Quit from the tray icon's menu (or argus-desktop --quit), or turn Keep running in the tray when the window is closed off in the palette (4.13).
No Argus icon in the tray On Windows look under the ^ arrow in the notification area. On GNOME install the AppIndicator extension. Starting Argus again still brings the window back.
Tests changed on disk don't show The app watches the folder; click Refresh in the explorer or Reload tests from disk.
The app is slow over Remote Desktop or in a VM Low-power drawing should switch on by itself there; ARGUS_LOW_POWER=1 forces it.
Sign-in, updates, Jira or GitLab: "TLS certificate not trusted" Put your proxy's CA (Netskope) PEM in ~/.argus/certificates/ or ~/.agentops/certificates/netskope.pem, or set ARGUS_CA_CERTS (2.2).
"could not connect … going direct" while the browser works The browser probably uses a proxy Argus didn't find: set ARGUS_PROXY (or HTTPS_PROXY) to it (Proxies). Otherwise a firewall or endpoint security is blocking Argus.
"… through proxy X (from HTTPS_PROXY)" fails An old HTTPS_PROXY may be set; remove it, or set ARGUS_PROXY=system to use the Windows settings instead.
argus login: the provider "has no public client argus-cli" The device-flow client is missing, needs a secret, or is disabled in IdentityServer (17.4).
Web Run on GitLab runs stay "running" Set the project's API token or the GitLab webhook so the server can follow pipelines (20.6).
Results upload is refused (403) for an environment The environment is restricted (20.4); use a role or API key that is allowed.
Exit code 2 in CI Configuration or infrastructure: read the first error line. argus validate and argus check in the job narrow it down.

Getting more detail

  • argus run -v prints more as it runs.
  • argus report show --failed argus-results/output.json prints failures with their messages.
  • In the desktop, the drawer's tabs show the full request, response, output and log of any step; the assistant can read them too (get_step_output).
  • The server logs to stdout (docker compose logs -f server); failed sign-ins are in Admin → Sign-in audit.