@serenity-js/core
Version:
The core Serenity/JS framework, providing the Screenplay Pattern interfaces, as well as the test reporting and integration infrastructure
142 lines • 4.86 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RuntimeError = void 0;
const tiny_types_1 = require("tiny-types");
/**
* Base class for custom errors that may occur during execution of a test scenario.
*
* ## Defining a custom error
*
* ```ts
* import { RuntimeError } from '@serenity-js/core'
*
* export class CustomError extends RuntimeError {
* constructor(message: string, cause?: Error) {
* super(CustomError, message, cause);
* }
* }
* ```
*
* ## Wrapping a sync error
*
* ```ts
* try {
* operationThatMightThrowAnError();
* } catch(error) {
* // catch and re-throw
* throw new CustomError('operationThatMightThrowAnError has failed', error);
* }
* ```
*
* ## Wrapping an async error
*
* ```ts
* operationThatMightRejectAPromise()
* .catch(error => {
* // catch and re-throw
* throw new CustomError('operationThatMightThrowAnError has failed', error)
* })
* ```
*
* ## Registering a custom error with [`ErrorSerialiser`](https://serenity-js.org/api/core/class/ErrorSerialiser/)
*
* ```ts
* import { RuntimeError } from '@serenity-js/core'
* import { ErrorSerialiser } from '@serenity-js/core/io'
*
* export class CustomError extends RuntimeError {
*
* static fromJSON(serialised: JSONObject): CustomError {
* const error = new CustomError(
* serialised.message as string,
* ErrorSerialiser.deserialise(serialised.cause as string | undefined),
* );
*
* error.stack = serialised.stack as string;
*
* return error;
* }
*
* constructor(message: string, cause?: Error) {
* super(CustomError, message, cause);
* }
* }
*
* ErrorSerialiser.registerErrorTypes(CustomError)
* ```
*
* @group Errors
*/
class RuntimeError extends Error {
cause;
/**
* Symbol used to brand RuntimeError instances for cross-module instanceof checks.
* This addresses the dual-package hazard where the same class loaded from
* both ESM and CJS creates distinct constructor functions.
*/
static TYPE_BRAND = Symbol.for('@serenity-js/core/RuntimeError');
/**
* Custom instanceof check that works across module boundaries.
* This addresses the dual-package hazard where the same class loaded from
* both ESM and CJS creates distinct constructor functions.
*
* The check walks up the prototype chain of the instance and compares
* constructor names, which remain consistent across module boundaries.
*/
static [Symbol.hasInstance](instance) {
if (instance === null || typeof instance !== 'object') {
return false;
}
// First, verify this is a RuntimeError instance using the brand
if (instance[RuntimeError.TYPE_BRAND] !== true) {
return false;
}
// When checking against the base RuntimeError class, any branded instance qualifies
if (this.name === 'RuntimeError') {
return true;
}
// For subclass checks, walk the prototype chain and compare by name
// This works across ESM/CJS boundaries where constructor references differ
let proto = Object.getPrototypeOf(instance);
while (proto !== null) {
if (proto.constructor?.name === this.name) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
/**
* @param type - Constructor function used to instantiate a subclass of a RuntimeError
* @param message - Human-readable description of the error
* @param [cause] - The root cause of this [`RuntimeError`](https://serenity-js.org/api/core/class/RuntimeError/), if any
*/
constructor(type, message, cause) {
const errorMessage = message || '';
const isMultiline = errorMessage.includes('\n');
const causeAlreadyIncluded = cause?.message && errorMessage.includes(cause.message);
super(isMultiline || causeAlreadyIncluded
? errorMessage
: errorMessage + (cause && cause?.message ? `; ${cause.message}` : ''));
this.cause = cause;
Object.setPrototypeOf(this, type.prototype);
this.name = this.constructor.name;
// Brand the instance for cross-module instanceof checks
this[RuntimeError.TYPE_BRAND] = true;
Error.captureStackTrace(this, type);
if (cause) {
this.stack = `${this.stack}\nCaused by: ${cause.stack}`;
}
}
/**
* Human-readable description of the error
*/
toString() {
return `${this.constructor.name}: ${this.message}`;
}
toJSON() {
return tiny_types_1.TinyType.prototype.toJSON.apply(this);
}
}
exports.RuntimeError = RuntimeError;
//# sourceMappingURL=RuntimeError.js.map