Collection Runner
The Collection Runner is Curlex's automated testing engine. Instead of clicking Send on each request one by one, you point the runner at a collection and it executes every request in sequence, records what happened, and gives you a clear pass/fail report.
It is particularly useful when:
- A new version of your API goes live and you want to check that nothing broke across dozens of endpoints.
- You have a workflow that spans multiple requests — like logging in, creating a record, updating it, and deleting it — and you want to run the whole flow end to end.
- You test the same endpoints with many different inputs — the runner can read a CSV or JSON file and repeat the entire sequence once for each row of data.
- You need to share results with someone who does not have Curlex — export as PDF, HTML, or JUnit XML.
Opening the Runner
Click the Runner icon in the sidebar rail (the play button). The runner opens in a two-panel view:
- Left panel — the list of all previous runs.
- Right panel — the detailed results of whichever run you have selected.
Setting Up a Run
1. Choose a Collection
Use the Collection dropdown at the top to pick which collection to run. Every collection in your active workspace is listed.
2. Choose an Environment
Use the Environment dropdown to select which set of variables to use. If your requests contain {{baseUrl}} or other variable placeholders, pick the environment that has those values defined. Choose No Environment if your requests use no variables.
3. Choose Which Requests to Run
Click Configure Run to expand the sequence panel. You will see every request in the collection listed in order.
- Uncheck any request you want to skip — it will be omitted from this run without being changed or deleted.
- Drag requests up or down to change the order they run in.
By default, every request is selected and runs in its natural collection order (top-level requests first, then folders in depth-first order).
4. Set Run Options
Still in the Configure Run panel, you can tune how the runner behaves:
| Option | What it does | Default |
|---|---|---|
| Delay Between Requests | Waits this many milliseconds between each request. Useful if the API has rate limits and you need to slow down. | 0 ms |
| Retry Failed Requests | If a request returns an error (non-2xx status), automatically try it again this many times before giving up. | 0 |
| Retry Delay | How long to wait between retry attempts. | 1 000 ms |
| Stop on First Failure | If any request fails or any test assertion fails, stop the entire run immediately instead of continuing. | Off |
5. Upload a Data File (optional)
If you want to run the same collection multiple times with different data — for example, testing with ten different user accounts — upload a data file in the Data File section.
Click Upload CSV or JSON, select your file, and Curlex will show you how many rows (iterations) it found. Click Preview to verify the data looks right before you commit to a long run.
See Data-Driven Testing below for full details.
Starting and Stopping a Run
Click Run to start. The button shows how many requests are selected — e.g. Run (12) — so you can confirm before going.
While the run is active:
- The button changes to a spinning Running… indicator.
- Results appear in real time as each request completes — you do not have to wait for it to finish.
- A notification appears when the run completes.
To stop before it finishes, click Stop. The request currently in progress will finish, and then the run halts. The partial run is saved to history with a Stopped label.
Reading the Results
The Run History List
Every completed run appears in the left panel, most recent at the top. Each entry shows:
- Collection name — which collection was run.
- Environment — which environment was used (or "No Environment").
- ✓ and ✗ counts — how many requests passed (green) and failed (red).
- Total time — how long the whole run took.
- ⏹ icon — shown if the run was stopped before finishing.
- Iteration count — shown if a data file was used.
Click any run to load its full details on the right.
The Results Panel
The summary bar at the top tells you the overall outcome:
- Total requests executed
- How many passed (green) and how many failed (red)
- Total time elapsed
- Total data transferred
The results table shows one row per request:
| Column | What it shows |
|---|---|
| Method | The HTTP method in its colour-coded label |
| Request | The request name and URL |
| Status | The HTTP status code — green tick for 2xx, red cross for anything else |
| Tests | How many test assertions passed and failed for this request |
| Time | How long this specific request took |
| Size | How much data came back |
| Error | A badge describing the failure type — only shown for failed requests |
Click any row to expand the full response detail for that request — the response body, headers, cookies, timing waterfall, test assertion results, and any pre-request script log output.
If you used a data file, results are grouped into Iteration 1, Iteration 2, etc., so you can see what happened for each row of your data.
Request Timing
Inside a result row, the timing panel shows a visual waterfall of how the request time was spent:
| Stage | What it measures |
|---|---|
| DNS | How long it took to look up the server's address |
| TCP | Time to open the connection |
| TLS | Time for the security handshake (HTTPS only) |
| Waiting | Time between sending the request and receiving the first byte back — this is the server's processing time |
| Download | Time to receive the complete response |
This is the fastest way to see whether a slow request is caused by slow DNS, a sluggish server, or a large response.
Exporting Results
To export a run, hover over it in the left panel and click the Download icon. Three formats are available:
| Format | Best for |
|---|---|
| Sharing with stakeholders, archiving, or printing. A clean, readable summary of what was tested and what the results were. | |
| HTML | Viewing in a browser or attaching to an email. A standalone file that opens anywhere without any special software. |
| JUnit XML | Connecting to CI/CD tools like Jenkins, GitHub Actions, GitLab CI, or CircleCI. Most CI platforms can read JUnit XML natively and display test pass/fail results in their dashboards. |
All three formats include the run summary, configuration, and per-request results with test assertion details. Request and response bodies are intentionally excluded to keep files small and to avoid accidentally including sensitive data.
Test Scripts
Test scripts run automatically after each request completes and check that the response is what you expected. If an assertion fails, the request is marked as failed in the results.
Where to Write Them
Open any request, click the Scripts tab, and write JavaScript in the Test Script area. The script runs every time that request is executed — both in the runner and when you click Send manually.
Basic Examples
// Check the HTTP status code
fc.test("Returns 200", function () {
fc.response.to.have.status(200);
});
// Check a value in the JSON response
fc.test("User has the correct name", function () {
var body = fc.response.json();
fc.expect(body.name).to.equal("Alice");
});
// Check a response header
fc.test("Content type is JSON", function () {
fc.response.to.have.header("Content-Type", "application/json");
});
// Check response speed
fc.test("Response arrives in under a second", function () {
fc.expect(fc.response.responseTime).to.be.below(1000);
});
// Check that the response is one of several valid status codes
fc.test("Request was accepted", function () {
fc.expect(fc.response.code).to.be.oneOf([200, 201, 202]);
});
Validating the Response Structure
Use jsonSchema to verify that a response matches the shape you expect — useful for catching API changes that add or remove fields:
fc.test("Response has required fields", function () {
fc.response.to.have.jsonSchema({
type: "object",
required: ["id", "name", "email"],
properties: {
id: { type: "number" },
name: { type: "string" },
email: { type: "string" }
}
});
});
If validation fails, the error message tells you exactly which property did not match.
Chaining Requests Together
This is one of the most powerful things you can do with test scripts — saving a value from one response so that a later request can use it:
// In the "POST /login" request's test script:
fc.test("Login succeeded", function () {
fc.expect(fc.response.code).to.equal(200);
var token = fc.response.json().access_token;
fc.expect(token).to.exist;
fc.environment.set("authToken", token); // save it for subsequent requests
});
Then in any subsequent request, use {{authToken}} in the Authorization header. The runner substitutes the value saved by the login script.
All Available Assertions
fc.expect(value).to.equal(expected) // exact match
fc.expect(value).to.eql(expected) // deep equality for objects/arrays
fc.expect(value).to.include("some text") // string or array contains this
fc.expect(value).to.exist // not null or undefined
fc.expect(value).to.be.true
fc.expect(value).to.be.false
fc.expect(value).to.be.above(100)
fc.expect(value).to.be.below(500)
fc.expect(value).to.be.oneOf([200, 201])
fc.expect(array).to.have.length(3)
fc.expect(object).to.have.property("id")
fc.expect(object).to.have.nested.property("user.address.city")
fc.expect(value).not.to.equal(other) // negation — add .not. anywhere
Reading and Writing Variables in Test Scripts
// Read a variable from the active environment
var base = fc.environment.get("baseUrl");
// Write a variable (available immediately to subsequent requests in the run)
fc.environment.set("userId", fc.response.json().id);
// Collection variables (scoped to the collection)
fc.collectionVariables.set("sessionId", "abc123");
var session = fc.collectionVariables.get("sessionId");
Pre-Request Scripts
A pre-request script runs before the HTTP request is sent. You write it in the same Scripts tab, in the Pre-Request Script area.
Common uses:
// Add a timestamp to make each request unique
fc.environment.set("timestamp", Date.now().toString());
// Generate a unique ID to track this request
fc.environment.set("requestId", crypto.randomUUID());
// Point to the right server based on a data file column
var env = fc.iterationData.get("environment") || fc.environment.get("target");
fc.environment.set("baseUrl",
env === "prod" ? "https://api.example.com" : "https://staging.api.example.com"
);
// Resolve a variable placeholder programmatically
var fullUrl = fc.variables.replaceIn("https://{{baseUrl}}/users/{{userId}}");
Pre-request scripts can read and write variables just like test scripts, but they cannot access fc.response (nothing has been received yet) and cannot run fc.test() assertions.
Any console.log() calls in a pre-request script show up in the Pre-request Logs tab inside the result detail panel.
Controlling the Flow
Inside either a test script or pre-request script, you can change what runs next:
// Skip the next request in the sequence
fc.execution.skipRequest();
// Jump to a specific request by name
fc.execution.setNextRequest("Cleanup — Delete User");
// Stop the entire run immediately
fc.execution.abort();
setNextRequest is useful for conditional workflows — for example, if a setup step fails, jump directly to a cleanup step instead of continuing through the rest of the sequence. The runner detects infinite loops automatically.
Data-Driven Testing
Data-driven testing means running the same collection multiple times, once per row in a file. Each row supplies a different set of values — different users, different IDs, different expected outcomes — so you can test many scenarios with a single collection.
Preparing a CSV File
The first row of the file is the header — these names become the variable names. Each following row is one iteration:
username,password,expected_status
alice@example.com,hunter2,200
bob@example.com,wrongpassword,401
,missinguser,400
- Values containing commas must be wrapped in double quotes:
"Smith, John" - Double quotes inside values are escaped by doubling:
"She said ""hello"""
Preparing a JSON File
The file must be an array of objects. Each object is one iteration:
[
{ "userId": "101", "role": "admin" },
{ "userId": "202", "role": "editor" },
{ "userId": "303", "role": "viewer" }
]
Using the Data in Requests
Once a file is loaded, every column header is available as a {{variable}} anywhere in your requests — no script needed:
URL: https://{{baseUrl}}/users/{{userId}}
Header: X-Role: {{role}}
Body: { "username": "{{username}}", "password": "{{password}}" }
Curlex substitutes the values for the current iteration automatically.
Using the Data in Scripts
var username = fc.iterationData.get("username");
var expectedStatus = parseInt(fc.iterationData.get("expected_status"));
fc.test("Returns the expected status", function () {
fc.expect(fc.response.code).to.equal(expectedStatus);
});
How Iterations Work
- The entire request sequence runs once per data row.
- Results in the panel are grouped by Iteration 1, Iteration 2, etc.
- Variable changes made by scripts during one iteration carry into the next — if you set
{{authToken}}in iteration 1, it is still set at the start of iteration 2. Plan accordingly if you need a clean state each time.
Previewing Your Data
After uploading a file, click Preview to see a paginated table of what was parsed. Rows with formatting problems are highlighted. Check this before a long run — fixing a CSV issue before 200 iterations saves a lot of time.
Secret Variables and Masking
If your environments contain Secret variables, their values show as **** in all run logs and result output. The actual values are still sent correctly in your requests — they are only hidden from the display.
Values shorter than 5 characters are never masked, to avoid accidentally blanking out unrelated content like short numeric codes.
Error Categories
When a request fails, a badge in the results table explains why:
| Badge | What it means |
|---|---|
| Timeout | The server did not respond within the configured timeout period |
| Network | Could not connect — the server may be unreachable or the URL may be wrong |
| DNS | The hostname could not be resolved — check the URL for typos |
| SSL | A TLS or certificate error — common with self-signed certificates or mismatched hostnames |
| Script Error | A test or pre-request script threw a JavaScript error |
| Assertion Failure | A fc.test() assertion failed — the response did not match what was expected |
Practical Tips
Chain login before protected requests
Put a login request at the top of your sequence, extract the token in its test script with fc.environment.set(), and use {{authToken}} in all subsequent requests.
Use Stop on First Failure for quick health checks When you just want to know "is this API working?", enabling this option means the run fails fast as soon as anything goes wrong, instead of running the full suite.
Add a small delay for rate-limited APIs If your API enforces a rate limit (e.g. 10 requests per second), a 50–100 ms delay between requests keeps you comfortably below the limit without any manual counting.
Store data files alongside your collection exports Keep your CSV and JSON test data files in the same folder as your exported collections. Share both with teammates so they can run the same tests immediately.
Export JUnit XML for your CI pipeline Most CI/CD tools — Jenkins, GitHub Actions, GitLab CI, CircleCI — display JUnit XML test results natively. Export after every run and attach the XML file as a build artifact to get a proper test report in your pipeline without any extra tooling.
Frequently Asked Questions
Do test scripts run when I click Send on a single request? Yes. Test scripts run both in the runner and in the normal single-request view. Results appear in the Tests tab of the response panel.
Can I use Postman-style pm.* API calls?
Yes. pm is a full alias for fc. Scripts written with pm.test(), pm.expect(), pm.environment.get(), etc. work without changes.
Why aren't my {{variables}} being substituted?
Make sure an environment is selected in the runner. Variable names are case-sensitive — {{baseUrl}} and {{BaseURL}} are treated as different variables.
Can I run just one folder instead of the whole collection? Yes. In the Configure Run panel, uncheck the requests you do not want. Unchecking every request in a folder skips the entire folder.
The run stops immediately with a "Stopped" status — why?
Check whether a pre-request or test script calls fc.execution.abort(). Also check whether Stop on First Failure is enabled and whether the first request is already failing.
Are run results saved permanently? Yes. Run history and results are saved in Curlex's local database and persist across restarts. They are also included in Git Sync backups if you have sync enabled.
How many iterations can I run? There is no built-in limit. Very large runs (thousands of rows combined with many requests) will take a long time — test with a smaller sample first.
Can I run the same collection against two environments back to back?
Not in a single run — you would run twice, once for each environment. A workaround is to include the target environment in your data file and use a pre-request script to set baseUrl based on fc.iterationData.get("environment").