Skip to content

Config Reference

verfix.config.json is the Flow Library for your project. It lives in your project root and is created by verfix init.

{
"baseUrl": "http://localhost:3000",
"mode": "assisted",
"selectors": {
"emailInput": "input[type=email]",
"submitBtn": "#login-form button[type=submit]"
},
"sourceCodePolicy": "warn",
"flows": [
{
"id": "homepage-smoke",
"steps": [
{ "action": "navigate", "url": "/" }
],
"assertions": [
{ "type": "page_loaded" },
{ "type": "no_console_errors" }
]
},
{
"id": "login",
"mode": "strict",
"clearState": true,
"saveState": "auth",
"steps": [
{ "action": "type", "selector": "emailInput", "value": "${TEST_EMAIL}" },
{ "action": "type", "selector": "password", "testId": "password", "value": "${TEST_PASSWORD}" },
{ "action": "click", "selector": "submitBtn" }
],
"assertions": [
{ "type": "network_request_success", "value": "/api/auth/login", "acceptStatuses": [200, 409] }
]
},
{
"id": "dashboard-load",
"useState": "auth",
"steps": [
{ "action": "navigate", "url": "/dashboard" }
],
"assertions": [
{ "type": "text_visible", "value": "Total Declarations", "selector": "[data-testid=stats-card]" }
]
}
]
}

FieldTypeRequiredDefaultDescription
baseUrlstringYesBase URL of your app. All navigate steps with relative URLs are resolved against this. Supports ${VAR} interpolation.
mode"strict" | "assisted" | "exploratory"No"strict"Default execution mode for all flows. Individual flows can override this.
taskstringNoNatural language task, required when mode is exploratory and no flows are given.
flowsFlow[]YesArray of verification flows.
assertionsAssertionDefinition[]NoGlobal assertions applied to every flow if the flow has none.
selectorsRecord<string, string>No{}Alias map of logical name → real selector. Steps referencing an alias are resolved at run time — this is the core of config-first target resolution.
sourceCodePolicy"warn" | "block" | "off"No"warn"What happens when project source is edited during a verify loop. See Config-First Verification.
browser{ channel?, headless? }No{}{ "channel": "chrome" } reuses your installed Chrome/Edge and skips the Chromium download. { "headless": false } shows the browser window.
metadataobjectNoArbitrary metadata passed through to run output (e.g. { "framework": "nextjs" }).
timeoutnumberNo15000Default timeout in milliseconds for steps and assertions.
retriesnumberNo2Number of retries on failure before declaring the flow failed.

Each item in the flows array:

FieldTypeRequiredDescription
idstringYesUnique identifier for this flow. Used with --flow <id>.
namestringNoAlternative to id. If both provided, id takes precedence.
mode"strict" | "assisted"NoOverrides the global mode for this flow only. "exploratory" is not valid per-flow — it only applies as the top-level mode.
stepsFlowStep[]YesOrdered list of browser actions.
assertionsAssertionDefinition[]NoAssertions to check after all steps complete.
clearStatebooleanNoClears cookies and localStorage/sessionStorage before this flow runs. Use on a flow that must start logged-out. Does not clear IndexedDB or service workers.
saveStatestringNoAfter this flow’s steps and assertions pass, saves the browser’s cookies (including httpOnly), localStorage, and IndexedDB under this name in .verfix/state/ (never committed). Put it on your login flow. sessionStorage is not captured.
useStatestringNoRestores the named state before the run navigates, so the flow starts already logged in. Fails normally if the state doesn’t exist yet or has expired — rerun the saveState flow to recreate it. One state name per run.
skipbooleanNoQuarantines the flow — excluded from a full verfix run, but still runs if named explicitly via --flow <id>.
skipReasonstringNoHuman-readable reason shown alongside a skipped flow in verfix flows.

Steps are the browser actions executed in order before assertions run. Any step accepts:

  • optional: true — if the step fails for any reason within its timeout, it’s skipped (reported in skipped_optional_steps) instead of aborting the flow. Use it for a UI branch that may or may not appear.
  • frame: "<css selector>" — resolves the step’s target inside an <iframe> matching that selector (payment widgets, embedded editors). One frame level; AI selector-healing does not apply inside frames.
{ "action": "navigate", "url": "/login" }
FieldTypeRequired
urlstringYes — absolute or relative (resolved against baseUrl), supports ${VAR}
{ "action": "click", "selector": "[data-testid=submit]" }
{ "action": "click", "text": "Logout previous session", "optional": true, "timeout": 2000 }
FieldTypeRequired
selector / testId / textstringOne of — target element (testId is shorthand for [data-testid=<value>])
timeoutnumberNo — step-level timeout override
{ "action": "type", "selector": "emailInput", "value": "[email protected]" }
FieldTypeRequired
selector / testId / textstringOne of — target element
valuestringYes — text to type, supports ${VAR}
{ "action": "press", "key": "Enter" }
{ "action": "press", "selector": "searchInput", "key": "Escape" }

Sends a keyboard key (Playwright key name — "Enter", "Escape", "Tab", etc.) on a target locator if given, otherwise at the page level. For UIs that submit or react to keydown rather than form submission.

{ "action": "select_option", "selector": "countrySelect", "value": "US" }

Picks a <select> option by value or visible label.

{ "action": "check", "selector": "acceptTerms" }
{ "action": "uncheck", "selector": "newsletterOptIn" }

Sets a checkbox/radio to a known state. Idempotent — unlike click on a checkbox, so reruns are deterministic regardless of the current state.

{ "action": "hover", "selector": "userMenu" }

Hovers to reveal hover-only UI (dropdown menus, tooltips).

{ "action": "upload_file", "selector": "avatarInput", "file": "fixtures/avatar.png" }
{ "action": "upload_file", "selector": "csvInput", "file": { "name": "note.csv", "content": "a,b\n1,2", "mimeType": "text/csv" } }
FieldTypeDescription
file (path)stringProject-relative fixture path (${VAR} substitution supported)
file (inline){ name, content, mimeType, encoding? }CI-safe inline content materialized at run time, no filesystem dependency. encoding: "base64" for binary. verfix validate warns above 64KB — use a fixture path for larger files.

Targets the <input type=file> by attachment, not visibility, since real UIs hide it behind styled buttons.

{ "action": "wait_for_selector", "selector": "[data-testid=dashboard]" }

Waits for an element to appear before proceeding.

{ "action": "wait_for_url", "value": "/dashboard" }

Waits until the URL contains a substring — same semantics as the url_contains assertion. For client-side redirects.

{ "action": "wait_for_network_idle" }

Waits for network activity to settle. For background-loaded data that doesn’t block a selector.


Assertions are checked after all steps complete. Each has a type field.

{ "type": "page_loaded" }

Verifies the page loaded without a navigation error.

{ "type": "selector_visible", "selector": "[data-testid=dashboard]" }
FieldTypeRequired
selectorstringYes
timeoutnumberNo
{ "type": "text_visible", "value": "Welcome back" }
{ "type": "text_visible", "value": "Total Declarations", "selector": "[data-testid=stats-card]" }

Verifies a text string appears on the page. Passes if any visible occurrence matches. Add the optional selector field to scope the search to matches inside a specific element — useful when the text is repeated elsewhere on the page (a stats-card title also appearing in a nav item, say).

FieldTypeRequired
valuestringYes
selectorstringNo — scopes the search
timeoutnumberNo
{ "type": "url_contains", "value": "/dashboard" }

Verifies the current URL contains a substring.

{ "type": "title_contains", "value": "Dashboard" }

Verifies the page <title> contains a substring.

{ "type": "no_console_errors" }
{ "type": "no_console_errors", "exclude": ["ACTIVE_SESSION_EXISTS"] }

Fails if any console.error() calls were detected during the flow. exclude is an array of regex patterns — matching console errors are ignored instead of failing the assertion, for known/expected warnings (e.g. a third-party script notice), without silencing every error.

{ "type": "network_request_success", "value": "/api/user" }
{ "type": "network_request_success", "value": "/api/auth/login", "acceptStatuses": [200, 409] }

Verifies a network request to a URL pattern returned a 2xx status. acceptStatuses replaces the default 200–399 range entirely when set — useful for endpoints with more than one valid outcome (e.g. 200 on login success, 409 when a session is already active) without branching the flow.

Both no_console_errors and network_request_success failures name the concrete matched request (method, URL, status) or console error text in detail/fix_hint, so an agent can tell whether to add an exception above or treat it as a real bug.


Step value/url, assertion value, and baseUrl may reference ${VAR_NAME}, resolved from process.env at run time (including anything set in .verfix/.env) — so secrets never need to be committed in verfix.config.json. An unset variable fails the run immediately, naming the missing variable and its field path, rather than typing the literal ${VAR_NAME} string into a form field.

{ "action": "type", "selector": "emailInput", "value": "${TEST_EMAIL}" }

verfix init writes verfix.config.json with flows: [] — empty. There’s no preset library and no scaffolding step, because Verfix has no way to know your app’s routes, forms, or components ahead of time. Flows are meant to be written by the coding agent, on demand, as part of the edit → verify → fix loop: it reads the component it just touched, finds the real selector, and adds (or updates) a flow — see Config-First Verification and the Agent Integration guide for the exact decision hierarchy an agent follows.