Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Octopodo/kt-testing-suite-core/llms.txt

Use this file to discover all available pages before exploring further.

The built-in ConsoleReporter and JSONReporter cover the most common output scenarios, but there are plenty of reasons to go further. You might need to format results for a specific CI system’s log parser, post test outcomes to an external webhook, render results inside a custom Adobe panel UI, or suppress noise and show only failures. All of this is possible by implementing the TestReporter interface — a six-method contract that the TestRunner calls at every stage of a run.

When to Use a Custom Reporter

Different CI providers recognise different log conventions. A custom reporter can emit results in JUnit XML, TAP (Test Anything Protocol), or any other format your pipeline expects — simply by writing the appropriate strings with $.writeln inside each lifecycle method.

The TestReporter Interface

Implement TestReporter from kt-testing-suite-core and provide a concrete method for each of the six lifecycle hooks. All six must be present even if the body is a no-op, because the TestRunner calls them unconditionally.
import { TestReporter } from 'kt-testing-suite-core';
import { Suite, Test } from 'kt-testing-suite-core';
import { TestResult } from 'kt-testing-suite-core';

export interface TestReporter {
  onStart(): void;
  onSuiteStart(suite: Suite): void;
  onTestStart(test: Test): void;
  onTestEnd(test: Test, result: TestResult): void;
  onSuiteEnd(suite: Suite): void;
  onFinished(results: { passed: number; failed: number; total: number }): void;
}

Method reference

MethodParametersCalled when
onStart()Once, before the first suite runs
onSuiteStart(suite)suite: SuiteEntering a describe block
onTestStart(test)test: TestJust before an it callback executes
onTestEnd(test, result)test: Test, result: TestResultAfter an it callback completes (any outcome)
onSuiteEnd(suite)suite: SuiteAfter all tests and nested suites in a describe finish
onFinished(results){ passed, failed, total }Once, after every suite has run

TestResult in detail

The result argument passed to onTestEnd carries everything you need to classify and display a test outcome:
export interface TestResult {
  name: string;
  status: 'passed' | 'failed' | 'skipped';
  error?: {
    message: string;
    fileName?: string;  // source file path, if available from ExtendScript
    line?: number;      // line number, if available from ExtendScript
  };
  duration?: number;
}
  • status is always one of the three string literals — no numeric codes.
  • error is undefined for passing and skipped tests; it is populated only when status === 'failed'.
  • fileName and line come from the raw ExtendScript error object and may be absent in some host environments.
The Suite and Test types are defined in the core types module. A Suite carries description, tests, children (nested suites), and hook functions (beforeAll, afterAll, beforeEach, afterEach). A Test carries name and fn (the test callback).

A Complete Custom Reporter Example

The example below is a minimal but fully functional reporter. It prefixes each test result with a plain OK or FAIL label rather than emoji, and prints a one-line summary when the run is complete — a format that many log parsers find easier to grep.
import { TestReporter } from 'kt-testing-suite-core';
import { Suite, Test } from 'kt-testing-suite-core';
import { TestResult } from 'kt-testing-suite-core';

class MyCustomReporter implements TestReporter {
  onStart(): void {
    $.writeln('--- Starting Tests ---');
  }

  onSuiteStart(suite: Suite): void {
    $.writeln(`Entering: ${suite.description}`);
  }

  onTestStart(test: Test): void {
    // Called before test execution — useful for timing or logging test name early
  }

  onTestEnd(test: Test, result: TestResult): void {
    const icon = result.status === 'passed' ? 'OK' : 'FAIL';
    $.writeln(`[${icon}] ${test.name}`);
  }

  onSuiteEnd(suite: Suite): void {
    // Called after suite execution — useful for per-suite summaries
  }

  onFinished(results: { passed: number; failed: number; total: number }): void {
    $.writeln(`--- Done. Passed: ${results.passed}/${results.total} ---`);
  }
}

// Register and run
import { runTests } from 'kt-testing-suite-core';
runTests(undefined, new MyCustomReporter());
This produces output like:
--- Starting Tests ---
Entering: Math Operations
[OK] should add two numbers
[OK] should subtract two numbers
--- Done. Passed: 2/2 ---

Extending ConsoleReporter as a Base

If you want all the standard human-readable output plus some additional behaviour, extend ConsoleReporter and override only the methods you need. This pattern is useful for test utilities, mock reporters in meta-tests, and scenarios where you want to intercept results without losing the existing log output.
import { ConsoleReporter } from 'kt-testing-suite-core';
import { Test } from 'kt-testing-suite-core';
import { TestResult } from 'kt-testing-suite-core';

class AuditingReporter extends ConsoleReporter {
  private failures: TestResult[] = [];

  // Override onTestEnd to capture failures, then call super to keep console output
  onTestEnd(test: Test, result: TestResult): void {
    super.onTestEnd(test, result);   // preserve ✅ / ❌ console lines

    if (result.status === 'failed') {
      this.failures.push(result);
    }
  }

  // Override onFinished to append a failure digest after the standard summary
  onFinished(results: { passed: number; failed: number; total: number }): void {
    super.onFinished(results);       // preserve "Test Results: Passed/Failed" block

    if (this.failures.length > 0) {
      $.writeln('\n--- Failure Digest ---');
      for (let i = 0; i < this.failures.length; i++) {
        const f = this.failures[i];
        $.writeln(`  ${i + 1}. ${f.name}: ${f.error ? f.error.message : 'unknown error'}`);
      }
    }
  }
}
The MockReporter pattern — extending ConsoleReporter and recording events into arrays — is also handy when you want to write tests about your test runner itself. Store each onTestEnd call in a results array, then assert on its contents after runner.run() returns.

Registering a Custom Reporter

Pass your reporter instance as the second argument to runTests(), or provide it directly to the TestRunner constructor. Both paths lead to the same TestRunner internals.
import { runTests } from 'kt-testing-suite-core';

runTests(undefined, new MyCustomReporter());
If you omit the reporter argument entirely, TestRunner defaults to a new ConsoleReporter. Always pass your custom reporter explicitly — there is no global reporter registration mechanism.

A Note on $.writeln in ExtendScript

All output inside Adobe ExtendScript must use $.writeln() rather than console.log(). The $ object is the ExtendScript global host object, and $.writeln is the standard way to write a line to the host application’s JavaScript console (visible in the ExtendScript Toolkit or routed to an IDE extension’s output channel).
// ✅ Correct — works in all Adobe ExtendScript hosts
$.writeln('Suite started: ' + suite.description);

// ❌ Incorrect — console is not defined in ExtendScript
console.log('Suite started: ' + suite.description);
When building reporters intended for use in both ExtendScript and a Node.js test harness, you can guard the call: typeof $ !== 'undefined' ? $.writeln(msg) : console.log(msg). For pure ExtendScript targets, $.writeln is always safe.

Build docs developers (and LLMs) love