@git.zone/tstest/tapbundle_protocol
📡 Enhanced TAP Protocol V2 implementation for structured test communication
Installation
# tapbundle_protocol is included as part of @git.zone/tstest
pnpm install --save-dev @git.zone/tstest
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who want to sign a contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
Overview
@git.zone/tstest/tapbundle_protocol implements Protocol V2, an enhanced version of the Test Anything Protocol (TAP) with support for structured metadata, real-time events, error diffs, and isomorphic operation. This protocol enables rich communication between test runners and test consumers while maintaining backward compatibility with standard TAP parsers.
Key Features
- 📋 TAP v13 Compliant - Fully compatible with standard TAP consumers
- 🎯 Enhanced Metadata - Timing, tags, errors, diffs, and custom data
- 🔄 Real-time Events - Live test execution updates
- 🔍 Structured Errors - JSON error blocks with stack traces and diffs
- 📸 Snapshot Support - Built-in snapshot testing protocol
- 🌐 Isomorphic - Works in Node.js, browsers, Deno, and Bun
- 🏷️ Protocol Markers - Structured data using Unicode delimiters
Protocol V2 Format
Protocol Markers
Protocol V2 uses special Unicode markers to embed structured data within TAP output:
⟦TSTEST:- Start marker⟧- End marker
These markers allow structured data to coexist with standard TAP without breaking compatibility.
Example Output
⟦TSTEST:PROTOCOL:2.0.0⟧
TAP version 13
1..3
ok 1 - should add numbers ⟦TSTEST:time:42⟧
not ok 2 - should validate input
⟦TSTEST:META:{"time":156,"file":"test.ts","line":42}⟧
⟦TSTEST:ERROR⟧
{
"error": {
"message": "Expected 5 to equal 6",
"diff": {...}
}
}
⟦TSTEST:/ERROR⟧
ok 3 - should handle edge cases # SKIP not implemented ⟦TSTEST:SKIP:not implemented⟧
API Reference
ProtocolEmitter
Generates Protocol V2 messages. Used by tapbundle to emit test results.
emitProtocolHeader()
Emit the protocol version header.
import { ProtocolEmitter } from '@git.zone/tstest/tapbundle_protocol';
const emitter = new ProtocolEmitter();
console.log(emitter.emitProtocolHeader());
// Output: ⟦TSTEST:PROTOCOL:2.0.0⟧
emitTapVersion(version?)
Emit TAP version line.
console.log(emitter.emitTapVersion(13));
// Output: TAP version 13
emitPlan(plan)
Emit test plan.
console.log(emitter.emitPlan({ start: 1, end: 5 }));
// Output: 1..5
console.log(emitter.emitPlan({ start: 1, end: 0, skipAll: 'Not ready' }));
// Output: 1..0 # Skipped: Not ready
emitTest(result)
Emit a test result with optional metadata.
const lines = emitter.emitTest({
ok: true,
testNumber: 1,
description: 'should work correctly',
metadata: {
time: 45,
tags: ['unit', 'fast']
}
});
lines.forEach(line => console.log(line));
// Output:
// ok 1 - should work correctly ⟦TSTEST:time:45⟧
// ⟦TSTEST:META:{"tags":["unit","fast"]}⟧
emitComment(comment)
Emit a comment line.
console.log(emitter.emitComment('Setup complete'));
// Output: # Setup complete
emitBailout(reason)
Emit a bailout (abort all tests).
console.log(emitter.emitBailout('Database connection failed'));
// Output: Bail out! Database connection failed
emitError(error)
Emit a structured error block.
const lines = emitter.emitError({
testNumber: 2,
error: {
message: 'Expected 5 to equal 6',
stack: 'Error: ...',
actual: 5,
expected: 6,
diff: '...'
}
});
lines.forEach(line => console.log(line));
// Output:
// ⟦TSTEST:ERROR⟧
// {
// "testNumber": 2,
// "error": { ... }
// }
// ⟦TSTEST:/ERROR⟧
emitEvent(event)
Emit a real-time test event.
console.log(emitter.emitEvent({
eventType: 'test:started',
timestamp: Date.now(),
data: {
testNumber: 1,
description: 'should work'
}
}));
// Output: ⟦TSTEST:EVENT:{"eventType":"test:started",...}⟧
emitSnapshot(snapshot)
Emit snapshot data.
const lines = emitter.emitSnapshot({
name: 'user-data',
content: { name: 'Alice', age: 30 },
format: 'json'
});
lines.forEach(line => console.log(line));
// Output:
// ⟦TSTEST:SNAPSHOT:user-data⟧
// {
// "name": "Alice",
// "age": 30
// }
// ⟦TSTEST:/SNAPSHOT⟧
ProtocolParser
Parses Protocol V2 messages. Used by tstest to consume test results.
parseLine(line)
Parse a single line and return protocol messages.
import { ProtocolParser } from '@git.zone/tstest/tapbundle_protocol';
const parser = new ProtocolParser();
const messages = parser.parseLine('ok 1 - test passed ⟦TSTEST:time:42⟧');
console.log(messages);
// Output:
// [{
// type: 'test',
// content: {
// ok: true,
// testNumber: 1,
// description: 'test passed',
// metadata: { time: 42 }
// }
// }]
Message Types
The parser returns different message types:
interface IProtocolMessage {
type: 'test' | 'plan' | 'comment' | 'version' | 'bailout' | 'protocol' | 'snapshot' | 'error' | 'event';
content: any;
}
Examples:
// Test result
{
type: 'test',
content: {
ok: true,
testNumber: 1,
description: 'test name',
metadata: { ... }
}
}
// Plan
{
type: 'plan',
content: {
start: 1,
end: 5
}
}
// Event
{
type: 'event',
content: {
eventType: 'test:started',
timestamp: 1234567890,
data: { ... }
}
}
// Error
{
type: 'error',
content: {
testNumber: 2,
error: {
message: '...',
stack: '...',
diff: '...'
}
}
}
getProtocolVersion()
Get the detected protocol version.
const version = parser.getProtocolVersion();
console.log(version); // "2.0.0" or null
TypeScript Types
ITestResult
interface ITestResult {
ok: boolean;
testNumber: number;
description: string;
directive?: {
type: 'skip' | 'todo';
reason?: string;
};
metadata?: ITestMetadata;
}
ITestMetadata
interface ITestMetadata {
// Timing
time?: number; // Test duration in milliseconds
startTime?: number; // Unix timestamp
endTime?: number; // Unix timestamp
// Status
skip?: string; // Skip reason
todo?: string; // Todo reason
retry?: number; // Current retry attempt
maxRetries?: number; // Max retries allowed
// Error details
error?: {
message: string;
stack?: string;
diff?: string;
actual?: any;
expected?: any;
code?: string;
};
// Test context
file?: string; // Source file path
line?: number; // Line number
column?: number; // Column number
// Custom data
tags?: string[]; // Test tags
custom?: Record<string, any>;
}
ITestEvent
interface ITestEvent {
eventType: EventType;
timestamp: number;
data: {
testNumber?: number;
description?: string;
suiteName?: string;
hookName?: string;
progress?: number; // 0-100
duration?: number;
error?: IEnhancedError;
[key: string]: any;
};
}
type EventType =
| 'test:queued'
| 'test:started'
| 'test:progress'
| 'test:completed'
| 'suite:started'
| 'suite:completed'
| 'hook:started'
| 'hook:completed'
| 'assertion:failed';
IEnhancedError
interface IEnhancedError {
message: string;
stack?: string;
diff?: IDiffResult;
actual?: any;
expected?: any;
code?: string;
type?: 'assertion' | 'timeout' | 'uncaught' | 'syntax' | 'runtime';
}
IDiffResult
interface IDiffResult {
type: 'string' | 'object' | 'array' | 'primitive';
changes: IDiffChange[];
context?: number; // Lines of context
}
interface IDiffChange {
type: 'add' | 'remove' | 'modify';
path?: string[]; // For object/array diffs
oldValue?: any;
newValue?: any;
line?: number; // For string diffs
content?: string;
}
Protocol Constants
import { PROTOCOL_MARKERS, PROTOCOL_VERSION } from '@git.zone/tstest/tapbundle_protocol';
console.log(PROTOCOL_VERSION); // "2.0.0"
console.log(PROTOCOL_MARKERS.START); // "⟦TSTEST:"
console.log(PROTOCOL_MARKERS.END); // "⟧"
Available Markers
const PROTOCOL_MARKERS = {
START: '⟦TSTEST:',
END: '⟧',
META_PREFIX: 'META:',
ERROR_PREFIX: 'ERROR',
ERROR_END: '/ERROR',
SNAPSHOT_PREFIX: 'SNAPSHOT:',
SNAPSHOT_END: '/SNAPSHOT',
PROTOCOL_PREFIX: 'PROTOCOL:',
SKIP_PREFIX: 'SKIP:',
TODO_PREFIX: 'TODO:',
EVENT_PREFIX: 'EVENT:',
};
Usage Patterns
Creating a Custom Test Runner
import { ProtocolEmitter } from '@git.zone/tstest/tapbundle_protocol';
const emitter = new ProtocolEmitter();
// Emit protocol header
console.log(emitter.emitProtocolHeader());
console.log(emitter.emitTapVersion(13));
// Emit plan
console.log(emitter.emitPlan({ start: 1, end: 2 }));
// Run test 1
emitter.emitEvent({
eventType: 'test:started',
timestamp: Date.now(),
data: { testNumber: 1 }
}).split('\n').forEach(line => console.log(line));
const result1 = emitter.emitTest({
ok: true,
testNumber: 1,
description: 'first test',
metadata: { time: 45 }
});
result1.forEach(line => console.log(line));
// Run test 2
const result2 = emitter.emitTest({
ok: false,
testNumber: 2,
description: 'second test',
metadata: {
time: 120,
error: {
message: 'Assertion failed',
actual: 5,
expected: 6
}
}
});
result2.forEach(line => console.log(line));
Parsing Test Output
import { ProtocolParser } from '@git.zone/tstest/tapbundle_protocol';
import * as readline from 'readline';
const parser = new ProtocolParser();
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.on('line', (line) => {
const messages = parser.parseLine(line);
messages.forEach(message => {
switch (message.type) {
case 'test':
console.log(`Test ${message.content.testNumber}: ${message.content.ok ? 'PASS' : 'FAIL'}`);
break;
case 'event':
console.log(`Event: ${message.content.eventType}`);
break;
case 'error':
console.error(`Error: ${message.content.error.message}`);
break;
}
});
});
Building Test Dashboards
Real-time events enable building live test dashboards:
const parser = new ProtocolParser();
parser.parseLine(line).forEach(message => {
if (message.type === 'event') {
const event = message.content;
switch (event.eventType) {
case 'test:started':
updateUI({ status: 'running', test: event.data.description });
break;
case 'test:completed':
updateUI({ status: 'done', duration: event.data.duration });
break;
case 'suite:started':
createSuiteCard(event.data.suiteName);
break;
}
}
});
Backward Compatibility
Protocol V2 is fully backward compatible with standard TAP parsers:
- Protocol markers use Unicode characters that TAP parsers ignore
- Standard TAP output (ok/not ok, plan, comments) works everywhere
- Enhanced features gracefully degrade in standard TAP consumers
Standard TAP View:
TAP version 13
1..3
ok 1 - should add numbers
not ok 2 - should validate input
ok 3 - should handle edge cases # SKIP not implemented
Protocol V2 View (same output):
⟦TSTEST:PROTOCOL:2.0.0⟧
TAP version 13
1..3
ok 1 - should add numbers ⟦TSTEST:time:42⟧
not ok 2 - should validate input
⟦TSTEST:META:{"time":156}⟧
ok 3 - should handle edge cases # SKIP not implemented ⟦TSTEST:SKIP:not implemented⟧
Isomorphic Design
This module works in all JavaScript environments:
- ✅ Node.js
- ✅ Browsers (via tapbundle)
- ✅ Deno
- ✅ Bun
- ✅ Web Workers
- ✅ Service Workers
No runtime-specific APIs are used, making it truly portable.
License and Legal Information
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the license file within this repository.
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
Company Information
Task Venture Capital GmbH Registered at District court Bremen HRB 35230 HB, Germany
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.