-
Notifications
You must be signed in to change notification settings - Fork 39
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 ability to run cli commands using html-reporter binary #551
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/usr/bin/env node | ||
'use strict'; | ||
|
||
(async () => { | ||
await require('../build/lib/cli').run(); | ||
})(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import type {Config, TestCollection} from 'testplane'; | ||
import type {CommanderStatic} from '@gemini-testing/commander'; | ||
|
||
import {GuiApi} from '../../gui/api'; | ||
import {EventSource} from '../../gui/event-source'; | ||
import {GuiReportBuilder} from '../../report-builder/gui'; | ||
import {ToolName} from '../../constants'; | ||
|
||
import type {ReporterConfig, ImageFile} from '../../types'; | ||
import type {TestSpec} from './types'; | ||
|
||
export interface ToolAdapterOptionsFromCli { | ||
toolName: ToolName; | ||
configPath?: string; | ||
} | ||
|
||
export interface UpdateReferenceOpts { | ||
refImg: ImageFile; | ||
state: string; | ||
} | ||
|
||
export interface ToolAdapter { | ||
readonly toolName: ToolName; | ||
readonly config: Config; | ||
readonly reporterConfig: ReporterConfig; | ||
readonly guiApi?: GuiApi; | ||
|
||
initGuiApi(): void; | ||
readTests(paths: string[], cliTool: CommanderStatic): Promise<TestCollection>; | ||
run(testCollection: TestCollection, tests: TestSpec[], cliTool: CommanderStatic): Promise<boolean>; | ||
|
||
updateReference(opts: UpdateReferenceOpts): void; | ||
handleTestResults(reportBuilder: GuiReportBuilder, eventSource: EventSource): void; | ||
|
||
halt(err: Error, timeout: number): void; | ||
} | ||
|
||
export const makeToolAdapter = async (opts: ToolAdapterOptionsFromCli): Promise<ToolAdapter> => { | ||
if (opts.toolName === ToolName.Testplane) { | ||
const {TestplaneToolAdapter} = await import('./testplane'); | ||
|
||
return TestplaneToolAdapter.create(opts); | ||
} else if (opts.toolName === ToolName.Playwright) { | ||
throw new Error('Playwright is not supported yet'); | ||
} else { | ||
throw new Error(`Tool adapter with name: "${opts.toolName}" is not supported`); | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import _ from 'lodash'; | ||
import Testplane, {type Config, type TestCollection} from 'testplane'; | ||
import type {CommanderStatic} from '@gemini-testing/commander'; | ||
|
||
import {GuiApi} from '../../../gui/api'; | ||
import {parseConfig} from '../../../config'; | ||
import {HtmlReporter} from '../../../plugin-api'; | ||
import {ApiFacade} from '../../../gui/api/facade'; | ||
import {createTestRunner} from './runner'; | ||
import {EventSource} from '../../../gui/event-source'; | ||
import {GuiReportBuilder} from '../../../report-builder/gui'; | ||
import {handleTestResults} from './test-results-handler'; | ||
import {ToolName} from '../../../constants'; | ||
|
||
import type {ToolAdapter, ToolAdapterOptionsFromCli, UpdateReferenceOpts} from '../index'; | ||
import type {TestSpec, CustomGuiActionPayload} from '../types'; | ||
import type {ReporterConfig, CustomGuiItem} from '../../../types'; | ||
|
||
type HtmlReporterApi = { | ||
gui: ApiFacade; | ||
htmlReporter: HtmlReporter; | ||
}; | ||
type TestplaneWithHtmlReporter = Testplane & HtmlReporterApi; | ||
|
||
interface ReplModeOption { | ||
enabled: boolean; | ||
beforeTest: boolean; | ||
onFail: boolean; | ||
} | ||
|
||
interface OptionsFromPlugin { | ||
toolName: ToolName.Testplane; | ||
tool: Testplane; | ||
reporterConfig: ReporterConfig; | ||
} | ||
|
||
type Options = ToolAdapterOptionsFromCli | OptionsFromPlugin; | ||
|
||
export class TestplaneToolAdapter implements ToolAdapter { | ||
private _toolName: ToolName; | ||
private _tool: TestplaneWithHtmlReporter; | ||
private _reporterConfig: ReporterConfig; | ||
private _htmlReporter: HtmlReporter; | ||
private _guiApi?: GuiApi; | ||
|
||
static create<TestplaneToolAdapter>( | ||
this: new (options: Options) => TestplaneToolAdapter, | ||
options: Options | ||
): TestplaneToolAdapter { | ||
return new this(options); | ||
} | ||
|
||
constructor(opts: Options) { | ||
if ('tool' in opts) { | ||
this._tool = opts.tool as TestplaneWithHtmlReporter; | ||
this._reporterConfig = opts.reporterConfig; | ||
} else { | ||
// in order to not use static report with gui simultaneously | ||
process.env['html_reporter_enabled'] = false.toString(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I need to disable |
||
this._tool = Testplane.create(opts.configPath) as TestplaneWithHtmlReporter; | ||
|
||
const pluginOpts = getPluginOptions(this._tool.config); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here I need to get options of html-reporter from parsed testplane config. |
||
this._reporterConfig = parseConfig(pluginOpts); | ||
} | ||
|
||
this._toolName = opts.toolName; | ||
this._htmlReporter = HtmlReporter.create(this._reporterConfig, {toolName: ToolName.Testplane}); | ||
|
||
// in order to be able to use it from other plugins as an API | ||
this._tool.htmlReporter = this._htmlReporter; | ||
} | ||
|
||
get toolName(): ToolName { | ||
return this._toolName; | ||
} | ||
|
||
get config(): Config { | ||
return this._tool.config; | ||
} | ||
|
||
get reporterConfig(): ReporterConfig { | ||
return this._reporterConfig; | ||
} | ||
|
||
get htmlReporter(): HtmlReporter { | ||
return this._htmlReporter; | ||
} | ||
|
||
get guiApi(): GuiApi | undefined { | ||
return this._guiApi; | ||
} | ||
|
||
initGuiApi(): void { | ||
this._guiApi = GuiApi.create(); | ||
|
||
// in order to be able to use it from other plugins as an API | ||
this._tool.gui = this._guiApi.gui; | ||
} | ||
|
||
async readTests(paths: string[], cliTool: CommanderStatic): Promise<TestCollection> { | ||
const {grep, set: sets, browser: browsers} = cliTool; | ||
const replMode = getReplModeOption(cliTool); | ||
|
||
return this._tool.readTests(paths, {grep, sets, browsers, replMode}); | ||
} | ||
|
||
async run(testCollection: TestCollection, tests: TestSpec[] = [], cliTool: CommanderStatic): Promise<boolean> { | ||
const {grep, set: sets, browser: browsers, devtools = false} = cliTool; | ||
const replMode = getReplModeOption(cliTool); | ||
const runner = createTestRunner(testCollection, tests); | ||
|
||
return runner.run((collection) => this._tool.run(collection, {grep, sets, browsers, devtools, replMode})); | ||
} | ||
|
||
updateReference(opts: UpdateReferenceOpts): void { | ||
this._tool.emit(this._tool.events.UPDATE_REFERENCE, opts); | ||
} | ||
|
||
handleTestResults(reportBuilder: GuiReportBuilder, eventSource: EventSource): void { | ||
handleTestResults(this._tool, reportBuilder, eventSource); | ||
} | ||
|
||
halt(err: Error, timeout: number): void { | ||
this._tool.halt(err, timeout); | ||
} | ||
|
||
async initGuiHandler(): Promise<void> { | ||
const {customGui} = this._reporterConfig; | ||
|
||
await Promise.all( | ||
_(customGui) | ||
.flatMap<CustomGuiItem>(_.identity) | ||
.map((ctx) => ctx.initialize?.({testplane: this._tool, hermione: this._tool, ctx})) | ||
.value() | ||
); | ||
} | ||
|
||
async runCustomGuiAction(payload: CustomGuiActionPayload): Promise<void> { | ||
const {customGui} = this._reporterConfig; | ||
|
||
const {sectionName, groupIndex, controlIndex} = payload; | ||
const ctx = customGui[sectionName][groupIndex]; | ||
const control = ctx.controls[controlIndex]; | ||
|
||
await ctx.action({testplane: this._tool, hermione: this._tool, control, ctx}); | ||
} | ||
} | ||
|
||
function getPluginOptions(config: Config): Partial<ReporterConfig> { | ||
const defaultOpts = {}; | ||
|
||
for (const toolName of [ToolName.Testplane, 'hermione']) { | ||
const opts = _.get(config.plugins, `html-reporter/${toolName}`, defaultOpts); | ||
|
||
if (!_.isEmpty(opts)) { | ||
return opts; | ||
} | ||
} | ||
|
||
return defaultOpts; | ||
} | ||
|
||
function getReplModeOption(cliTool: CommanderStatic): ReplModeOption { | ||
const {repl = false, replBeforeTest = false, replOnFail = false} = cliTool; | ||
|
||
return { | ||
enabled: repl || replBeforeTest || replOnFail, | ||
beforeTest: replBeforeTest, | ||
onFail: replOnFail | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,10 @@ | ||
import _ from 'lodash'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All testplane runners moved to testplane folder. They cannot be used for pwt (for example) |
||
import type {TestCollection} from 'testplane'; | ||
|
||
import {TestRunner, TestSpec} from './runner'; | ||
import {AllTestRunner} from './all-test-runner'; | ||
import {SpecificTestRunner} from './specific-test-runner'; | ||
import type {TestRunner} from './runner'; | ||
import type {TestSpec} from '../../types'; | ||
|
||
export const createTestRunner = (collection: TestCollection, tests: TestSpec[]): TestRunner => { | ||
return _.isEmpty(tests) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
export interface TestSpec { | ||
testName: string; | ||
browserName: string; | ||
} | ||
|
||
export interface CustomGuiActionPayload { | ||
sectionName: string; | ||
groupIndex: number; | ||
controlIndex: number; | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,16 @@ | ||
'use strict'; | ||
|
||
const {cliCommands} = require('.'); | ||
const runGui = require('../gui').default; | ||
const {Api} = require('../gui/api'); | ||
const {commands} = require('..'); | ||
const runGui = require('../../gui').default; | ||
|
||
const {GUI: commandName} = cliCommands; | ||
const {GUI: commandName} = commands; | ||
|
||
module.exports = (program, pluginConfig, testplane) => { | ||
// must be executed here because it adds `gui` field in `gemini`, `testplane` and `hermione tool`, | ||
module.exports = (cliTool, toolAdapter) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All cli commands now works with |
||
// must be executed here because it adds `gui` field in tool instance, | ||
// which is available to other plugins and is an API for interacting with the current plugin | ||
const guiApi = Api.create(testplane); | ||
toolAdapter.initGuiApi(); | ||
|
||
program | ||
cliTool | ||
.command(`${commandName} [paths...]`) | ||
.allowUnknownOption() | ||
.description('update the changed screenshots or gather them if they does not exist') | ||
|
@@ -20,6 +19,6 @@ module.exports = (program, pluginConfig, testplane) => { | |
.option('-a, --auto-run', 'auto run immediately') | ||
.option('-O, --no-open', 'not to open a browser window after starting the server') | ||
.action((paths, options) => { | ||
runGui({paths, testplane, guiApi, configs: {options, program, pluginConfig}}); | ||
runGui({paths, toolAdapter, cli: {options, tool: cliTool}}); | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is async because I need to import cli commands in runtime and register them