
Bruno Api Testing
- 6 installs
- 21 repo stars
- Updated July 31, 2026
- jim60105/copilot-prompt
Create, run, and maintain file-based Bruno API test collections in OpenCollection YAML or legacy Bru format, including assertions, environments, and CI runs.
About
Guides building Bruno API test collections as plain files, writing requests with assertions, running them via the bru CLI, and wiring CI pipelines. A developer uses it to automate API testing from a Git-first, offline collection.
- Supports both OpenCollection YAML (v3.1+) and legacy Bru formats
- Covers environments, request chaining, reports, and GitHub Actions CI
Bruno Api Testing by the numbers
- 6 all-time installs (skills.sh)
- Ranked #1,591 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/jim60105/copilot-prompt --skill bruno-api-testingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 6 |
|---|---|
| repo stars | ★ 21 |
| Last updated | July 31, 2026 |
| Repository | jim60105/copilot-prompt ↗ |
What it does
Create, run, and maintain file-based Bruno API test collections in OpenCollection YAML or legacy Bru format, including assertions, environments, and CI runs.
Files
Bruno API Testing
Create and run API test collections using Bruno — a Git-first, offline-only API client that stores collections as plain files.
Format Selection
Bruno supports two file formats. Determine which to use:
- YAML (OpenCollection) — Default since Bruno v3.1. Uses
.ymlfiles andopencollection.ymlroot. Preferred for new projects. - Bru (Legacy) — Uses
.brufiles andbruno.jsonroot. Use only for existing Bru-format collections.
Detect format by checking the collection root: opencollection.yml → YAML, bruno.json → Bru.
For YAML format syntax details, see references/yaml-syntax.md. For Bru format syntax details, see references/bru-syntax.md.
Workflow
1. Create Collection Structure
Create the directory layout with the collection root file, environments, and organized request folders.
YAML format:
my-api-tests/
├── opencollection.yml # REQUIRED: collection root
├── environments/
│ ├── Local.yml
│ ├── Staging.yml
│ └── Production.yml
├── Auth/
│ ├── folder.yml
│ └── Login.yml
└── Users/
├── folder.yml
├── Get Users.yml
├── Get User by ID.yml
└── Create User.ymlMinimal opencollection.yml:
opencollection: 1.0.0
info:
name: My API TestsBru format: Same structure but use bruno.json + .bru extensions. See references/bru-syntax.md.
2. Create Environment Files
YAML (environments/Local.yml):
variables:
- name: baseUrl
value: http://localhost:3000/api
- name: apiKey
value: ""
secret: trueBru (environments/Local.bru):
vars {
baseUrl: http://localhost:3000/api
}
vars:secret [
apiKey
]3. Write Request Files with Tests
YAML format — a complete request with tests:
info:
name: Get Users
type: http
seq: 1
http:
method: GET
url: "{{baseUrl}}/users"
headers:
- name: accept
value: application/json
- name: authorization
value: "Bearer {{authToken}}"
runtime:
assertions:
- expression: res.status
operator: eq
value: "200"
- expression: res.body
operator: isArray
scripts:
- type: tests
code: |-
test("returns 200", function() {
expect(res.status).to.equal(200);
});
test("returns array of users", function() {
expect(res.body).to.be.an('array');
expect(res.body).to.have.lengthOf.at.least(1);
});
test("each user has required fields", function() {
res.body.forEach(user => {
expect(user).to.have.property('id');
expect(user).to.have.property('email');
});
});
settings:
encodeUrl: trueUse assertions (declarative) for simple checks, tests scripts (Chai.js) for complex logic.
4. Chain Requests with Data Extraction
Extract data from one response and use it in subsequent requests:
YAML — Login request saving a token:
info:
name: Login
type: http
seq: 1
http:
method: POST
url: "{{baseUrl}}/auth/login"
body:
type: json
data: |-
{
"username": "{{username}}",
"password": "{{password}}"
}
auth:
type: none
runtime:
scripts:
- type: after-response
code: |-
bru.setEnvVar("authToken", res.body.access_token);
- type: tests
code: |-
test("login successful", function() {
expect(res.status).to.equal(200);
expect(res.body).to.have.property('access_token');
});Then reference {{authToken}} in subsequent requests via Bearer {{authToken}}.
5. Run Tests with bru CLI
Install and run:
npm install -g @usebruno/cli
# Run entire collection
cd my-api-tests && bru run --env Local
# Run specific folder
bru run Auth --env Local
# Run with developer mode (for external packages, fs access)
bru run --env Local --sandbox=developer
# Filter by tags
bru run --tags=smoke --env Local
# Generate reports
bru run --env Local \
--reporter-html results.html \
--reporter-junit results.xml \
--reporter-json results.json
# Pass secrets via CLI
bru run --env Local --env-var API_KEY=secret123
# Parallel execution
bru run --env Local --parallel
# Data-driven testing
bru run --csv-file-path data.csv --env Localv3.0.0 breaking change: Default is now Safe Mode. Use --sandbox=developer for developer mode features.
6. Set Up CI/CD
See references/ci-cd.md for complete GitHub Actions workflows, matrix testing, and reporting patterns.
Minimal GitHub Actions workflow:
name: API Tests
on: [push, pull_request]
jobs:
api-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm install -g @usebruno/cli
- name: Run API Tests
working-directory: ./my-api-tests
env:
API_KEY: ${{ secrets.API_KEY }}
run: bru run --env CI --reporter-html results.html --reporter-junit results.xml
- uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: |
./my-api-tests/results.html
./my-api-tests/results.xmlCritical: Always set working-directory to the collection root in CI/CD.
Testing Patterns
Assertions (Declarative) — Use for Simple Checks
runtime:
assertions:
- expression: res.status
operator: eq
value: "200"
- expression: res.body.success
operator: eq
value: "true"
- expression: res.body.data
operator: isJson
- expression: res.headers.content-type
operator: contains
value: application/jsonOperators vary slightly by Bruno version and editor surface. Check Bruno's current Assertions docs for the exact operator names supported by your version when writing declarative assertions.
Tests (Chai.js) — Use for Complex Validation
runtime:
scripts:
- type: tests
code: |-
test("status and structure", function() {
expect(res.status).to.equal(200);
expect(res.body).to.be.an('object');
expect(res.body).to.have.all.keys('id', 'name', 'email');
});
test("validates email format", function() {
expect(res.body.email).to.match(/^[\w\-.]+@([\w-]+\.)+[\w-]{2,4}$/);
});
test("response time acceptable", function() {
expect(res.responseTime).to.be.below(2000);
});
test("pagination works", function() {
expect(res.body.data).to.be.an('array');
expect(res.body.meta.total).to.be.a('number');
expect(res.body.meta.page).to.equal(1);
});For the complete JavaScript API (req, res, bru objects), see references/javascript-api.md.
Common Mistakes
1. Missing opencollection.yml (YAML) or bruno.json (Bru) at collection root 2. Using meta: instead of info: in YAML request files 3. Using script type test instead of tests (plural) 4. Putting request-level fields (http:, method:) in opencollection.yml 5. Forgetting working-directory in CI/CD steps 6. Committing secrets — use secret: true in env files + CI/CD secrets 7. Using |- for body data is required in YAML to preserve JSON formatting 8. Missing seq number in info: — controls execution order 9. Relying on `folder.yml` script/auth inheritance in CLI — Bruno's Sandwich execution order (Collection → Folder → Request) for before-request scripts may work in the Bruno GUI, but @usebruno/cli does NOT reliably inherit folder.yml scripts or auth settings to individual test files. Always add before-request scripts and auth blocks directly to each request file that needs them. The folder.yml is useful for documentation and GUI users, but CLI-driven tests must be self-contained.
Script Execution Order
Bruno supports two script flows:
1. Sandwich (default): Collection before-request → Folder before-request → Request before-request → Request is sent → Request after-response → Folder after-response → Collection after-response 2. Sequential: Collection before-request → Folder before-request → Request before-request → Request is sent → Collection after-response → Folder after-response → Request after-response
Request assertions and request tests run after the post-response scripts.
⚠️ CLI Inheritance Caveat
The Sandwich/Sequential script execution order described above applies to Bruno GUI only. When running tests with @usebruno/cli (the CLI runner used in CI/CD), folder-level before-request scripts and auth settings are NOT reliably inherited by individual request files.
Impact: If a folder.yml contains a before-request script (e.g., to skip requests when an environment variable is "false"), request files in that folder will NOT inherit this logic when run via CLI.
Workaround: Duplicate the before-request logic into each individual request file that needs it:
# In each request file that needs conditional skip:
runtime:
scripts:
- type: before-request
code: |-
const featureAvailable = bru.getEnvVar("featureAvailable");
if (featureAvailable === "false") {
bru.runner.skipRequest();
}
- type: tests
code: |-
test("returns 200", function() {
expect(res.status).to.equal(200);
});Same applies to auth: Set http.auth in each request file, not just folder.yml.
Variable Precedence (Highest to Lowest)
1. Runtime variables (bru.setVar()) 2. Request variables 3. Folder variables 4. Collection variables 5. Environment variables
Use bru.getGlobalEnvVar() for global environment values and bru.getProcessEnv() for OS process environment variables. They are not documented as part of the standard collection variable precedence chain.
References
- [YAML Syntax](references/yaml-syntax.md) — Complete OpenCollection YAML format for requests, bodies, auth, headers, params, environments, folders, collections
- [Bru Syntax](references/bru-syntax.md) — Legacy
.brufile format reference - [JavaScript API](references/javascript-api.md) — Full
req,res,bruobject API with runner control, cookies, utilities - [CI/CD Integration](references/ci-cd.md) — GitHub Actions workflows, report generation, matrix testing, environment secrets
Bru File Format Reference (Legacy)
Reference for Bruno's legacy .bru plain-text markup format. Use for existing Bru-format collections.
Table of Contents
- Collection Root
- Request File Structure
- Request Types
- Body Formats
- Authentication
- Headers and Parameters
- Variables
- Scripts
- Testing
- Environment Files
- Folder and Collection Files
Collection Root
bruno.json — required at collection root:
{
"version": "1",
"name": "Your Collection Name",
"type": "collection"
}Do NOT add pathname, files, activeEnvironmentUid. Keep minimal.
Request File Structure
.bru files use block syntax:
meta {
name: Request Name
type: http
seq: 1
}
get {
url: {{baseUrl}}/endpoint
body: none
auth: none
}
headers {
content-type: application/json
authorization: Bearer {{token}}
}
body:json {
{
"key": "value"
}
}
script:pre-request {
bru.setVar("timestamp", Date.now());
}
tests {
test("Status is 200", function() {
expect(res.status).to.equal(200);
});
}Request Types
HTTP/REST
Method name is the block name (lowercase): get, post, put, patch, delete, options, head.
meta {
name: Create User
type: http
seq: 1
}
post {
url: {{baseUrl}}/users
body: json
auth: bearer
}
body:json {
{
"username": "johndoe",
"email": "john@example.com"
}
}
auth:bearer {
token: {{token}}
}GraphQL
meta {
name: Get User Data
type: graphql-request
seq: 1
}
post {
url: {{baseUrl}}/graphql
body: graphql
auth: bearer
}
body:graphql {
query {
user(id: "123") {
id
name
email
}
}
}
body:graphql:vars {
{
"userId": "123"
}
}gRPC
meta {
name: SayHello
type: grpc
seq: 1
}
grpc {
url: {{host}}
method: /hello.HelloService/SayHello
body: grpc
auth: inherit
methodType: unary
}
body:grpc {
name: message 1
content: '''
{
"greeting": "hello"
}
'''
}WebSocket
meta {
name: WebSocket Test
type: ws
seq: 1
}
ws {
url: ws://localhost:8081/ws
auth: inherit
}
headers {
Authorization: Bearer {{token}}
}Body Formats
JSON
body:json {
{
"username": "johndoe",
"email": "john@example.com"
}
}Text
body:text {
This is plain text content
}XML
body:xml {
<?xml version="1.0" encoding="UTF-8"?>
<user>
<username>johndoe</username>
</user>
}Form URL Encoded
body:form-urlencoded {
username: johndoe
password: secret123
~disabled_field: value
}Multipart Form
body:multipart-form {
username: johndoe
avatar: @file(/path/to/avatar.jpg)
}Authentication
Bearer Token
auth:bearer {
token: {{token}}
}Basic Auth
auth:basic {
username: admin
password: secret123
}API Key
auth:apikey {
key: x-api-key
value: api-secret-key-12345
placement: header
}OAuth2
auth:oauth2 {
grant_type: authorization_code
callback_url: http://localhost:8080/callback
authorization_url: https://provider.com/oauth/authorize
access_token_url: https://provider.com/oauth/token
client_id: {{client_id}}
client_secret: {{client_secret}}
scope: read write
}Supported auth types: none, inherit, basic, bearer, apikey, digest, oauth2, awsv4, ntlm.
Headers and Parameters
Headers
headers {
content-type: application/json
x-api-key: {{apiKey}}
~disabled-header: value
}Prefix with ~ to disable.
Query Parameters
params:query {
page: 1
limit: 10
~disabled_param: value
}Path Parameters
params:path {
userId: 123
status: active
}Variables
Request-Level Variables
vars:pre-request {
user_id: 12345
environment: production
}
vars:post-response {
response_id: {{res.body.id}}
processed_at: {{$timestamp}}
}Variable Interpolation
Same as YAML format: {{variableName}}, {{$guid}}, {{$timestamp}}, etc.
Scripts
Pre-Request
script:pre-request {
const timestamp = Date.now();
bru.setVar("request_timestamp", timestamp);
req.setHeader("X-Timestamp", timestamp.toString());
}Post-Response
script:post-response {
const token = res.body.token;
bru.setVar("authToken", token);
bru.setEnvVar("sessionToken", token);
}Testing
Assert Block (Simple)
assert {
res.status: eq 200
res.body.success: eq true
res.body.data: isJson
res.body.id: isNumber
res.headers.content-type: contains application/json
~res.body.optional: eq value
}Disable with ~ prefix. Same operators as YAML format.
Tests Block (Complex)
tests {
test("Status is 200", function() {
expect(res.status).to.equal(200);
});
test("Response has required fields", function() {
expect(res.body).to.have.property('id');
expect(res.body).to.have.property('name');
});
}Environment Files
Located in environments/ with .bru extension:
vars {
baseUrl: https://api.example.com
apiVersion: v1
timeout: 30000
}
vars:secret [
apiKey,
authToken,
clientSecret
]Folder and Collection Files
folder.bru
meta {
name: User Management
seq: 1
}
headers {
x-api-version: v2
}
auth {
mode: bearer
}
auth:bearer {
token: {{token}}
}
script:pre-request {
bru.setVar("folder_timestamp", Date.now());
}
tests {
test("Folder level test", function() {
expect(res.status).to.be.oneOf([200, 201, 204]);
});
}collection.bru
meta {
name: My API Collection
}
headers {
user-agent: Bruno/1.0
}
script:pre-request {
console.log("Collection pre-request script");
}
tests {
test("Response time under 5s", function() {
expect(res.responseTime).to.be.below(5000);
});
}CI/CD Integration Reference
Guide for running Bruno tests in CI/CD pipelines using the bru CLI.
Table of Contents
- bru CLI Installation
- CLI Commands
- Reporters and Reports
- GitHub Actions Workflows
- Environment Variables in CI
- Best Practices
---
bru CLI Installation
npm install -g @usebruno/cliVerify:
bru --versionSafe Mode (v3.0.0+)
bru CLI v3.0.0 introduced Safe Mode as default. This restricts script capabilities.
--sandbox=safe(default) — Restricted mode, no file system access or external modules--sandbox=developer— Full access to Node.js APIs and external modules
If tests use require(), fs, or other Node.js APIs, you MUST use --sandbox=developer.
---
CLI Commands
Run Collection
bru run --env <environment>From the collection root directory. Runs all requests in sequence order.
Run Single Request
bru run request.bru --env <environment>Run Folder
bru run folder/ --env <environment>Key Options
| Option | Description |
|---|---|
--env <name> | Environment to use |
--env-var "key=value" | Override/set environment variables |
--output <path> | Deprecated output path option; prefer reporter flags |
--reporter-html [path] | Generate HTML report |
--reporter-junit [path] | Generate JUnit XML report |
--reporter-json [path] | Generate JSON report |
--reporter-skip-all-headers | Omit headers from reports |
--reporter-skip-request-body | Omit request bodies from reports |
--reporter-skip-response-body | Omit response bodies from reports |
--reporter-skip-body | Omit both request and response bodies from reports |
--sandbox <mode> | safe (default) or developer |
--bail | Stop on first failure |
--cacert <path> | CA certificate file |
--insecure | Skip SSL verification |
-r | Recursive run |
Examples
# Run full collection with HTML + JUnit reports
bru run --env production \
--reporter-html results/report.html \
--reporter-junit results/report.xml
# Run with environment variable overrides
bru run --env staging \
--env-var "baseUrl=https://staging-api.example.com" \
--env-var "apiKey=$API_KEY"
# Run specific folder with developer sandbox
bru run users/ --env dev --sandbox=developer
# Run with bail (stop on first failure)
bru run --env production --bail---
Reporters and Reports
HTML Report
bru run --env prod --reporter-html results/report.htmlOptions:
--reporter-skip-all-headers— Exclude request/response headers--reporter-skip-body— Exclude request/response bodies
JUnit XML Report
bru run --env prod --reporter-junit results/report.xmlJUnit XML is compatible with most CI systems (GitHub Actions, Jenkins, GitLab CI).
JSON Report
bru run --env prod --reporter-json results/report.jsonOptions:
--reporter-skip-all-headers— Exclude headers--reporter-skip-body— Exclude bodies
Multiple Reporters
Combine reporters in a single run:
bru run --env prod \
--reporter-html results/report.html \
--reporter-junit results/report.xml \
--reporter-json results/report.json---
GitHub Actions Workflows
Basic Workflow
name: API Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
api-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install bru CLI
run: npm install -g @usebruno/cli
- name: Run API tests
working-directory: ./bruno-collection
run: |
bru run --env ci \
--reporter-html results/report.html \
--reporter-junit results/report.xml
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: bruno-collection/results/CRITICAL: working-directory MUST point to the collection root directory (where opencollection.yml or bruno.json lives).
With Environment Secrets
- name: Run API tests
working-directory: ./bruno-collection
env:
API_KEY: ${{ secrets.API_KEY }}
BASE_URL: ${{ vars.API_BASE_URL }}
run: |
bru run --env ci \
--env-var "apiKey=$API_KEY" \
--env-var "baseUrl=$BASE_URL" \
--reporter-junit results/report.xmlMatrix Testing (Multiple Environments)
jobs:
api-tests:
runs-on: ubuntu-latest
strategy:
matrix:
environment: [staging, production]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install bru CLI
run: npm install -g @usebruno/cli
- name: Run API tests (${{ matrix.environment }})
working-directory: ./bruno-collection
run: |
bru run --env ${{ matrix.environment }} \
--reporter-html results/${{ matrix.environment }}-report.html \
--reporter-junit results/${{ matrix.environment }}-report.xml
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.environment }}
path: bruno-collection/results/With JUnit Test Report
- name: Publish test report
if: always()
uses: mikepenz/action-junit-report@v4
with:
report_paths: 'bruno-collection/results/report.xml'
fail_on_failure: trueScheduled Tests (Monitoring)
on:
schedule:
- cron: '0 */6 * * *' # Every 6 hours
workflow_dispatch: # Manual trigger
jobs:
api-monitoring:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm install -g @usebruno/cli
- name: Run health checks
working-directory: ./bruno-collection
run: |
bru run health-checks/ --env production \
--reporter-json results/health.json \
--bail---
Environment Variables in CI
CI Environment File
Create a dedicated ci.yml (YAML) or ci.bru (Bru) environment file for CI:
YAML format (environments/ci.yml):
variables:
- name: baseUrl
value: "https://api.example.com"
enabled: true
- name: apiKey
value: ""
enabled: true
secret: trueBru format (environments/ci.bru):
vars {
baseUrl: https://api.example.com
}
vars:secret [
apiKey
]Injecting Secrets
Pass secrets via --env-var flags:
bru run --env ci \
--env-var "apiKey=${API_KEY}" \
--env-var "dbPassword=${DB_PASSWORD}"NEVER hardcode secrets in environment files committed to git.
---
Best Practices
Collection Structure for CI
bruno-collection/
├── opencollection.yml # or bruno.json
├── environments/
│ ├── local.yml # Local development
│ ├── staging.yml # Staging environment
│ ├── production.yml # Production environment
│ └── ci.yml # CI-specific environment
├── health-checks/ # Quick smoke tests
│ ├── folder.yml
│ └── api-health.yml
├── auth/ # Authentication flows
│ ├── folder.yml
│ ├── 1-login.yml
│ └── 2-refresh-token.yml
├── users/ # CRUD tests
│ ├── folder.yml
│ ├── 1-create-user.yml
│ ├── 2-get-user.yml
│ ├── 3-update-user.yml
│ └── 4-delete-user.yml
└── .gitignore.gitignore for Bruno Collections
# Test results
results/
# Local environment overrides
environments/local.yml
environments/local.bruOrdering Requests
Use seq (sequence number) in request metadata to control execution order. Requests run in ascending seq order within each folder.
Request Chaining Pattern
Use runtime variables to chain requests:
// In login request's after-response / tests:
bru.setVar("authToken", res.body.token);
bru.setVar("userId", res.body.user.id);# In subsequent request's auth:
auth:
mode: bearer
bearer:
token: "{{authToken}}"Fail-Fast Strategy
Use --bail in CI to stop on first failure for faster feedback:
bru run --env ci --bail --reporter-junit results/report.xmlParallel Test Suites
Run different folders as separate CI jobs for parallelism:
strategy:
matrix:
test-suite: [auth, users, orders, products]
steps:
- name: Run ${{ matrix.test-suite }} tests
working-directory: ./bruno-collection
run: bru run ${{ matrix.test-suite }}/ --env ci --reporter-junit results/report.xmlBruno JavaScript API Reference
Complete reference for the req, res, and bru objects available in Bruno scripts and tests.
Table of Contents
---
req Object
Available in before-request (pre-request) scripts.
URL & Method
| Method | Description |
|---|---|
req.getUrl() | Get the current request URL |
req.setUrl(url) | Set the request URL |
req.getMethod() | Get the HTTP method |
req.setMethod(method) | Set the HTTP method |
Headers
| Method | Description |
|---|---|
req.getHeader(name) | Get a single header value |
req.getHeaders() | Get all headers as an object |
req.setHeader(name, value) | Set a header |
req.setHeaders(headers) | Set multiple headers (merges) |
req.removeHeader(name) | Remove a header |
Body
| Method | Description |
|---|---|
req.getBody() | Get the request body |
req.setBody(data) | Set the request body |
Timeout
| Method | Description |
|---|---|
req.getTimeout() | Get the request timeout (ms) |
req.setTimeout(ms) | Set the request timeout (ms) |
Execution Context
| Method | Description |
|---|---|
req.getExecutionMode() | Returns "standalone" or "runner" |
req.getRequestSequence() | Get execution order index (runner mode) |
Request Error Handling
| Method | Description |
|---|---|
req.getMaxRedirects() | Get max redirects |
req.setMaxRedirects(n) | Set max redirects |
Example
// before-request script
const timestamp = Date.now().toString();
req.setHeader("X-Request-Timestamp", timestamp);
// Modify URL dynamically
const url = req.getUrl();
req.setUrl(url + "?nocache=" + timestamp);---
res Object
Available in after-response (post-response) and tests scripts.
Properties & Methods
| Method | Description |
|---|---|
res.status | HTTP status code (number) |
res.statusText | HTTP status text (string) |
res.headers | Response headers object |
res.body | Parsed response body (object for JSON, string for text) |
res.responseTime | Response time in milliseconds |
res.getStatus() | Get HTTP status code |
res.getHeader(name) | Get a single response header |
res.getHeaders() | Get all response headers |
res.getBody() | Get the response body |
res.getResponseTime() | Get response time (ms) |
res.getUrl() | Get the final request URL |
res.getSize() | Get response size information |
Example
// after-response script
if (res.status === 200) {
const token = res.body.token;
bru.setVar("authToken", token);
}
// tests block
test("Status is 200", function() {
expect(res.status).to.equal(200);
});
test("Response time acceptable", function() {
expect(res.responseTime).to.be.below(2000);
});---
bru Object
Available in all script contexts.
Environment Variables
Environment variables are available across the active Bruno environment. bru.setEnvVar() is in-memory by default; pass { persist: true } to write the change to disk.
| Method | Description |
|---|---|
bru.getEnvVar(name) | Get an environment variable |
bru.setEnvVar(name, value, options?) | Set an environment variable; use { persist: true } to save it to file |
bru.getEnvName() | Get the active environment name |
bru.hasEnvVar(name) | Check if an environment variable exists |
bru.deleteEnvVar(name) | Delete an environment variable |
bru.getAllEnvVars() | Get all environment variables as an object |
bru.deleteAllEnvVars() | Delete all environment variables in the active environment |
bru.getGlobalEnvVar(name) | Get a global environment variable |
bru.setGlobalEnvVar(name, value) | Set a global environment variable |
bru.getAllGlobalEnvVars() | Get all global environment variables as an object |
Collection, Folder, and Request Variables
| Method | Description |
|---|---|
bru.getCollectionName() | Get the current collection name |
bru.getCollectionVar(name) | Get a collection variable |
bru.hasCollectionVar(name) | Check if a collection variable exists |
bru.getFolderVar(name) | Get a folder variable |
bru.getRequestVar(name) | Get a request-level variable |
Process Environment Variables
| Method | Description |
|---|---|
bru.getProcessEnv(name) | Get a process environment variable exposed by the OS or CI runtime |
Runtime Variables
Runtime variables exist only during the current execution and are NOT persisted:
| Method | Description |
|---|---|
bru.hasVar(name) | Check whether a runtime variable exists |
bru.getVar(name) | Get a runtime variable |
bru.setVar(name, value) | Set a runtime variable |
bru.deleteVar(name) | Delete a runtime variable |
bru.getAllVars() | Get all runtime variables as an object |
bru.deleteAllVars() | Delete all runtime variables |
Runner Control (Collection Runner Only)
| Method | Description |
|---|---|
bru.setNextRequest(name) | Set the next request to execute by name |
bru.runner.setNextRequest(name) | Set the next request to execute by name |
bru.runner.skipRequest() | Skip the current request |
bru.runner.stopExecution() | Stop the collection run |
Utilities
| Method | Description |
|---|---|
bru.sleep(ms) | Sleep for specified milliseconds |
bru.interpolate(string) | Interpolate variables in a string |
bru.cwd() | Get the collection root directory path |
bru.getTestResults() | Get test results for current request |
bru.getAssertionResults() | Get assertion results for current request |
Cookie Management
Bruno exposes a cookie jar API in scripts:
const jar = bru.cookies.jar();
jar.setCookie("https://example.com", "sessionId", "abc123");
const sessionCookie = await jar.getCookie("https://example.com", "sessionId");
const allCookies = await jar.getCookies("https://example.com");sendRequest
Send additional HTTP requests from within scripts:
// Basic sendRequest
const response = await bru.sendRequest({
method: "POST",
url: "https://api.example.com/oauth/token",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
data: "grant_type=client_credentials&client_id=xxx"
});
bru.setVar("accessToken", response.body.access_token);runRequest
Execute another request from the same collection:
// Run another request from the collection
await bru.runRequest("path/to/request.bru");---
Chai.js Assertions
Bruno uses Chai.js expect style assertions in test blocks. The expect function is globally available.
Basic Assertions
expect(value).to.equal(expected); // Strict equality
expect(value).to.not.equal(unexpected); // Not equal
expect(value).to.deep.equal(expected); // Deep equality
expect(value).to.be.true; // Strictly true
expect(value).to.be.false; // Strictly false
expect(value).to.be.null; // Is null
expect(value).to.be.undefined; // Is undefined
expect(value).to.exist; // Not null/undefined
expect(value).to.be.ok; // TruthyType Assertions
expect(value).to.be.a('string');
expect(value).to.be.a('number');
expect(value).to.be.a('boolean');
expect(value).to.be.an('object');
expect(value).to.be.an('array');
expect(value).to.be.an.instanceof(Constructor);Numeric Comparisons
expect(value).to.be.above(5); // Greater than
expect(value).to.be.below(100); // Less than
expect(value).to.be.at.least(5); // >=
expect(value).to.be.at.most(100); // <=
expect(value).to.be.within(5, 100); // Range inclusive
expect(value).to.be.closeTo(10, 0.5); // Approx equalString Assertions
expect(str).to.include('substring');
expect(str).to.match(/regex/);
expect(str).to.have.lengthOf(10);
expect(str).to.be.empty; // Length 0Object Assertions
expect(obj).to.have.property('name');
expect(obj).to.have.property('name', 'John');
expect(obj).to.have.nested.property('user.name');
expect(obj).to.have.all.keys('id', 'name', 'email');
expect(obj).to.have.any.keys('id', 'name');
expect(obj).to.include({ name: 'John' });
expect(obj).to.deep.include({ user: { name: 'John' } });Array Assertions
expect(arr).to.have.lengthOf(3);
expect(arr).to.include('item');
expect(arr).to.include.members(['a', 'b']);
expect(arr).to.have.ordered.members(['a', 'b', 'c']);
expect(arr).to.deep.include({ id: 1 });
expect(arr).to.be.an('array').that.is.not.empty;
expect(arr).to.satisfy(a => a.every(x => x > 0));Chaining
Chain language helpers for readability (no behavioral effect): to, be, been, is, that, which, and, has, have, with, at, of, same, but, does, still, also.
expect(res.body).to.be.an('object').that.has.property('id');
expect(res.status).to.be.a('number').and.to.equal(200);---
Script Execution Order
Bruno supports two script flows:
1. Sandwich (default): Collection before-request → Folder before-request → Request before-request → HTTP request executes → Request after-response → Folder after-response → Collection after-response 2. Sequential: Collection before-request → Folder before-request → Request before-request → HTTP request executes → Collection after-response → Folder after-response → Request after-response
Request assertions and the request tests script run after the post-response scripts.
---
Variable Precedence (Highest to Lowest)
1. Runtime variables (bru.setVar) 2. Request variables 3. Folder variables (innermost first) 4. Collection variables 5. Environment variables
Use bru.getGlobalEnvVar() for global environment variables and bru.getProcessEnv() for process environment variables. Bruno's docs only define the precedence chain through environment variables.
---
Dynamic Variables
Available in variable interpolation:
| Variable | Description |
|---|---|
{{$guid}} | Random UUID v4 |
{{$timestamp}} | Unix timestamp (seconds) |
{{$isoTimestamp}} | ISO 8601 timestamp |
{{$randomInt}} | Random integer |
---
Assert Block Operators
Used in the assert / assertions sections:
| Operator | Description | Example |
|---|---|---|
eq | Equals | res.status: eq 200 |
neq | Not equals | res.status: neq 404 |
gt | Greater than | res.body.count: gt 0 |
gte | Greater than or equal | res.body.count: gte 1 |
lt | Less than | res.responseTime: lt 5000 |
lte | Less than or equal | res.body.age: lte 100 |
in | In list | res.status: in [200, 201] |
notIn | Not in list | res.status: notIn [500, 502] |
contains | Contains substring | res.body.name: contains John |
notContains | Not contains | res.body.msg: notContains error |
matches | Regex match | res.body.email: matches @.*\\.com |
length | Array/string length | res.body.items: length 10 |
between | Range (inclusive) | res.body.score: between 0 100 |
isString | Type check | res.body.name: isString |
isNumber | Type check | res.body.id: isNumber |
isBoolean | Type check | res.body.active: isBoolean |
isNull | Is null | res.body.deleted: isNull |
isJson | Is valid JSON | res.body: isJson |
isDefined | Is defined | res.body.id: isDefined |
isUndefined | Is undefined | res.body.deleted: isUndefined |
isEmpty | Is empty | res.body.errors: isEmpty |
isTruthy | Truthy value | res.body.success: isTruthy |
isFalsy | Falsy value | res.body.error: isFalsy |
OpenCollection YAML Syntax Reference
Complete reference for Bruno's YAML-based OpenCollection format (v3.1+).
Table of Contents
- Request File Structure
- Request Types
- Body Formats
- Authentication
- Headers and Parameters
- Variables
- Scripts
- Assertions
- Settings
- Environment Files
- Folder Files
- Collection Files
- opencollection.yml
Request File Structure
Top-level sections in a .yml request file:
info: # Request metadata (name, type, seq, tags)
http: # HTTP request configuration
runtime: # Scripts and assertions
settings: # Request settings
docs: # Request documentation (markdown string)info
info:
name: Get Users # Display name
type: http # http | folder | grpc | ws
seq: 1 # Sort order in UI
tags: # Optional tags for filtering
- smoke
- regressionRequest Types
HTTP/REST
info:
name: Create User
type: http
seq: 1
http:
method: POST
url: "{{baseUrl}}/users"
body:
type: json
data: |-
{
"username": "johndoe",
"email": "john@example.com"
}
auth:
type: bearer
token: "{{token}}"
settings:
encodeUrl: trueGraphQL
info:
name: Get User Data
type: http
seq: 1
http:
method: POST
url: "{{baseUrl}}/graphql"
body:
type: graphql
data: |-
query {
user(id: "123") {
id
name
email
}
}
auth:
type: bearer
token: "{{token}}"gRPC
info:
name: SayHello
type: grpc
seq: 1
grpc:
url: "{{host}}"
service: hello.HelloService
method: SayHello
body:
type: json
data: |-
{
"greeting": "hello"
}
auth: inheritWebSocket
info:
name: WebSocket Test
type: ws
seq: 1
ws:
url: "ws://localhost:8081/ws"
headers:
- name: Authorization
value: "Bearer {{token}}"
auth: inheritBody Formats
JSON
body:
type: json
data: |-
{
"username": "johndoe",
"email": "john@example.com"
}Text
body:
type: text
data: "This is plain text content"XML
body:
type: xml
data: |-
<?xml version="1.0" encoding="UTF-8"?>
<user>
<username>johndoe</username>
<email>john@example.com</email>
</user>Form URL Encoded
body:
type: form-urlencoded
data:
- name: username
value: johndoe
- name: password
value: secret123
- name: disabled_field
value: value
disabled: trueMultipart Form
body:
type: multipart-form
data:
- name: username
value: johndoe
- name: avatar
value: "@file(/path/to/avatar.jpg)"
- name: description
value: User profile pictureAuthentication
Bearer Token
auth:
type: bearer
token: "{{token}}"Basic Auth
auth:
type: basic
username: admin
password: secret123API Key
auth:
type: apikey
key: x-api-key
value: "{{api-key}}"
placement: headerOAuth2
auth:
type: oauth2
grant_type: authorization_code
callback_url: http://localhost:8080/callback
authorization_url: https://provider.com/oauth/authorize
access_token_url: https://provider.com/oauth/token
client_id: "{{client_id}}"
client_secret: "{{client_secret}}"
scope: read writeInherit / None
auth: inherit # Inherit from parent folder or collection
auth:
type: none # No authenticationSupported types: none, inherit, basic, bearer, apikey, digest, oauth2, awsv4, ntlm.
Headers and Parameters
Headers
Array of objects with name, value, optional disabled:
http:
headers:
- name: content-type
value: application/json
- name: x-api-key
value: "{{apiKey}}"
- name: x-request-id
value: "{{$uuid}}"
- name: disabled-header
value: some-value
disabled: trueQuery Parameters
http:
params:
query:
- name: page
value: "1"
- name: limit
value: "10"
- name: disabled_param
value: value
disabled: truePath Parameters
http:
params:
path:
- name: userId
value: "123"Variables
Variable Interpolation
Use {{variableName}} syntax:
- Environment variables:
{{baseUrl}},{{apiKey}} - Runtime variables:
{{user_id}} - Dynamic variables:
{{$guid}},{{$timestamp}},{{$randomInt}} - Response data:
{{res.body.token}}
Dynamic Variables
{{$guid}} Random GUID
{{$timestamp}} Current Unix timestamp
{{$isoTimestamp}} ISO 8601 timestamp
{{$randomInt}} Random integer (0-1000)
{{$randomEmail}} Random email address
{{$randomFirstName}} Random first name
{{$randomLastName}} Random last name
{{$randomPhoneNumber}} Random phone number
{{$randomCity}} Random city name
{{$randomStreetAddress}} Random street address
{{$randomCountry}} Random country
{{$randomUUID}} Random UUID v4Scripts
Pre-Request Script
runtime:
scripts:
- type: before-request
code: |-
const timestamp = Date.now();
bru.setVar("request_timestamp", timestamp);
req.setHeader("X-Timestamp", timestamp.toString());Post-Response Script
runtime:
scripts:
- type: after-response
code: |-
const token = res.body.token;
bru.setVar("authToken", token);
bru.setEnvVar("sessionToken", token);
if (res.status === 200) {
bru.setNextRequest("Get User Profile");
}Tests Script
runtime:
scripts:
- type: tests
code: |-
test("Status is 200", function() {
expect(res.status).to.equal(200);
});
test("Response has required fields", function() {
expect(res.body).to.have.property('id');
expect(res.body).to.have.property('name');
});Script type must be tests (not test).
Assertions
Declarative assertions without JavaScript:
runtime:
assertions:
- expression: res.status
operator: eq
value: "200"
- expression: res.body.success
operator: eq
value: "true"
- expression: res.body.data
operator: isJson
- expression: res.body.id
operator: isNumber
- expression: res.headers.content-type
operator: contains
value: application/jsonOperator names vary slightly by Bruno version and editor surface. Check Bruno's current Assertions docs for the exact operator names supported by your version when writing declarative assertions.
Settings
settings:
encodeUrl: true # URL-encode the request URL
timeout: 0 # Timeout in ms (0 = no timeout)
followRedirects: true # Follow HTTP redirects
maxRedirects: 5 # Max redirects to followEnvironment Files
Located in environments/ directory with .yml extension:
variables:
- name: baseUrl
value: https://api.example.com
- name: apiVersion
value: v1
- name: timeout
value: "30000"
- name: apiKey
value: ""
secret: true
- name: authToken
value: ""
secret: trueReference CI/CD secrets with {{process.env.VARIABLE_NAME}}.
Folder Files
folder.yml in subdirectories — settings apply to all requests in the folder:
info:
name: User Management
type: folder
http:
headers:
- name: x-api-version
value: v2
auth:
type: bearer
token: "{{token}}"
runtime:
scripts:
- type: before-request
code: |-
bru.setVar("folder_timestamp", Date.now());
- type: tests
code: |-
test("Folder level test", function() {
expect(res.status).to.be.oneOf([200, 201, 204]);
});Collection Files
collection.yml — settings apply to all requests in the collection:
info:
name: My API Collection
http:
headers:
- name: user-agent
value: Bruno/1.0
runtime:
scripts:
- type: before-request
code: |-
console.log("Collection pre-request script");
- type: tests
code: |-
test("Response time under 5s", function() {
expect(res.responseTime).to.be.below(5000);
});opencollection.yml
Required at collection root. Identifies the directory as a Bruno OpenCollection.
Minimal:
opencollection: 1.0.0
info:
name: My CollectionFull with optional fields:
opencollection: 1.0.0
info:
name: Bruno Example
config:
proxy:
inherit: true
request:
variables:
- name: tokenVar
value: tokenCollection
disabled: true
scripts:
- type: before-request
code: // console.log('Collection Level Script Logic')
docs:
content: |-
### Collection Documentation
type: text/markdown
bundled: false
ignore:
- node_modules
- .gitDo NOT put request-level fields (http:, method:, url:, body:) in opencollection.yml — those belong in individual request files.
Documentation
Request-level docs use markdown:
docs: |-
# User Creation API
This endpoint creates a new user.
## Required Fields
- name: User's full name
- email: User's email address