Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add 'list-browsers' command #1051

Merged
merged 1 commit into from
Jan 9, 2025
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
102 changes: 102 additions & 0 deletions src/cli/commands/list-browsers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import _ from "lodash";
import { Testplane } from "../../../testplane";
import { CliCommands } from "../../constants";
import logger from "../../../utils/logger";

const { LIST_BROWSERS: commandName } = CliCommands;

const BrowsersListOutputType = {
IDS: "ids",
TAGS: "tags",
};

const BrowsersListOutputFormat = {
JSON: "json",
PLAIN: "plain",
};

const validateOption = (availableOptions: Record<string, string>, value: string, optionName: string): void => {
const optionsArray = Object.values(availableOptions);

if (!optionsArray.includes(value)) {
const optionsList = optionsArray.map(value => `"${value}"`).join(", ");

throw new Error(`"${optionName}" option must be one of: ${optionsList}, but got "${value}"`);
}
};

const extractBrowserIds = (testplaneConfig: Testplane["config"]): string[] => {
const browsersArray = Object.keys(testplaneConfig.browsers);

return _.uniq(browsersArray).filter(Boolean).sort();
};

const extractBrowserTags = (
testplaneConfig: Testplane["config"],
format: (typeof BrowsersListOutputFormat)[keyof typeof BrowsersListOutputFormat],
): { browserName: string; browserVersion?: string }[] | string[] => {
const browserConfigs = Object.values(testplaneConfig.browsers).filter(browserConfig => {
return Boolean(browserConfig.desiredCapabilities?.browserName);
});

if (format === BrowsersListOutputFormat.PLAIN) {
const browsersArray = browserConfigs.map(browserConfig => {
const { browserName, browserVersion } = browserConfig.desiredCapabilities || {};

return browserVersion ? `${browserName}@${browserVersion}` : (browserName as string);
});

return _.uniq(browsersArray).sort();
}

const browsersArray = browserConfigs.map(browserConfig => {
const { browserName, browserVersion } = browserConfig.desiredCapabilities || {};

return { browserName: browserName as string, browserVersion };
});

return _(browsersArray).uniq().sortBy(["browserName", "browserVersion"]).value();
};

export const registerCmd = (cliTool: typeof commander, testplane: Testplane): void => {
cliTool
.command(commandName)
.description("Lists all browsers from the config")
.option(
"--type [type]",
"return browsers in specified type ('tags': browserName and browserVersion, 'ids': browserId from config)",
String,
BrowsersListOutputType.TAGS,
)
.option(
"--format [format]",
"return browsers in specified format ('json' / 'plain')",
String,
BrowsersListOutputFormat.JSON,
)
.action(async (options: typeof commander) => {
const { type, format } = options;

try {
validateOption(BrowsersListOutputType, type, "type");
validateOption(BrowsersListOutputFormat, format, "format");

const browsersSorted =
type === BrowsersListOutputType.IDS
? extractBrowserIds(testplane.config)
: extractBrowserTags(testplane.config, format);

const outputResult =
format === BrowsersListOutputFormat.PLAIN
? browsersSorted.join(" ")
: JSON.stringify(browsersSorted);

console.info(outputResult);

process.exit(0);
} catch (err) {
logger.error((err as Error).stack || err);
process.exit(1);
}
});
};
1 change: 1 addition & 0 deletions src/cli/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const CliCommands = {
LIST_TESTS: "list-tests",
LIST_BROWSERS: "list-browsers",
INSTALL_DEPS: "install-deps",
} as const;
131 changes: 131 additions & 0 deletions test/src/cli/commands/list-browsers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { Command } from "@gemini-testing/commander";
import sinon, { SinonStub } from "sinon";

import logger from "../../../../../src/utils/logger";
import { Testplane } from "../../../../../src/testplane";
import * as testplaneCli from "../../../../../src/cli";

describe("cli/commands/list-browsers", () => {
const sandbox = sinon.createSandbox();

let testplaneStub: Testplane;
let loggerErrorStub: SinonStub;
let consoleInfoStub: SinonStub;

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

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

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

Object.defineProperty(testplaneStub, "config", {
value: {
browsers: {
"my-chrome": { desiredCapabilities: { browserName: "chrome", browserVersion: "109.0" } },
"my-safari": { desiredCapabilities: { browserName: "safari" } },
},
},
writable: true,
configurable: true,
});

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

loggerErrorStub = sandbox.stub(logger, "error");
consoleInfoStub = sandbox.stub(console, "info");
sandbox.stub(process, "exit");

sandbox.spy(Command.prototype, "action");
});

afterEach(() => sandbox.restore());

it("should exit with code 0", async () => {
await listBrowsers_();

assert.notCalled(loggerErrorStub);
assert.calledOnceWith(process.exit as unknown as SinonStub, 0);
});

describe("list browsers", () => {
it("should output browser tags in json format", async () => {
testplaneStub.config.browsers = {
"my-chrome": { desiredCapabilities: { browserName: "chrome", browserVersion: "109.0" } },
"my-safari": { desiredCapabilities: { browserName: "safari" } },
} as unknown as Testplane["config"]["browsers"];

await listBrowsers_();

const browserTags = [{ browserName: "chrome", browserVersion: "109.0" }, { browserName: "safari" }];

assert.calledWith(consoleInfoStub, JSON.stringify(browserTags));
});

it("should output browser ids in json format", async () => {
testplaneStub.config.browsers = {
"my-chrome": { desiredCapabilities: { browserName: "chrome", browserVersion: "109.0" } },
"my-safari": { desiredCapabilities: { browserName: "safari" } },
} as unknown as Testplane["config"]["browsers"];

await listBrowsers_(["--type", "ids"]);

const browserIds = ["my-chrome", "my-safari"];

assert.calledWith(consoleInfoStub, JSON.stringify(browserIds));
});

it("should output browser ids in plain format", async () => {
testplaneStub.config.browsers = {
"my-chrome": { desiredCapabilities: { browserName: "chrome", browserVersion: "109.0" } },
"my-safari": { desiredCapabilities: { browserName: "safari" } },
} as unknown as Testplane["config"]["browsers"];

await listBrowsers_(["--type", "ids", "--format", "plain"]);

assert.calledWith(consoleInfoStub, "my-chrome my-safari");
});

it("should output browser tags in plain format", async () => {
testplaneStub.config.browsers = {
"my-chrome": { desiredCapabilities: { browserName: "chrome", browserVersion: "109.0" } },
"my-safari": { desiredCapabilities: { browserName: "safari" } },
} as unknown as Testplane["config"]["browsers"];

await listBrowsers_(["--format", "plain"]);

assert.calledWith(consoleInfoStub, "[email protected] safari");
});

it("should throw error if format is invalid", async () => {
testplaneStub.config.browsers = {
"my-chrome": { desiredCapabilities: { browserName: "chrome", browserVersion: "109.0" } },
"my-safari": { desiredCapabilities: { browserName: "safari" } },
} as unknown as Testplane["config"]["browsers"];

await listBrowsers_(["--format", "plan"]);

const errorMessage = '"format" option must be one of: "json", "plain", but got "plan"';

assert.calledOnceWith(loggerErrorStub, sinon.match(errorMessage));
assert.calledOnceWith(process.exit as unknown as SinonStub, 1);
});

it("should throw error if type is invalid", async () => {
testplaneStub.config.browsers = {
"my-chrome": { desiredCapabilities: { browserName: "chrome", browserVersion: "109.0" } },
"my-safari": { desiredCapabilities: { browserName: "safari" } },
} as unknown as Testplane["config"]["browsers"];

await listBrowsers_(["--type", "id"]);

const errorMessage = '"type" option must be one of: "ids", "tags", but got "id"';

assert.calledOnceWith(loggerErrorStub, sinon.match(errorMessage));
assert.calledOnceWith(process.exit as unknown as SinonStub, 1);
});
});
});
Loading