Runtime variables
How project variables become Playwright environment variables and how jobs consume them.
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
Project variable
Stored as plain text or encrypted secret metadata.
Backend resolver
Builds env_vars and secret_env_keys for the run.
Worker process
Merges allowed variables into the Playwright child env.
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
| Name | Uppercase env var name, for example HAPPY_PATH_QUEUE_API_KEY. |
| Value | Injected into the Playwright Node process as process.env.NAME. |
| Secret | Decrypted only for executions allowed to use variables, then redacted from logs. |
| Browser page | Not 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_KEYHAPPY_PATH_QUEUE_IDTARGET_TENANTCHECKOUT_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.
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:
- The variable exists on the correct project.
- The name matches exactly, including underscores.
- The caller can use secrets: project admin for JWT runs, or
variables:usefor API keys. - The variable name is not reserved and was accepted when saved.
- The run is using the expected project, not another project with a similar job.