UNPKG

@itwin/certa

Version:

A mocha-based integration test runner

87 lines 4.92 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ // NB: This file is not a CommonJs module - it needs to run in the browser. Do not import or export modules here! /** * Helper for marshalling certain mocha objects between frontend and backend processes. * * Normally, playwright automatically uses JSON to serialize and deserialize arguments to backend functions exposed via `page.exposeFunction()`. * However, we face two problems when trying to pass `Mocha.Runnable` and `Mocha.Suite` instances to the backend: * - These objects have circular references * - JSON round-tripping won't preserve these objects' prototypes, and thus will be missing methods that reporters rely on. * * This class attempts to solve this by replacing all instances of these classes with "handles" that simply refer to their * index in a central "registry" array. Because we serialize and deserialize in the same order, we can re-create this "registry" * on the backend as we deserialize. */ class MochaSerializer { static _registry = []; static createHandle(raw, isPrimary) { let $$index = this._registry.indexOf(raw); // Browser side of mocha is webpacked so the constructor name has an added suffix for example `Test` is converted into `Test$4`. // This is not the case is node side of mocha, so an error is thrown because of this mismatch. The `.replace()` is there to strip that suffix let $$typeName; if (raw instanceof Mocha.Suite) $$typeName = "Suite"; else if (raw instanceof Mocha.Test) $$typeName = "Test"; else if (raw instanceof Mocha.Hook) $$typeName = "Hook"; else throw new Error("Unexpected instance of Mocha"); if ($$index < 0) { $$index = this._registry.push(raw) - 1; // This is a new instance, so we also need to include its properties isPrimary = true; } return (isPrimary) ? { $$index, $$typeName, ...raw } : { $$index, $$typeName }; } /** * (On the frontend) Serializes a value to JSON string, recursively replacing any MochaObjs with appropriate handles * TODO: There are ***surely*** more performant ways to do this... * * @param root The root object to serialize. If this is a MochaObj, it will always include all * properties (even if it should already exist in the backend's registry). */ static serialize(root) { const isMochaObj = (obj) => (obj instanceof Mocha.Runnable || obj instanceof Mocha.Suite); const replacer = (key, value) => { // Some pretty important properties of Errors are not enumerable, so we need to special handle them here: if (value instanceof Error) return { ...value, name: value.name, message: value.message, stack: value.stack }; if (key === "" || !isMochaObj(value)) return value; return JSON.parse(JSON.stringify(this.createHandle(value, false), replacer)); }; return JSON.stringify(isMochaObj(root) ? this.createHandle(root, true) : root, replacer); } /** * (On the backend) Deserializes a JSON string, replacing any MochaObj handles with their corresponding instances. * The first time we encounter a given instance's handle, we'll transform that handle object to serve as the actual instance on the backend. */ static deserialize(txt) { return JSON.parse(txt, (_key, value) => { // We only need to special-case our "handle" objects, and we'll assume anything with `$$index` fits the bill. if (typeof value !== "object" || value === null || typeof value.$$index !== "number") return value; // Try to lookup this handle's instance in our registry const existing = this._registry[value.$$index]; if (existing) { // Update property values (in case they've changed) for (const name of Object.getOwnPropertyNames(value)) { existing[name] = value[name]; } return existing; } // Set the prototype and add the handle to the registry - we can now treat this as (essentially) the real instance. Object.setPrototypeOf(value, require("mocha")[value.$$typeName].prototype); this._registry[value.$$index] = value; return value; }); } } if (typeof global !== "undefined") global.MochaSerializer = MochaSerializer; //# sourceMappingURL=MochaSerializer.js.map