From ff62aadb9bc86e81df9ac430d52a756c1aca675f Mon Sep 17 00:00:00 2001 From: Roman Kuznetsov Date: Thu, 9 Jan 2025 09:52:16 +0300 Subject: [PATCH] feat: add 'list-browsers' command --- src/cli/commands/list-browsers/index.ts | 102 +++++++++++++++ src/cli/constants.ts | 1 + test/src/cli/commands/list-browsers/index.ts | 131 +++++++++++++++++++ 3 files changed, 234 insertions(+) create mode 100644 src/cli/commands/list-browsers/index.ts create mode 100644 test/src/cli/commands/list-browsers/index.ts diff --git a/src/cli/commands/list-browsers/index.ts b/src/cli/commands/list-browsers/index.ts new file mode 100644 index 000000000..8e58736e6 --- /dev/null +++ b/src/cli/commands/list-browsers/index.ts @@ -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, 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); + } + }); +}; diff --git a/src/cli/constants.ts b/src/cli/constants.ts index ff7019f03..3fe4abe81 100644 --- a/src/cli/constants.ts +++ b/src/cli/constants.ts @@ -1,4 +1,5 @@ export const CliCommands = { LIST_TESTS: "list-tests", + LIST_BROWSERS: "list-browsers", INSTALL_DEPS: "install-deps", } as const; diff --git a/test/src/cli/commands/list-browsers/index.ts b/test/src/cli/commands/list-browsers/index.ts new file mode 100644 index 000000000..9b84eeaae --- /dev/null +++ b/test/src/cli/commands/list-browsers/index.ts @@ -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 => { + 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, "chrome@109.0 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); + }); + }); +});