Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/browser-pool/basic-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,17 @@ export class BasicPool implements Pool {

this.log(`stop browser ${browser.fullId}`);

let error;

try {
await this._emit(MasterEvents.SESSION_END, browser);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
error = err;
console.warn((err && err.stack) || err);
}

await browser.quit();
await browser.quit(error);
}

private _emit(event: string, browser: Browser): Promise<unknown[]> {
Expand Down
5 changes: 5 additions & 0 deletions src/browser/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class Browser {
protected _customCommands: Set<CustomCommand>;
protected _wdPool?: WebdriverPool;
protected _wdProcess: WdProcess | null;
protected _exitError?: Error;
/** This Promise is awaited after test is finished. Can be used for cleanup.
Right now is used to wait for time travel snapshots to finish collecting */
protected _snapshotsPromiseRef: history.PromiseRef;
Expand Down Expand Up @@ -214,4 +215,8 @@ export class Browser {
get emitter(): AsyncEmitter {
return this._emitter;
}

get exitError(): Error | undefined {
return this._exitError;
}
}
6 changes: 4 additions & 2 deletions src/browser/new-browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class NewBrowser extends Browser {
constructor(config: Config, opts: BrowserOpts) {
super(config, opts);

signalHandler.on("exit", () => this.quit());
signalHandler.on("exit", (err?: Error) => this.quit(err));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pass error from exit event

}

async init(): Promise<NewBrowser> {
Expand All @@ -81,7 +81,9 @@ export class NewBrowser extends Browser {
return Promise.resolve();
}

async quit(): Promise<void> {
async quit(err?: Error): Promise<void> {
this._exitError = err;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

save in browser for using in test fail


try {
this.setHttpTimeout(this._config.sessionQuitTimeout);
await this._session!.deleteSession();
Expand Down
2 changes: 2 additions & 0 deletions src/runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ export class MainRunner extends RunnableEmitter {
this.workersRegistry.init();
this.workers = this.registerWorkers(require.resolve("../worker"), ["runTest", "cancel"] as const) as Workers;
this.browserPool = pool.create(this.config, this);

eventsUtils.passthroughEvent(this, this.workersRegistry, MasterEvents.EXIT);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass event to childs

}

_isRunning(): boolean {
Expand Down
4 changes: 2 additions & 2 deletions src/runner/test-runner/regular-test-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ module.exports = class RegularTestRunner extends RunnableEmitter {

this._emit(MasterEvents.TEST_PASS);
} catch (error) {
this._test.err = error;
this._test.err = this._browser?.exitError || error;

this._applyTestResults(error);
this._applyTestResults(this._test.err);

this._emit(MasterEvents.TEST_FAIL);
}
Expand Down
30 changes: 0 additions & 30 deletions src/signal-handler.js

This file was deleted.

48 changes: 48 additions & 0 deletions src/signal-handler.ts
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactoring this to typescript

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { AsyncEmitter } from "./events";
import { log } from "./utils/logger";
import { MasterEvents } from "./events";

const signalHandler = new AsyncEmitter();

signalHandler.setMaxListeners(0);

let callCount = 0;

let lastCallTime = 0;

const throttleTime = 10;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throttle for SIGINT, by some reason if kill using Control + C event fired twice with delay 7-8ms, probably it is duplicates from webdriver.io


function notifyAndExit(signalNo: number): (signal: NodeJS.Signals) => void {
const exitCode = 128 + signalNo;

return function (signal: NodeJS.Signals) {
const time = Date.now();

if (time - lastCallTime < throttleTime) {
lastCallTime = time;

return;
}

lastCallTime = time;

if (callCount++ > 0) {
log("Force quit.");
process.exit(exitCode);
}

const err = new Error(`The process was terminated by a signal: ${signal}`);

signalHandler.emitAndWait(MasterEvents.EXIT, err).then(() => {
signalHandler.emitAndWait(MasterEvents.RUNNER_END, err).then(() => {
process.exit(exitCode);
});
});
};
}

process.on("SIGHUP", notifyAndExit(1));
process.on("SIGINT", notifyAndExit(2));
process.on("SIGTERM", notifyAndExit(15));

export default signalHandler;
2 changes: 2 additions & 0 deletions src/testplane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ export class Testplane extends BaseTestplane {
eventsUtils.passthroughEvent(this.runner, this, _.values(MasterSyncEvents));
eventsUtils.passthroughEventAsync(this.runner, this, _.values(MasterAsyncEvents));
eventsUtils.passthroughEventAsync(signalHandler, this, MasterEvents.EXIT);
eventsUtils.passthroughEventAsync(signalHandler, this, MasterEvents.RUNNER_END);
eventsUtils.passthroughEventAsync(signalHandler, this.runner, MasterEvents.EXIT);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pass exit event to runner


await this._startServersIfNeeded();
await this._emitInitEventOnce();
Expand Down
2 changes: 2 additions & 0 deletions src/utils/workers-registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ module.exports = class WorkersRegistry extends EventEmitter {
logger.error(`testplane:worker:${child.pid} terminated unexpectedly with ${errMsg}`);
});

this.once(MasterEvents.EXIT, () => child.kill());
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kill child process if get EXIT event from master (through runner)


child.on("message", (data = {}) => {
switch (data.event) {
case WORKER_INIT:
Expand Down
74 changes: 42 additions & 32 deletions test/src/browser/new-browser.js → test/src/browser/new-browser.ts
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor to typescript

Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
"use strict";

const crypto = require("crypto");
const proxyquire = require("proxyquire");
const signalHandler = require("src/signal-handler");
const history = require("src/browser/history");
const { WEBDRIVER_PROTOCOL, DEVTOOLS_PROTOCOL } = require("src/constants/config");
const { X_REQUEST_ID_DELIMITER } = require("src/constants/browser");
const RuntimeConfig = require("src/config/runtime-config");
const { mkNewBrowser_, mkSessionStub_, mkWdPool_ } = require("./utils");
import sinon, { SinonStub } from "sinon";
import crypto from "crypto";
import proxyquire from "proxyquire";
import signalHandler from "src/signal-handler";
import { runGroup } from "src/browser/history";
import { WEBDRIVER_PROTOCOL, DEVTOOLS_PROTOCOL } from "src/constants/config";
import { X_REQUEST_ID_DELIMITER } from "src/constants/browser";
import RuntimeConfig from "src/config/runtime-config";
import { mkNewBrowser_, mkSessionStub_, mkWdPool_ } from "./utils";
import { Config } from "src/config";
import { RequestOptions } from "node:https";
import { DesiredCapabilities, SelenoidOptions } from "@testplane/wdio-types/build/Capabilities";

describe("NewBrowser", () => {
const sandbox = sinon.createSandbox();
let session;
let NewBrowser, webdriverioRemoteStub, runGroupStub, initCommandHistoryStub, installBrowserStub, warnStub;

const mkBrowser_ = (configOpts, opts) => {
let session: any;
let NewBrowser: any;
let webdriverioRemoteStub: SinonStub;
let runGroupStub: SinonStub;
let initCommandHistoryStub: SinonStub;
let installBrowserStub: SinonStub;
let warnStub: SinonStub;

const mkBrowser_ = (configOpts?: Partial<Config>, opts?: any): any => {
return mkNewBrowser_(configOpts, opts, NewBrowser);
};

Expand All @@ -23,7 +30,7 @@ describe("NewBrowser", () => {
installBrowserStub = sandbox.stub().resolves("/browser/path");
warnStub = sandbox.stub();
webdriverioRemoteStub = sandbox.stub().resolves(session);
runGroupStub = sandbox.stub().callsFake(history.runGroup);
runGroupStub = sandbox.stub().callsFake(runGroup);
initCommandHistoryStub = sandbox.stub();

NewBrowser = proxyquire("src/browser/new-browser", {
Expand Down Expand Up @@ -74,7 +81,7 @@ describe("NewBrowser", () => {
});

it("should use devtools protocol if testplane runs in devtools mode", async () => {
RuntimeConfig.getInstance.returns({ devtools: true });
(RuntimeConfig.getInstance as SinonStub).returns({ devtools: true });

await mkBrowser_().init();

Expand Down Expand Up @@ -281,7 +288,7 @@ describe("NewBrowser", () => {

describe("transformRequest option", () => {
beforeEach(() => {
sandbox.stub(crypto, "randomUUID").returns("00000");
sandbox.stub(crypto, "randomUUID").returns("0-0-0-0-0");
});

it("should call user handler from config", async () => {
Expand All @@ -297,9 +304,9 @@ describe("NewBrowser", () => {
});

it('should not add "X-Request-ID" header if it is already add by user', async () => {
const request = { headers: {} };
const transformRequestStub = req => {
req.headers["X-Request-ID"] = "100500";
const request: RequestOptions = { headers: {} };
const transformRequestStub = (req: RequestOptions): RequestOptions => {
(req.headers as Record<string, string>)["X-Request-ID"] = "100500";
return req;
};

Expand All @@ -308,20 +315,23 @@ describe("NewBrowser", () => {
const { transformRequest } = webdriverioRemoteStub.lastCall.args[0];
transformRequest(request);

assert.equal(request.headers["X-Request-ID"], "100500");
assert.equal((request.headers as Record<string, string>)["X-Request-ID"], "100500");
});

it('should add "X-Request-ID" header', async () => {
crypto.randomUUID.returns("67890");
(crypto.randomUUID as SinonStub).returns("67890");
const state = { testXReqId: "12345" };
const request = { headers: {} };
const request: RequestOptions = { headers: {} };

await mkBrowser_({}, { state }).init();

const { transformRequest } = webdriverioRemoteStub.lastCall.args[0];
transformRequest(request);

assert.equal(request.headers["X-Request-ID"], `12345${X_REQUEST_ID_DELIMITER}67890`);
assert.equal(
(request.headers as Record<string, string>)["X-Request-ID"],
`12345${X_REQUEST_ID_DELIMITER}67890`,
);
});
});

Expand All @@ -340,7 +350,7 @@ describe("NewBrowser", () => {
});

describe("set page load timeout if it is specified in a config", () => {
let browser;
let browser: any;

beforeEach(() => {
browser = mkBrowser_({ pageLoadTimeout: 100500 });
Expand Down Expand Up @@ -380,7 +390,7 @@ describe("NewBrowser", () => {
describe("should use local grid url", () => {
it("if gridUrl is 'local'", async () => {
installBrowserStub.withArgs("chrome", "115.0").resolves("/browser/path/chrome/115.0");
RuntimeConfig.getInstance.returns({ local: false });
(RuntimeConfig.getInstance as SinonStub).returns({ local: false });
const wdPool = mkWdPool_({ gridUrl: "http://localhost:12345/" });
const browser = mkBrowser_(
{
Expand Down Expand Up @@ -413,7 +423,7 @@ describe("NewBrowser", () => {

it("if local cli arg is set", async () => {
installBrowserStub.withArgs("chrome", "115.0").resolves("/browser/path/chrome/115.0");
RuntimeConfig.getInstance.returns({ local: true });
(RuntimeConfig.getInstance as SinonStub).returns({ local: true });
const wdPool = mkWdPool_({ gridUrl: "http://localhost:12345/" });
const browser = mkBrowser_(
{
Expand Down Expand Up @@ -446,7 +456,7 @@ describe("NewBrowser", () => {

it("should remove unknown capabilities", async () => {
installBrowserStub.withArgs("chrome", "115.0").resolves("/browser/path/chrome/115.0");
RuntimeConfig.getInstance.returns({ local: true });
(RuntimeConfig.getInstance as SinonStub).returns({ local: true });
const wdPool = mkWdPool_({ gridUrl: "http://localhost:23456/" });
const browser = mkBrowser_(
{
Expand All @@ -455,10 +465,10 @@ describe("NewBrowser", () => {
desiredCapabilities: {
browserName: "chrome",
browserVersion: "115.0",
"selenoid:options": { baz: "qux" },
"selenoid:options": { baz: "qux" } as SelenoidOptions,
"moz:firefoxOptions": {},
perfLoggingPrefs: { foo: "bar" },
},
} as DesiredCapabilities,
},
{ wdPool },
);
Expand Down Expand Up @@ -516,7 +526,7 @@ describe("NewBrowser", () => {
});

it("should free webdriver session", async () => {
RuntimeConfig.getInstance.returns({ local: false });
(RuntimeConfig.getInstance as SinonStub).returns({ local: false });
const wdProcess = { gridUrl: "http://localhost:12345", free: sandbox.stub(), kill: sandbox.stub() };
const wdPool = { getWebdriver: sandbox.stub().resolves(wdProcess) };
const browser = mkBrowser_(
Expand All @@ -540,7 +550,7 @@ describe("NewBrowser", () => {
});

it("should kill webdriver session if cant quit normally", async () => {
RuntimeConfig.getInstance.returns({ local: false });
(RuntimeConfig.getInstance as SinonStub).returns({ local: false });
session.deleteSession.rejects(new Error("failed end"));
const wdProcess = { gridUrl: "http://localhost:12345", free: sandbox.stub(), kill: sandbox.stub() };
const wdPool = { getWebdriver: sandbox.stub().resolves(wdProcess) };
Expand Down
Loading
Loading