Skip to content

Custom Reporters

Custom reporters let you push results into Slack, internal dashboards, CSV files, metrics systems, or any other destination.

Import reporter types from spana-test:

import type { Reporter, FlowResult, RunSummary } from "spana-test";
const reporter: Reporter = {
onFlowStart(name, platform, workerName) {
console.log(`START ${platform}:${workerName ?? "default"} ${name}`);
},
onFlowFail(result: FlowResult) {
console.log(`FAIL ${result.name}: ${result.error?.message}`);
},
onRunComplete(summary: RunSummary) {
console.log(`${summary.passed}/${summary.total} passed`);
},
};
export default reporter;

All hooks except onRunComplete are optional.

import { defineConfig } from "spana-test";
export default defineConfig({
reporters: ["console", "./reporters/slack.ts"],
});

Reporter module paths are resolved relative to spana.config.ts.

If your reporter needs options, export a factory function:

import type { Reporter } from "spana-test";
export default function createCsvReporter(options: { outputDir: string }): Reporter {
return {
onRunComplete(summary) {
console.log(`Write results into ${options.outputDir}`);
},
};
}

Spana calls factory reporters with { outputDir }.

interface Reporter {
onFlowStart?(name: string, platform: Platform, workerName?: string): void;
onFlowPass?(result: FlowResult): void;
onFlowFail?(result: FlowResult): void;
onRunComplete(summary: RunSummary): void;
flowCount?: number;
platformFlowCounts?: Partial<Record<Platform, number>>;
}

flowCount and platformFlowCounts are populated by Spana so console-style reporters can render progress.

FieldDescription
nameFlow name
platformweb, android, or ios
statuspassed, failed, or skipped
flakytrue when the flow failed first and later passed on retry
attemptsNumber of attempts made
durationMsTotal runtime in milliseconds
errorStructured FlowError for failed flows
attachmentsCaptured artifacts such as screenshots, HAR, or logs
stepsLow-level driver steps recorded for the flow
scenarioStepsGherkin step results for scenario-style flows
workerNameWorker or device name when parallel mode is enabled
FieldDescription
totalTotal flow executions across all platforms
passed / failed / skippedFinal result counts
flakyNumber of flaky flows
durationMsTotal run duration
resultsAll FlowResult entries
platformsPlatforms included in the run
bailedOuttrue when --bail stopped the run early
bailLimitBail threshold that triggered the stop
workerStatsPer-worker timing and flow counts in parallel mode

StepResult represents low-level driver actions such as taps, assertions, typing, or network setup.

ScenarioStepResult represents higher-level Gherkin steps such as Given, When, Then, And, Before, and After. Each scenario step can include nested steps for the underlying driver actions.

interface Attachment {
name: string;
contentType: string;
path: string;
}

Attachments point at files on disk, for example screenshots, hierarchy dumps, console logs, JavaScript errors, or HAR captures.

interface FlowError {
message: string;
stack?: string;
category: FailureCategory;
suggestion?: string;
errorCode?: string;
}

FailureCategory values include:

  • element-not-found
  • element-not-visible
  • element-off-screen
  • element-not-interactive
  • text-mismatch
  • timeout
  • device-disconnected
  • app-crashed
  • app-not-installed
  • driver-error
  • config-error
  • unknown
import type { Reporter } from "spana-test";
const reporter: Reporter = {
async onFlowFail(result) {
await fetch("https://hooks.example.com/test-failures", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
name: result.name,
platform: result.platform,
category: result.error?.category,
message: result.error?.message,
worker: result.workerName,
attachments: result.attachments?.map((attachment) => attachment.name),
}),
});
},
onRunComplete(summary) {
console.log(`${summary.failed} failed flow(s)`);
},
};
export default reporter;