"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HotTesterMochaSelenium = void 0;
const mocha_1 = __importDefault(require("mocha"));
const mocha_2 = require("mocha");
const HotTesterMocha_1 = require("./HotTesterMocha");
const HotTestSeleniumDriver_1 = require("./HotTestSeleniumDriver");
/**
 * The tester that uses Mocha to executes all tests.
 */
class HotTesterMochaSelenium extends HotTesterMocha_1.HotTesterMocha {
    constructor(processor, name, baseUrl, testMaps = {}, onSetup = null, beforeAll = null, afterAll = null) {
        super(processor, name, baseUrl, testMaps, beforeAll, afterAll);
        /**
         * Cookies snapshotted on destroy() so the next setup() can re-add
         * them to a fresh WebDriver session. HotTester calls destroy()
         * after every destination, which would otherwise wipe auth cookies
         * (refreshToken, session) set during a previous destination's
         * login flow.
         */
        this.savedCookies = [];
        this.savedCookiesUrl = "";
        this.driver = new HotTestSeleniumDriver_1.HotTestSeleniumDriver(processor);
        this.mocha = null;
        this.timeout = 10000;
        this.suite = null;
        this.onSetup = onSetup;
        this.beforeAll = beforeAll;
        this.afterAll = afterAll;
        this.waitForTesterData = true;
    }
    /**
     * Setup the Mocha/Selenium frameworks.
     *
     * @param tester The
     * @param url If this is set, this url will be used instead of the url associated with this
     * tester.
     */
    setup(isWebRoute_1) {
        return __awaiter(this, arguments, void 0, function* (isWebRoute, url = "", destinationKey = "") {
            // Only execute web routes using Selenium.
            if (isWebRoute === false) {
                this.processor.logger.verbose(`HotTesterMochaSelenium: Not a web route. Skipping...`);
                return;
            }
            this.processor.logger.verbose(`HotTesterMochaSelenium: Setting up`);
            let testDriver = this.driver;
            yield testDriver.loadSeleniumDriver();
            let driver = testDriver.driver;
            // Restore cookies snapshotted by destroy() — without this, login
            // state (refreshToken, session) set in a previous destination is
            // lost when HotTester quits the WebDriver between destinations.
            if (this.savedCookies != null && this.savedCookies.length > 0 && url !== "") {
                try {
                    const targetOrigin = new URL(url).origin;
                    yield driver.get(targetOrigin + "/");
                    for (const c of this.savedCookies) {
                        try {
                            yield driver.manage().addCookie(c);
                        }
                        catch (e) { /* skip cookies that addCookie rejects (e.g. domain mismatch) */ }
                    }
                    this.processor.logger.verbose(`HotTesterMochaSelenium: restored ${this.savedCookies.length} cookies to ${targetOrigin}`);
                }
                catch (e) { /* best-effort */ }
                this.savedCookies = [];
                this.savedCookiesUrl = "";
            }
            let loadDriverUrl = true;
            if (this.onSetup != null) {
                yield new Promise((resolve, reject) => {
                    this.onSetup(driver, url).then((result) => {
                        loadDriverUrl = result;
                        resolve();
                    })
                        .catch((error) => {
                        this.processor.logger.error(`HotTesterMochaSelenium Error: ${error.message}`);
                        reject(error);
                    });
                });
                if (loadDriverUrl == null)
                    loadDriverUrl = true;
            }
            this.processor.logger.verbose(`HotTesterMochaSelenium: Finished setting up`);
            if (loadDriverUrl === true) {
                this.processor.logger.verbose(`HotTesterMochaSelenium: Retreiving url ${url} and waiting for data...`);
                try {
                    let testUrl = new URL(url);
                }
                catch (ex) {
                    throw new Error(`HotTesterMochaSelenium: Cannot start tests. Invalid URL: ${url}`);
                }
                this.finishedLoading = false;
                yield driver.get(url);
                try {
                    yield this.waitForData();
                    this.processor.logger.verbose(`HotTesterMochaSelenium: Received data...`);
                }
                catch (ex) {
                    // Drain the browser console + page source so the CI log shows
                    // *what the browser actually saw* rather than just "we never
                    // heard from it." A page-side JS error before
                    // Hot.CurrentPage.createTestPath is the most common reason
                    // the tester never registers test paths and waitForData
                    // hits its cap.
                    try {
                        const logs = yield driver.manage().logs().get("browser");
                        const lines = logs.map((e) => { var _a; return `[${((_a = e.level) === null || _a === void 0 ? void 0 : _a.name) || e.level || "?"}] ${e.message}`; });
                        this.processor.logger.error(`HotTesterMochaSelenium: browser console (${lines.length} entries):\n` +
                            (lines.length === 0 ? "  (empty)" : lines.map((l) => `  ${l}`).join("\n")));
                    }
                    catch (logEx) {
                        this.processor.logger.error(`HotTesterMochaSelenium: could not pull browser logs — ${logEx.message}`);
                    }
                    try {
                        const html = yield driver.getPageSource();
                        const head = html.length > 4000 ? html.slice(0, 4000) + "\n[…truncated]" : html;
                        this.processor.logger.error(`HotTesterMochaSelenium: rendered page source (first 4kB):\n${head}`);
                    }
                    catch (srcEx) { /* ignore */ }
                    throw ex;
                }
            }
            else
                this.processor.logger.verbose(`HotTesterMochaSelenium: Not retreiving url.`);
        });
    }
    /**
     * Executed when destroying this tester.
     */
    destroy() {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.driver != null) {
                // Snapshot cookies before quitting the WebDriver so loadSeleniumDriver
                // can restore them on the next destination's fresh session.
                //
                // WebDriver's getCookies returns cookies visible to the *current* URL.
                // Cookies with restrictive paths (e.g. an auth refreshToken set with
                // path=/v1/users) won't show up if the current URL is "/" or
                // "/login". Try common deeper paths so path-restricted auth cookies
                // surface, then merge by name.
                try {
                    const wd = this.driver.driver;
                    if (wd != null) {
                        this.savedCookiesUrl = yield wd.getCurrentUrl();
                        const seen = {};
                        const collect = () => __awaiter(this, void 0, void 0, function* () {
                            try {
                                const cs = yield wd.manage().getCookies();
                                for (const c of cs)
                                    seen[c.name] = c;
                            }
                            catch (e) { /* skip */ }
                        });
                        yield collect();
                        try {
                            const origin = new URL(this.savedCookiesUrl).origin;
                            for (const probe of ["/v1/users", "/v1", "/api"]) {
                                try {
                                    yield wd.get(origin + probe);
                                }
                                catch (e) {
                                    continue;
                                }
                                yield collect();
                            }
                        }
                        catch (e) { /* origin parse failed */ }
                        this.savedCookies = Object.values(seen);
                        this.processor.logger.verbose(`HotTesterMochaSelenium: snapshotted ${this.savedCookies.length} cookies (from ${this.savedCookiesUrl} + path probes)`);
                    }
                }
                catch (e) { /* best-effort */ }
                yield this.driver.destroy();
            }
        });
    }
    /**
     * Executed when tests are started.
     */
    onTestStart(destination_1, url_1) {
        return __awaiter(this, arguments, void 0, function* (destination, url, destinationKey = "") {
            let destinationName = "";
            if (destinationKey !== "")
                destinationName = ` - ${destinationKey}`;
            this.processor.logger.verbose(`HotTesterMochaSelenium: Setting up Mocha and creating test suite...`);
            this.mocha = new mocha_1.default();
            this.suite = mocha_1.default.Suite.create(this.mocha.suite, `${url} ${destinationName} Tests`);
            this.suite.timeout(this.timeout);
            if (this.beforeAll != null)
                this.suite.beforeAll(this.beforeAll);
            return (true);
        });
    }
    onTestPagePathStart(destination_1, stop_1) {
        return __awaiter(this, arguments, void 0, function* (destination, stop, continueWhenTestIsComplete = false) {
            let testPathName = stop.path;
            this.processor.logger.verbose(`HotTesterMochaSelenium: Adding test path ${testPathName}`);
            /*if (continueWhenTestIsComplete === true)
            {
                await new Promise<void> ((resolve, reject) =>
                    {
                        this.suite.addTest (new Test (testPathName, async () =>
                            {
                                this.processor.logger.verbose (() => `HotTesterMochaSelenium: Executing ${testPathName} destination ${JSON.stringify (destination)} with stop ${stop}`);
    
                                // The true is a dumb hack to prevent any recursion.
                                await this.executeTestPagePath (destination, stop, true);
                                resolve ();
                            }));
                    });
            }
            else
            {*/
            this.suite.addTest(new mocha_2.Test(testPathName, () => __awaiter(this, void 0, void 0, function* () {
                this.processor.logger.verbose(() => `HotTesterMochaSelenium: Executing ${testPathName} destination ${JSON.stringify(destination)} with stop ${stop}`);
                // The true is a dumb hack to prevent any recursion.
                yield this.executeTestPagePath(destination, stop, true);
            })));
            //}
            return (false);
        });
    }
    onCommand(destination, stop, cmd, args, cmdFunc) {
        return __awaiter(this, void 0, void 0, function* () {
            this.suite.addTest(new mocha_2.Test(cmd, () => __awaiter(this, void 0, void 0, function* () {
                this.processor.logger.verbose(() => `HotTesterMochaSelenium: Executing command ${cmd} with arguments ${JSON.stringify(args)}`);
                yield cmdFunc(args);
            })));
        });
    }
    onTestEnd(destination) {
        return __awaiter(this, void 0, void 0, function* () {
            return (new Promise((resolve, reject) => {
                if (this.afterAll != null)
                    this.suite.afterAll(this.afterAll);
                this.processor.logger.verbose(`HotTesterMochaSelenium: Starting Mocha/Selenium and running tests...`);
                // Same listener-leak fix as HotTesterMocha.onTestEnd:
                // mocha.run() registers uncaughtException + unhandledRejection
                // listeners on `process`; without runner.dispose() each test
                // destination leaks two of them, the EventEmitter limit is
                // hit after a handful, and tests stall on the 10s mocha
                // timeout until the whole job times out.
                const runner = this.mocha.run((failures) => {
                    this.numFailures += failures;
                    this.processor.logger.verbose(`HotTesterMochaSelenium: Tests complete!`);
                    try {
                        runner.dispose();
                    }
                    catch (e) { /* mocha < 8 lacks dispose */ }
                    resolve();
                });
            }));
        });
    }
}
exports.HotTesterMochaSelenium = HotTesterMochaSelenium;
//# sourceMappingURL=HotTesterMochaSelenium.js.map