Skip to content
Open
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
19 changes: 14 additions & 5 deletions src/base-testplane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,31 @@ import { ConfigInput } from "./config/types";

export abstract class BaseTestplane extends AsyncEmitter {
protected _interceptors: Interceptor[] = [];
protected _config: Config;
protected _config!: Config;
private _pendingConfig?: string | ConfigInput;

static create<T extends BaseTestplane>(
static async create<T extends BaseTestplane>(
this: new (config?: string | ConfigInput) => T,
config?: string | ConfigInput,
): T {
return new this(config);
): Promise<T> {
const instance = new this(config);

await instance._setup();

return instance;
}

protected constructor(config?: string | ConfigInput) {
super();

this._interceptors = [];
this._pendingConfig = config;
}

protected async _setup(): Promise<void> {
registerTransformHook(this.isWorker());
this._config = Config.create(config);
this._config = await Config.create(this._pendingConfig);
this._pendingConfig = undefined;
updateTransformHook(this._config);

this._setLogLevel();
Expand Down
2 changes: 1 addition & 1 deletion src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const run = async (opts: TestplaneRunOpts = {}): Promise<void> => {
}

const configPath = preparseOption(program, "config") as string;
testplane = Testplane.create(configPath);
testplane = await Testplane.create(configPath);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Testplane.create is now async, because config is parsed inside of it


withCommonCliOptions({ cmd: program, actionName: "run" })
.on("--help", () => console.log(configOverriding(opts)))
Expand Down
104 changes: 71 additions & 33 deletions src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,103 @@
import * as path from "path";
import * as _ from "lodash";
import path from "path";
import _ from "lodash";
import defaults from "./defaults";
import { BrowserConfig } from "./browser-config";
import parseOptions from "./options";
import * as logger from "../utils/logger";
import { ConfigInput, ConfigParsed } from "./types";
import { ConfigInput, ConfigInputData, ConfigParsed } from "./types";
import { addUserAgentToArgs } from "./utils";

export { TimeTravelMode } from "./types";

export class Config {
configPath!: string;
configPath?: string;

static create(config?: string | ConfigInput): Config {
return new Config(config);
static async create(config?: string | ConfigInput): Promise<Config> {
try {
const { configPath, options } = await Config._resolve(config);

await Config._prepareEnvironment(options);

return new Config(options, configPath);
} catch (e: unknown) {
const error = new Error(`Got an error while trying to read config: ${(e as Error).message}`);
error.stack = (e as Error).stack;
error.cause = (e as Error).cause;

throw error;
}
}

static read(configPath: string): unknown {
static async read(configPath: string): Promise<ConfigInputData> {
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const configModule = require(path.resolve(process.cwd(), configPath));
const exported = (configModule.__esModule ? configModule.default : configModule) as ConfigInput;

return configModule.__esModule ? configModule.default : configModule;
return await Config._resolveExportedConfig(exported);
} catch (e) {
logger.error(`Unable to read config from path ${configPath}`);
throw e;
}
}

constructor(config?: string | ConfigInput) {
let options: ConfigInput;
private static async _resolve(
config?: string | ConfigInput,
): Promise<{ configPath?: string; options: ConfigInputData }> {
if (typeof config === "function") {
return { options: await Config._resolveExportedConfig(config) };
}

if (_.isObjectLike(config)) {
options = config as ConfigInput;
} else if (typeof config === "string") {
this.configPath = config;
options = Config.read(config) as ConfigInput;
} else {
for (const configPath of defaults.configPaths) {
try {
const resolvedConfigPath = path.resolve(configPath);
require(resolvedConfigPath);
this.configPath = resolvedConfigPath;

break;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
if (err.code !== "MODULE_NOT_FOUND") {
throw err;
}
return { options: config as ConfigInputData };
}

if (typeof config === "string") {
return { configPath: config, options: await Config.read(config) };
}

const located = Config._locateConfigPath();

if (!located) {
throw new Error(`Unable to read config from paths: ${defaults.configPaths.join(", ")}`);
}

return { configPath: located, options: await Config.read(located) };
}

private static _locateConfigPath(): string | null {
for (const configPath of defaults.configPaths) {
try {
const resolvedConfigPath = path.resolve(configPath);
// eslint-disable-next-line @typescript-eslint/no-var-requires
require(resolvedConfigPath);

return resolvedConfigPath;
} catch (err: unknown) {
if ((err as { code?: string }).code !== "MODULE_NOT_FOUND") {
throw err;
}
}
}

if (!this.configPath) {
throw new Error(`Unable to read config from paths: ${defaults.configPaths.join(", ")}`);
}
return null;
}

options = Config.read(this.configPath) as ConfigInput;
}
private static async _resolveExportedConfig(exported: ConfigInput): Promise<ConfigInputData> {
const resolved = typeof exported === "function" ? await (exported as () => unknown)() : exported;

return resolved as ConfigInputData;
}

private static async _prepareEnvironment(options: ConfigInputData): Promise<void> {
if (_.isFunction(options.prepareEnvironment)) {
options.prepareEnvironment();
await options.prepareEnvironment();
}
}

constructor(options: ConfigInputData, configPath?: string) {
if (configPath) {
this.configPath = configPath;
}

const parsedOptions = parseOptions({
Expand Down
8 changes: 5 additions & 3 deletions src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,20 +459,22 @@ type PartialCommonConfig = Partial<
export type HookType = (params: { config: Config }) => Promise<void> | undefined;

// Only browsers desiredCapabilities are required in input config
export type ConfigInput = Partial<PartialCommonConfig> & {
export type ConfigInputData = Partial<PartialCommonConfig> & {
browsers: Record<string, PartialCommonConfig & { desiredCapabilities: WebdriverIO.Capabilities }>;
plugins?: Record<string, unknown>;
sets?: Record<string, SetsConfig>;
prepareEnvironment?: () => void | null;
prepareEnvironment?: () => void | Promise<void> | null;
beforeAll?: HookType;
afterAll?: HookType;
};

export type ConfigInput = ConfigInputData | (() => ConfigInputData) | (() => Promise<ConfigInputData>);

export interface ConfigParsed extends CommonConfig {
browsers: Record<string, BrowserConfig>;
plugins: Record<string, Record<string, unknown>>;
sets: Record<string, SetsConfigParsed>;
prepareEnvironment?: () => void | null;
prepareEnvironment?: () => void | Promise<void> | null;
beforeAll?: HookType;
afterAll?: HookType;
}
Expand Down
8 changes: 6 additions & 2 deletions src/dev-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { findCwd, pipeLogsWithPrefix, probeServer, waitDevServerReady } from "./
import * as logger from "../utils/logger";
import type { Testplane } from "../testplane";

export type DevServerOpts = { testplane: Testplane; devServerConfig: Config["devServer"]; configPath: string };
export type DevServerOpts = {
testplane: Testplane;
devServerConfig: Config["devServer"];
configPath?: string;
};

export type InitDevServer = (opts: DevServerOpts) => Promise<void>;

Expand Down Expand Up @@ -45,7 +49,7 @@ export const initDevServer: InitDevServer = async ({ testplane, devServerConfig,

const devServer = spawn(devServerConfig.command, devServerConfig.args, {
env: { ...process.env, ...devServerConfig.env },
cwd: devServerConfig.cwd || findCwd(configPath),
cwd: devServerConfig.cwd || (configPath ? findCwd(configPath) : process.cwd()),
shell: true,
windowsHide: true,
});
Expand Down
16 changes: 9 additions & 7 deletions src/worker/testplane-facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,15 @@ module.exports = class TestplaneFacade {
});
}

promise = promise.then(() => {
RuntimeConfig.getInstance().extend(runtimeConfig);
const testplane = Testplane.create(configPath);

debug("worker initialized");
resolve(testplane);
});
promise = promise
.then(async () => {
RuntimeConfig.getInstance().extend(runtimeConfig);
const testplane = await Testplane.create(configPath);

debug("worker initialized");
resolve(testplane);
})
.catch(reject);
} catch (e) {
debug("worker initialization failed");
reject(e);
Expand Down
6 changes: 5 additions & 1 deletion src/worker/testplane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,14 @@ export interface Testplane {
}

export class Testplane extends BaseTestplane {
protected runner: Runner;
protected runner!: Runner;

constructor(config?: string | ConfigInput) {
super(config);
}

protected async _setup(): Promise<void> {
await super._setup();

this.runner = Runner.create(this._config);

Expand Down
6 changes: 3 additions & 3 deletions test/src/cli/commands/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ describe("cli/commands/config", () => {
let consoleInfoStub: SinonStub;
let jsonStringifyStub: SinonSpy;

const config_ = async (options: string[] = [], cli: { run: VoidFunction } = testplaneCli): Promise<void> => {
const config_ = async (options: string[] = [], cli: { run: () => Promise<void> } = testplaneCli): Promise<void> => {
process.argv = ["foo/bar/node", "foo/bar/script", "config", ...options];
cli.run();
await cli.run();

await (Command.prototype.action as SinonStub).lastCall.returnValue;
};

beforeEach(() => {
testplaneStub = Object.create(Testplane.prototype);

sandbox.stub(Testplane, "create").returns(testplaneStub);
sandbox.stub(Testplane, "create").resolves(testplaneStub);

consoleInfoStub = sandbox.stub(console, "info");
jsonStringifyStub = sandbox.spy(JSON, "stringify");
Expand Down
6 changes: 3 additions & 3 deletions test/src/cli/commands/install-deps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import type { Config } from "../../../../../src/config";
describe("cli/commands/install-deps", () => {
const sandbox = sinon.createSandbox();

let cli: { run: () => void };
let cli: { run: () => Promise<void> };
let loggerStub: { log: SinonStub; warn: SinonStub; error: SinonStub };
let testplaneStub: Writable<Testplane>;
let installBrowsersWithDriversStub: SinonStub;

const installBrowsers_ = async (argv: string = ""): Promise<void> => {
process.argv = ["foo/bar/node", "foo/bar/script", "install-deps", ...argv.split(" ")].filter(Boolean);
cli.run();
await cli.run();
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

cli.run was always async


await new Promise(resolve => setImmediate(resolve));
};
Expand All @@ -36,7 +36,7 @@ describe("cli/commands/install-deps", () => {
configurable: true,
});

sandbox.stub(Testplane, "create").returns(testplaneStub as Testplane);
sandbox.stub(Testplane, "create").resolves(testplaneStub as Testplane);

sandbox.stub(process, "exit");

Expand Down
9 changes: 6 additions & 3 deletions test/src/cli/commands/list-browsers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ describe("cli/commands/list-browsers", () => {
let loggerErrorStub: SinonStub;
let consoleInfoStub: SinonStub;

const listBrowsers_ = async (options: string[] = [], cli: { run: VoidFunction } = testplaneCli): Promise<void> => {
const listBrowsers_ = async (
options: string[] = [],
cli: { run: () => Promise<void> } = testplaneCli,
): Promise<void> => {
process.argv = ["foo/bar/node", "foo/bar/script", "list-browsers", ...options];
cli.run();
await cli.run();

await (Command.prototype.action as SinonStub).lastCall.returnValue;
};
Expand Down Expand Up @@ -56,7 +59,7 @@ describe("cli/commands/list-browsers", () => {
configurable: true,
});

sandbox.stub(Testplane, "create").returns(testplaneStub);
sandbox.stub(Testplane, "create").resolves(testplaneStub);

consoleInfoStub = sandbox.stub(console, "info");
sandbox.stub(process, "exit");
Expand Down
6 changes: 3 additions & 3 deletions test/src/cli/commands/list-tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ describe("cli/commands/list-tests", () => {
const sandbox = sinon.createSandbox();
let testplaneCli: typeof testplaneCliOriginal;

const listTests_ = async (argv: string = "", cli: { run: VoidFunction } = testplaneCli): Promise<void> => {
const listTests_ = async (argv: string = "", cli: { run: () => Promise<void> } = testplaneCli): Promise<void> => {
process.argv = ["foo/bar/node", "foo/bar/script", "list-tests", ...argv.split(" ")].filter(Boolean);
cli.run();
await cli.run();

await (Command.prototype.action as SinonStub).lastCall.returnValue;
};
Expand All @@ -36,7 +36,7 @@ describe("cli/commands/list-tests", () => {
},
),
});
sandbox.stub(Testplane, "create").returns(Object.create(Testplane.prototype));
sandbox.stub(Testplane, "create").resolves(Object.create(Testplane.prototype));
sandbox.stub(Testplane.prototype, "readTests").resolves(TestCollection.create({}));

sandbox.stub(fs, "ensureDir").resolves();
Expand Down
2 changes: 1 addition & 1 deletion test/src/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe("cli", () => {
"get-port": getPortStub,
});

sandbox.stub(Testplane, "create").returns(Object.create(Testplane.prototype));
sandbox.stub(Testplane, "create").resolves(Object.create(Testplane.prototype));
sandbox.stub(Testplane.prototype, "run").resolves();
sandbox.stub(Testplane.prototype, "extendCli");

Expand Down
Loading
Loading