Documentation
Documentation/Execution

Runtime variables

How project variables become Playwright environment variables and how jobs consume them.

variablessecretsprocess.envBASE_URLvariables:use
View Markdown
On this page

Runtime variables let a project provide values to a Playwright run without hard-coding them inside the script. They are the right place for API keys, queue IDs, project IDs, tenant names, target hosts, or feature flags used during execution.

How injection works

01

Project variable

Stored as plain text or encrypted secret metadata.

02

Backend resolver

Builds env_vars and secret_env_keys for the run.

03

Worker process

Merges allowed variables into the Playwright child env.

04

Playwright test

Reads values with process.env.NAME.

At run dispatch time, the backend reads project_variables for the project. Plain variables are copied directly. Secret variables are decrypted only when the caller is allowed to use variables. The backend sends the resolved values to the worker as env_vars, plus a list of secret_env_keys for redaction.

The worker starts Playwright with those values in the child process environment. The script reads them through process.env.

const PROJECT_ID = requiredEnv('HAPPY_PATH_QUEUE_PROJECT_ID');
const QUEUE_ID = requiredEnv('HAPPY_PATH_QUEUE_ID');
const API_KEY = requiredEnv('HAPPY_PATH_QUEUE_API_KEY');
 
function requiredEnv(name: string) {
  const value = process.env[name]?.trim();
  if (!value) throw new Error(`Missing required runtime variable: ${name}`);
  return value;
}

Job contract

NameUppercase env var name, for example HAPPY_PATH_QUEUE_API_KEY.
ValueInjected into the Playwright Node process as process.env.NAME.
SecretDecrypted only for executions allowed to use variables, then redacted from logs.
Browser pageNot automatic. Pass values from the test process to page.evaluate or requests explicitly.

A variable is available only to the Node process running Playwright. It is not automatically available to application JavaScript loaded in Chromium, Firefox, or WebKit.

If the browser page needs the value, pass it explicitly:

const tenant = requiredEnv('TEST_TENANT');
 
await page.evaluate((tenantName) => {
  window.localStorage.setItem('tenant', tenantName);
}, tenant);

Names and reserved prefixes

Native SDK bridge variables use the reserved BERNOPS_ namespace and are injected and rotated by the worker. Jobs must not define or copy them. See Automation SDK transport selection.

Variable names use environment-variable syntax: uppercase letters, numbers, and underscores, and they cannot start with a number.

Good names:

  • HAPPY_PATH_QUEUE_API_KEY
  • HAPPY_PATH_QUEUE_ID
  • TARGET_TENANT
  • CHECKOUT_REGION

Do not use reserved names or prefixes such as PATH, HOME, AB_LOG_URL, BERNOPS_*, SUPABASE_*, NEXT_PUBLIC_*, PLAYWRIGHT_*, npm_*, or npm_config_*.

Secret behavior

Secrets are stored encrypted and are write-only in the UI. During execution, BernOps decrypts them for the worker process, registers their values with the worker redactor, and avoids exposing the secret value in run metadata.

What users can see

Run metadata records which variable names were resolved, whether each was secret, and the variable version. It does not store the secret plaintext.

Permission model

User JWT runs receive plain project variables by default. Secret variables are injected for project admins only when the run request explicitly sets include_secret_variables: true. API-key saved job executions receive secret variables automatically when the key has variables:use, unless the request explicitly sets include_secret_variables: false.

This matters for saved job execution from CI. If the script calls requiredEnv('HAPPY_PATH_QUEUE_API_KEY'), a calling API key with variables:use receives that permitted secret by default. A key without variables:use, or a request that explicitly disables secret variables, starts without that secret and the script fails with the missing variable error.

Base URL variables

BASE_URL and TARGET_URL are special. If the run request does not provide a base URL, BernOps first checks runtime variables with those names. If neither exists, it falls back to http://localhost:3000.

The worker also forces both BASE_URL and TARGET_URL to the final resolved base URL so jobs can consistently use either name.

Debugging missing variables

When a job reports Missing required runtime variable, check these in order:

  1. The variable exists on the correct project.
  2. The name matches exactly, including underscores.
  3. The caller can use secrets: project admin for JWT runs, or variables:use for API keys.
  4. The variable name is not reserved and was accepted when saved.
  5. The run is using the expected project, not another project with a similar job.