Skip to main content

Utilities Reference

Helper functions exported by @esimplicity/stack-tests.

Variable Interpolation

interpolate

Replaces {varName} placeholders with values from a variables object.

import { interpolate } from '@esimplicity/stack-tests';

Signature

function interpolate(template: string, vars: Record<string, string>): string

Parameters

ParameterTypeDescription
templatestringString containing {varName} placeholders
varsRecord<string, string>Variable values

Returns

String with placeholders replaced.

Example

const vars = {
userId: '123',
email: 'test@example.com',
};

interpolate('/users/{userId}', vars);
// Result: '/users/123'

interpolate('Email: {email}', vars);
// Result: 'Email: test@example.com'

interpolate('No vars here', vars);
// Result: 'No vars here'

interpolate('Missing: {unknown}', vars);
// Result: 'Missing: {unknown}' (unchanged)

JSON Utilities

tryParseJson

Safely parses a JSON string, returning undefined on failure.

import { tryParseJson } from '@esimplicity/stack-tests';

Signature

function tryParseJson(text: string): unknown

Example

tryParseJson('{"key": "value"}');
// Result: { key: 'value' }

tryParseJson('invalid json');
// Result: undefined

tryParseJson('');
// Result: undefined

selectPath

Accesses nested properties using JSONPath-like syntax.

import { selectPath } from '@esimplicity/stack-tests';

Signature

function selectPath(root: unknown, path: string): unknown

Parameters

ParameterTypeDescription
rootunknownObject to traverse
pathstringPath expression

Path Syntax

SyntaxDescriptionExample
propProperty accessname
prop.nestedNested propertyuser.email
arr[0]Array indexitems[0]
arr[0].propCombineditems[0].id

Example

const data = {
user: {
id: 123,
profile: {
name: 'John',
},
},
items: [
{ id: 1, name: 'First' },
{ id: 2, name: 'Second' },
],
};

selectPath(data, 'user.id');
// Result: 123

selectPath(data, 'user.profile.name');
// Result: 'John'

selectPath(data, 'items[0].name');
// Result: 'First'

selectPath(data, 'items[1].id');
// Result: 2

selectPath(data, 'nonexistent');
// Result: undefined

parseExpected

Parses expected values with type coercion and variable interpolation.

import { parseExpected } from '@esimplicity/stack-tests';

Signature

function parseExpected(input: string, world: World): unknown

Behavior

InputResult
"null"null
"true"true
"false"false
"123" (numeric)123 (number)
"{varName}"Value from world.vars
"text""text" (string)

Example

const world = { vars: { count: '42' }, headers: {}, cleanup: [] };

parseExpected('null', world); // null
parseExpected('true', world); // true
parseExpected('123', world); // 123 (number)
parseExpected('{count}', world); // '42'
parseExpected('hello', world); // 'hello'

Assertions

assertMasked

Asserts that a value equals '****' (masked value).

import { assertMasked } from '@esimplicity/stack-tests';

Signature

function assertMasked(val: unknown): void

Example

assertMasked('****');  // Passes
assertMasked('secret'); // Throws error

Cleanup

registerCleanup

Adds an item to the world's cleanup queue.

import { registerCleanup } from '@esimplicity/stack-tests';

Signature

function registerCleanup(
world: World,
item: {
method?: 'DELETE' | 'POST' | 'PATCH' | 'PUT';
path: string;
headers?: Record<string, string>;
}
): void

Parameters

ParameterTypeDefaultDescription
methodstring'DELETE'HTTP method for cleanup
pathstringRequiredCleanup endpoint path
headersRecord<string, string>{}Additional headers

Example

// Register DELETE cleanup
registerCleanup(world, {
path: '/users/123',
});

// Register with specific method
registerCleanup(world, {
method: 'POST',
path: '/users/123/deactivate',
});

// Register with headers
registerCleanup(world, {
path: '/users/123',
headers: { 'X-Force': 'true' },
});

World Management

initWorld

Creates a new initialized World object.

import { initWorld } from '@esimplicity/stack-tests';

Signature

function initWorld(): World

Returns

{
vars: {},
headers: {},
cleanup: [],
}

Example

const world = initWorld();
world.vars['key'] = 'value';
world.headers['Authorization'] = 'Bearer token';

World Type

World

The test state container type.

import type { World, CleanupItem } from '@esimplicity/stack-tests';

Definition

type World = {
vars: Record<string, string>;
headers: Record<string, string>;
cleanup: CleanupItem[];
skipCleanup?: boolean;

// Response state (set by API steps)
lastResponse?: APIResponse;
lastStatus?: number;
lastText?: string;
lastJson?: unknown;
lastHeaders?: Record<string, string>;
lastContentType?: string;
};

type CleanupItem = {
method: 'DELETE' | 'POST' | 'PATCH' | 'PUT';
path: string;
headers?: Record<string, string>;
};

Worker Configuration

resolveWorkers

Resolves the number of Playwright workers based on environment variables, test type, and CI detection.

import { resolveWorkers } from '@esimplicity/stack-tests';

Signature

function resolveWorkers(options?: {
testType?: 'api' | 'ui' | 'tui' | 'hybrid';
ciWorkers?: number;
defaultWorkers?: number;
}): number | undefined

Parameters

ParameterTypeDefaultDescription
testTypestringundefinedTest type. 'tui' forces 1 worker (sequential).
ciWorkersnumber1Workers to use in CI when WORKERS env is not set
defaultWorkersnumberundefinedWorkers locally when WORKERS env is not set. undefined lets Playwright decide (50% of CPU cores).

Precedence

  1. testType: 'tui' -- always returns 1
  2. WORKERS env var set to a positive integer -- returns that number
  3. WORKERS env var set to 'auto' or empty -- treated as unset
  4. CI env var is truthy -- returns ciWorkers (default: 1)
  5. Otherwise -- returns defaultWorkers (default: undefined)

Example

import { resolveWorkers } from '@esimplicity/stack-tests';

// playwright.config.ts
export default defineConfig({
workers: resolveWorkers(),
projects: [
{ ...apiBdd },
{ ...uiBdd },
{ ...tuiBdd, workers: resolveWorkers({ testType: 'tui' }) },
],
});
# Override via environment
WORKERS=4 npm test
WORKERS=auto npm test # same as unset

getCpuCount

Returns the number of available CPU cores on the current machine.

import { getCpuCount } from '@esimplicity/stack-tests';

Signature

function getCpuCount(): number

Example

import { getCpuCount } from '@esimplicity/stack-tests';

console.log(`Available CPU cores: ${getCpuCount()}`);

Usage in Step Definitions

Complete Example

import { createBdd } from 'playwright-bdd';
import { test } from './fixtures';
import { interpolate, selectPath, registerCleanup } from '@esimplicity/stack-tests';

const { When, Then } = createBdd(test);

When('I create a user with email {string}', async ({ api, world }, email) => {
// Interpolate variables in email
const resolvedEmail = interpolate(email, world.vars);

// Send request
const result = await api.sendJson('POST', '/users', {
email: resolvedEmail,
}, world.headers);

// Store response
world.lastStatus = result.status;
world.lastJson = result.json;

// Extract and store ID
const userId = selectPath(result.json, 'id');
world.vars['userId'] = String(userId);

// Register cleanup
registerCleanup(world, {
path: `/users/${userId}`,
});
});

Then('the user ID should be stored', async ({ world }) => {
const userId = world.vars['userId'];
if (!userId) {
throw new Error('userId not set');
}
});