Test FastEdge WASM binaries programmatically — no server required. Use it in CI pipelines, agent scripts, or alongside your existing test runner (vitest, jest, etc.).
Supports both FastEdge binary types:
- CDN (proxy-wasm) — request/response filter binaries
- HTTP-WASM — component model HTTP handler binaries
The WASM type is detected automatically from the binary.
npm install @gcoredev/fastedge-test
# or
pnpm add @gcoredev/fastedge-testRequirements: Node.js 18+
Import the test framework to test WASM binaries programmatically — no server, no browser:
| Import | Purpose |
|---|---|
@gcoredev/fastedge-test |
Low-level runner — load and execute WASM directly |
@gcoredev/fastedge-test/test |
High-level test framework — define and run structured test suites |
Start with @gcoredev/fastedge-test/test unless you need direct runner control.
Run a local server with a browser UI, REST API, and WebSocket log streaming — useful for interactive debugging without VSCode:
npx @gcoredev/fastedge-test
# Opens http://localhost:5179Or with a custom port:
PORT=8080 npx @gcoredev/fastedge-testSee Local Debugger Server for the full guide including programmatic usage, REST API access, and WebSocket log streaming.
import { defineTestSuite, runAndExit, runFlow } from '@gcoredev/fastedge-test/test';
import { assertRequestHeader, assertFinalStatus } from '@gcoredev/fastedge-test/test';
const suite = defineTestSuite({
wasmPath: './build/my-cdn-app.wasm',
tests: [
{
name: 'injects x-custom header',
run: async (runner) => {
const result = await runFlow(runner, {
url: 'https://example.com/page',
method: 'GET',
requestHeaders: { 'user-agent': 'Mozilla/5.0' },
responseStatus: 200,
});
assertRequestHeader(result.hookResults.onRequestHeaders, 'x-custom', 'expected-value');
},
},
{
name: 'blocks /admin with 403',
run: async (runner) => {
const result = await runFlow(runner, { url: 'https://example.com/admin' });
assertFinalStatus(result, 403);
},
},
],
});
await runAndExit(suite);
// Output:
// ✓ injects x-custom header (12ms)
// ✓ blocks /admin with 403 (5ms)
// 2/2 passed in 17msimport { defineTestSuite, runAndExit } from '@gcoredev/fastedge-test/test';
const suite = defineTestSuite({
wasmPath: './build/my-http-app.wasm',
tests: [
{
name: 'responds with 200 OK',
run: async (runner) => {
const response = await runner.execute({
path: '/api/hello',
method: 'GET',
headers: { 'content-type': 'application/json' },
});
if (response.status !== 200) {
throw new Error(`Expected 200, got ${response.status}`);
}
},
},
],
});
await runAndExit(suite);Validates and returns a typed suite definition.
type TestSuite =
| { wasmPath: string; runnerConfig?: RunnerConfig; tests: TestCase[] }
| { wasmBuffer: Buffer; runnerConfig?: RunnerConfig; tests: TestCase[] }Exactly one of wasmPath or wasmBuffer is required — TypeScript enforces this at compile time.
interface RunnerConfig {
dotenvEnabled?: boolean; // Load .env file (default: false)
enforceProductionPropertyRules?: boolean; // CDN property access control (default: true)
}Runs all tests sequentially. Each test gets a fresh, isolated runner instance.
interface SuiteResult {
passed: number;
failed: number;
total: number;
durationMs: number;
results: TestResult[];
}
interface TestResult {
name: string;
passed: boolean;
error?: string;
durationMs: number;
}Runs the suite, prints a summary to stdout, and exits with code 0 (all passed) or 1 (any failures). Designed for CI scripts and Makefile targets.
Convenience wrapper around the full CDN request/response flow. Accepts named options and derives HTTP/2 pseudo-headers from the URL automatically.
interface FlowOptions {
url: string;
method?: string; // Default: 'GET'
requestHeaders?: Record<string, string>;
requestBody?: string;
responseStatus?: number; // Default: 200
responseStatusText?: string;
responseHeaders?: Record<string, string>;
responseBody?: string;
properties?: Record<string, unknown>;
enforceProductionPropertyRules?: boolean; // Default: true
}Returns a FullFlowResult containing hookResults, finalResponse, and calculatedProperties.
Load and validate a fastedge-config.test.json file, returning a typed TestConfig.
import { loadConfigFile, defineTestSuite, runAndExit } from '@gcoredev/fastedge-test/test';
const config = await loadConfigFile('./fastedge-config.test.json');
const suite = defineTestSuite({
wasmPath: './build/app.wasm',
tests: [
{
name: 'uses config properties',
run: async (runner) => {
const result = await runFlow(runner, {
url: 'https://example.com',
properties: config.properties,
});
},
},
],
});All helpers throw a descriptive Error on failure — compatible with any test framework or plain try/catch.
assertRequestHeader(hookResult, 'x-custom') // header exists
assertRequestHeader(hookResult, 'x-custom', 'value') // header equals value
assertNoRequestHeader(hookResult, 'x-should-not-exist')
assertResponseHeader(hookResult, 'cache-control', 'no-store')
assertNoResponseHeader(hookResult, 'x-sensitive')assertFinalStatus(fullFlowResult, 200)
assertFinalHeader(fullFlowResult, 'content-type', 'application/json')assertReturnCode(hookResult, 0) // 0 = Ok, 1 = PauseassertLog(hookResult, 'cache hit') // at least one log contains substring
assertNoLog(hookResult, 'error') // no log contains substring
const found = logsContain(hookResult, 'x') // boolean — for conditional logicassertPropertyAllowed(hookResult, 'client.ip') // access was permitted
assertPropertyDenied(hookResult, 'internal.key') // access was denied
const violated = hasPropertyAccessViolation(hookResult)The assertion helpers throw plain Error instances, so they work inside any test framework:
import { describe, it } from 'vitest';
import { createRunner } from '@gcoredev/fastedge-test';
import { runFlow, assertFinalStatus } from '@gcoredev/fastedge-test/test';
describe('my CDN app', () => {
it('returns 200 for homepage', async () => {
const runner = await createRunner('./build/app.wasm');
try {
const result = await runFlow(runner, { url: 'https://example.com/' });
assertFinalStatus(result, 200);
} finally {
await runner.cleanup();
}
});
});Or let defineTestSuite manage lifecycle and inspect SuiteResult inside your test:
it('all flows pass', async () => {
const suite = defineTestSuite({ wasmPath: './build/app.wasm', tests: [...] });
const results = await runTestSuite(suite);
expect(results.failed).toBe(0);
});For cases needing direct runner control:
import { createRunner, createRunnerFromBuffer } from '@gcoredev/fastedge-test';
// From file path (preferred — faster startup, less memory)
const runner = await createRunner('./build/app.wasm');
// From buffer (e.g. fetched from URL or built in-memory)
const buffer = await fs.readFile('./build/app.wasm');
const runner = await createRunnerFromBuffer(buffer);
// CDN: run full request/response flow
const result = await runner.callFullFlow(
'https://example.com', 'GET',
{}, '', // request headers, body
{}, '', // response headers, body
200, 'OK', // response status
{}, // properties
true, // enforce production property rules
);
// HTTP-WASM: execute a request
const response = await runner.execute({
path: '/api/hello',
method: 'GET',
headers: {},
});
// Always clean up when done
await runner.cleanup();The package ships JSON Schema files for fastedge-config.test.json and all API request/response bodies, enabling IDE autocomplete and validation.
Reference from fastedge-config.test.json for VS Code autocomplete:
{
"$schema": "./node_modules/@gcoredev/fastedge-test/schemas/fastedge-config.test.schema.json",
"envVars": {},
"secrets": {},
"properties": {}
}- name: Test FastEdge WASM
run: node test/my-suite.jsWhere test/my-suite.js:
import { defineTestSuite, runAndExit, runFlow } from '@gcoredev/fastedge-test/test';
import { assertFinalStatus } from '@gcoredev/fastedge-test/test';
await runAndExit(defineTestSuite({
wasmPath: './build/app.wasm',
tests: [
{
name: 'homepage returns 200',
run: async (runner) => {
assertFinalStatus(await runFlow(runner, { url: 'https://example.com/' }), 200);
},
},
],
}));- Local Debugger Server — running the visual debugger UI, npx usage, programmatic
startServer(), REST API quickstart - Test Framework Guide — full API reference, all assertion helpers, examples
- REST API Reference — HTTP endpoints for server-based testing (UI / WebSocket)
- WASM Loading Guide — path vs buffer tradeoffs and performance data
- Repository — source code, issues, contributing
MIT