API testing · Scripts · JavaScript · Tutorial
Write API tests with swc.* scripts (Postman pm.* compatible)
A hands-on guide to Postman-style test scripts: assertions, JSON Schema validation, variable scopes, swc.sendRequest, setNextRequest, script inheritance and the bundled libraries.

API tests in the Postman style are small JavaScript snippets that run after a response arrives. Each swc.test('name', fn) call is one named check. Inside it you assert with swc.expect(...) or with swc.response.to... helpers, and a thrown assertion marks that test as failed. A pre-request script runs before the request is sent and is used to prepare data: timestamps, signatures, tokens.
Swastik Codes runs these scripts in a sandboxed JavaScript engine that implements Postman's documented pm.* API, so most scripts written for Postman work unchanged. The examples use swc.*, Swastik Codes' name for the same API: swc and pm are one object, so every swc. below can also be written pm., and imported pm.* scripts can be converted to swc.* in one click. This guide is a set of working examples you can paste into the app, from the basic status check to chained requests and runner control.
Where scripts live
Open a request in the app and select the Scripts tab. It has two editors:
- Pre-request – runs before the request is sent.
- Post-response – runs after the response arrives. This is where tests go.
Collections and folders have a Scripts tab too. When a request is sent, the pre-request scripts run in order collection → folder(s) → request, then the request is sent, then the post-response scripts run in the same order. Put shared logic (an auth header, a common status check) at the collection level and request-specific checks on the request.
The editor offers snippets for common tasks, such as Status code is 200, JSON value check, Validate JSON schema and Save a token from the response, so you rarely start from an empty file.
Results appear in the response panel's Test Results tab, with a passed/total count, and console.log output goes to the console panel.
Sending requires a free account, because the web app sends them through the swastik.codes server; that server also refuses private and loopback addresses, so the API must be publicly reachable. The examples below use https://jsonplaceholder.typicode.com and https://httpbin.org, which you can call right away.
The basics: status, time, headers
Create a GET request to https://jsonplaceholder.typicode.com/users/1 and add this post-response script:
swc.test('status is 200', function () {
swc.response.to.have.status(200);
});
swc.test('responds within 1500 ms', function () {
swc.expect(swc.response.responseTime).to.be.below(1500);
});
swc.test('returns JSON', function () {
swc.response.to.have.header('Content-Type');
swc.expect(swc.response.headers.get('Content-Type')).to.include('application/json');
});Other status helpers read well in tests: swc.response.to.be.ok (exactly 200), swc.response.to.be.success (any 2XX), swc.response.to.be.clientError, swc.response.to.be.notFound, swc.response.to.be.unauthorized and swc.response.to.be.rateLimited.
Asserting on the JSON body
swc.response.json() parses the body. From there it is ordinary chai:
const user = swc.response.json();
swc.test('user has the expected identity', function () {
swc.expect(user.id).to.eql(1);
swc.expect(user.email).to.match(/@/);
swc.expect(user).to.have.property('address');
swc.expect(user.address.geo).to.have.all.keys('lat', 'lng');
});
swc.test('company name is a non-empty string', function () {
swc.expect(user.company.name).to.be.a('string').and.not.empty;
});For quick checks without parsing, jsonBody takes a lodash-style path:
swc.test('city is present', function () {
swc.response.to.have.jsonBody('address.city');
});
swc.test('username is Bret', function () {
swc.response.to.have.jsonBody('username', 'Bret');
});And swc.response.to.have.body(/Leanne/) checks the raw text against a regular expression.
Validating against a JSON Schema
Checking individual fields catches the fields you thought of. A schema catches the rest: wrong types, missing required fields, unexpected nulls. jsonSchema validates with Ajv:
const userSchema = {
type: 'object',
required: ['id', 'name', 'email', 'address'],
properties: {
id: { type: 'integer', minimum: 1 },
name: { type: 'string' },
email: { type: 'string' },
address: {
type: 'object',
required: ['city', 'zipcode'],
properties: {
city: { type: 'string' },
zipcode: { type: 'string' }
}
}
}
};
swc.test('matches the user schema', function () {
swc.response.to.have.jsonSchema(userSchema);
});If a test fails, the message lists every schema error, because Ajv runs with allErrors. Older scripts that use tv4.validate(data, schema) also work: tv4 is one of the bundled libraries.
Variables and scopes
Variables are written as {{name}} in URLs, headers and bodies, and read or written in scripts:
| Scope | Script API | Typical use |
|---|---|---|
| Global | swc.globals | values shared by every collection in the workspace |
| Collection | swc.collectionVariables | base URLs and ids belonging to one API |
| Environment | swc.environment | per-environment values: staging vs production hosts, credentials |
| Data | swc.iterationData | the current row of a data file in a collection run (read-only) |
| Local | swc.variables | values for this request or run only |
swc.variables.get('name') resolves through all scopes, with the narrowest winning (local, then data, environment, collection, global). swc.variables.replaceIn('{{baseUrl}}/users') expands a string the same way.
A common pattern is saving a value from one response for the next request:
// Post-response script of POST /posts
const created = swc.response.json();
swc.test('post was created', function () {
swc.response.to.have.status(201);
swc.expect(created.id).to.be.a('number');
});
swc.collectionVariables.set('postId', created.id);The next request can then call GET https://jsonplaceholder.typicode.com/posts/{{postId}}. (JSONPlaceholder fakes writes, so that id will not really exist there, but the pattern is the same on a real API.)
Pre-request scripts
Pre-request scripts prepare the request. They can change it directly through swc.request:
// Pre-request: add a timestamp and a request id
swc.variables.set('now', new Date().toISOString());
swc.request.headers.upsert({ key: 'X-Request-Id', value: require('uuid').v4() });Signing a request with an HMAC is a typical job for crypto-js:
const CryptoJS = require('crypto-js');
const secret = swc.environment.get('signingSecret');
const body = swc.request.body ? swc.request.body.raw || '' : '';
const ts = Math.floor(Date.now() / 1000).toString();
const signature = CryptoJS.HmacSHA256(ts + '.' + body, secret).toString(CryptoJS.enc.Hex);
swc.request.headers.upsert({ key: 'X-Timestamp', value: ts });
swc.request.headers.upsert({ key: 'X-Signature', value: 'sha256=' + signature });If a pre-request script throws, the request is not sent and the error is shown, which is how the desktop Postman app behaves. In a collection run the request is still sent, as Newman does.
Calling another endpoint from a script
swc.sendRequest sends an extra request from inside a script. It accepts a URL string or a request object, and works with a callback or as a promise:
// Pre-request: fetch a token if we do not have one yet
if (!swc.environment.get('token')) {
swc.sendRequest({
url: 'https://httpbin.org/post',
method: 'POST',
header: [{ key: 'Content-Type', value: 'application/json' }],
body: { mode: 'raw', raw: JSON.stringify({ token: 'demo-token' }) }
}, function (err, res) {
if (err) { console.log(err); return; }
swc.environment.set('token', res.json().json.token);
});
}httpbin echoes the JSON you post under json, so this saves demo-token. Against a real identity provider you would post credentials and read access_token. For OAuth 2.0 it is usually simpler to use the built-in OAuth 2.0 authorization type instead; see OAuth 2.0 in an API client.
Requests made with swc.sendRequest appear in the console as swc.sendRequest entries, so you can see exactly what the script sent and received.
Controlling a collection run
When requests run in the collection runner (or in a monitor), scripts can change the order:
// Post-response: stop polling once the job is done
const job = swc.response.json();
if (job.status === 'done') {
swc.execution.setNextRequest('Download result');
} else {
swc.execution.setNextRequest('Check job status'); // run this request again
}swc.execution.setNextRequest(null) ends the current iteration. swc.execution.skipRequest(), called from a pre-request script, skips the send:
// Pre-request: only run on staging
if (swc.environment.get('env') !== 'staging') {
swc.execution.skipRequest();
}Skipped requests are counted separately in the run results. The legacy postman.setNextRequest('name') spelling also works.
Bundled libraries
require() gives access to these libraries without installing anything: chai, lodash, moment, crypto-js, uuid, ajv, tv4, xml2js, cheerio, csv-parse/lib/sync, handlebars, and Node-style modules such as buffer, url, querystring, util, events, path, assert and crypto. atob and btoa are available too.
Parsing XML, for example:
const xml2js = require('xml2js');
xml2js.parseString(swc.response.text(), function (err, result) {
swc.test('XML parses', function () {
swc.expect(err).to.be.null;
});
});Visualizing a response
swc.visualizer.set(template, data) renders a Handlebars template in the response's Visualize view, which is handy for turning a JSON list into a readable table:
const template = `
<table>
<tr><th>Name</th><th>Email</th></tr>
{{#each users}}
<tr><td>{{name}}</td><td>{{email}}</td></tr>
{{/each}}
</table>`;
// GET https://jsonplaceholder.typicode.com/users
swc.visualizer.set(template, { users: swc.response.json() });Older script styles
Collections that have been around for a while often contain legacy syntax. It still runs:
tests['Status code is 200'] = responseCode.code === 200;
tests['Body mentions Leanne'] = responseBody.has('Leanne');
postman.setEnvironmentVariable('lastStatus', responseCode.code);Converting to swc.test is worth it when you next touch the script, because named tests with assertion messages are much easier to debug.
Troubleshooting
swc.response.json() throws. The body is not JSON: check the status (an HTML error page is common) and the Content-Type. Test the status first, so the failure says what really happened.
A variable is empty. Check which scope you set and which you read. swc.environment.set does nothing useful without an active environment; use swc.collectionVariables when the value belongs to the collection.
Tests pass locally but fail in a run. In the runner, variable changes always carry to the next request within an iteration, but they carry into the next iteration only when Persist variables between requests is on; the order follows the collection unless scripts call setNextRequest.
A module is not found. Only the bundled libraries listed above can be required.
Frequently asked questions
Are Postman test scripts compatible?
Scripts that use Postman's documented pm.* API (tests, expectations, variables, sendRequest, cookies, visualizer, execution control) and the legacy tests[]/postman.* globals are supported. Compatibility is built against the public documentation.
Can tests run on a schedule?
Yes. Collections with tests can run on a cron schedule as monitors, with email or webhook alerts when they fail.
Can I run the same tests with different data?
Yes. Attach a CSV or JSON file in the collection runner and read the current row with swc.iterationData. See data-driven API testing.
Do async tests work?
Yes. A test function can return a promise, or take a done callback, and its result is recorded when it settles.
Try it
Open the app, paste the first example into a request's Post-response script and send it with a free account. The API testing feature page summarises the scripting support. When you have a few requests with tests, run them as a collection with a data file, or import your existing Postman collection and keep the scripts you already have.