-
Notifications
You must be signed in to change notification settings - Fork 40
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(html-reporter): add testplane support #541
Conversation
index.ts
Outdated
import {ImagesInfoSaver} from './lib/images-info-saver'; | ||
import {getStatus} from './lib/test-adapter/testplane'; | ||
|
||
export default (testplane: Testplane, opts: Partial<ReporterOptions>): void => { |
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.
We can use export default
for testplane
hermione.ts
Outdated
const htmlReporter = HtmlReporter.create(config, {toolName: ToolName.Hermione}); | ||
|
||
(hermione as Hermione & HtmlReporterApi).htmlReporter = htmlReporter; | ||
|
||
let isCliCommandLaunched = false; | ||
let handlingTestResults: Promise<void>; | ||
let staticReportBuilder: StaticReportBuilder; | ||
|
||
const withMiddleware = <T extends (...args: unknown[]) => unknown>(fn: T): | ||
(...args: Parameters<T>) => ReturnType<T> | undefined => { | ||
return (...args: unknown[]) => { | ||
// If any CLI command was launched, e.g. merge-reports, we need to interrupt regular flow | ||
if (isCliCommandLaunched) { | ||
return; | ||
} | ||
|
||
return fn.call(undefined, ...args) as ReturnType<T>; | ||
}; | ||
}; | ||
|
||
hermione.on(hermione.events.CLI, (commander: CommanderStatic) => { | ||
_.values(cliCommands).forEach((command: string) => { | ||
// eslint-disable-next-line @typescript-eslint/no-var-requires | ||
require(path.resolve(__dirname, 'lib/cli-commands', command))(commander, config, hermione); | ||
|
||
commander.prependListener(`command:${command}`, () => { | ||
isCliCommandLaunched = true; | ||
}); | ||
}); | ||
}); | ||
|
||
hermione.on(hermione.events.INIT, withMiddleware(async () => { | ||
const dbClient = await SqliteClient.create({htmlReporter, reportPath: config.path}); | ||
const imageStore = new SqliteImageStore(dbClient); | ||
const expectedPathsCache = new Cache<[TestSpecByPath, string | undefined], string>(getExpectedCacheKey); | ||
|
||
const imagesInfoSaver = new ImagesInfoSaver({ | ||
imageFileSaver: htmlReporter.imagesSaver, | ||
expectedPathsCache, | ||
imageStore, | ||
reportPath: htmlReporter.config.path | ||
}); | ||
|
||
staticReportBuilder = StaticReportBuilder.create(htmlReporter, config, {dbClient, imagesInfoSaver}); | ||
|
||
handlingTestResults = Promise.all([ | ||
staticReportBuilder.saveStaticFiles(), | ||
handleTestResults(hermione, staticReportBuilder) | ||
]).then(async () => { | ||
await staticReportBuilder.finalize(); | ||
}).then(async () => { | ||
await htmlReporter.emitAsync(htmlReporter.events.REPORT_SAVED, {reportPath: config.path}); | ||
}); | ||
|
||
htmlReporter.emit(htmlReporter.events.DATABASE_CREATED, dbClient.getRawConnection()); | ||
})); | ||
|
||
hermione.on(hermione.events.RUNNER_START, withMiddleware((runner) => { | ||
staticReportBuilder.registerWorkers(createWorkers(runner as unknown as CreateWorkersRunner)); | ||
})); | ||
|
||
hermione.on(hermione.events.RUNNER_END, withMiddleware(async () => { | ||
try { | ||
await handlingTestResults; | ||
|
||
logPathToHtmlReport(config); | ||
} catch (e: unknown) { | ||
logError(e as Error); | ||
} | ||
})); | ||
}; | ||
|
||
async function handleTestResults(hermione: Hermione, reportBuilder: StaticReportBuilder): Promise<void> { | ||
return new Promise((resolve, reject) => { | ||
const queue = new PQueue({concurrency: os.cpus().length}); | ||
const promises: Promise<unknown>[] = []; | ||
|
||
[ | ||
{eventName: hermione.events.TEST_PASS}, | ||
{eventName: hermione.events.RETRY}, | ||
{eventName: hermione.events.TEST_FAIL}, | ||
{eventName: hermione.events.TEST_PENDING} | ||
].forEach(({eventName}) => { | ||
type AnyHermioneTestEvent = typeof hermione.events.TEST_PASS; | ||
|
||
hermione.on(eventName as AnyHermioneTestEvent, (testResult: HermioneTestResult) => { | ||
promises.push(queue.add(async () => { | ||
const formattedResult = formatTestResult(testResult, getStatus(eventName, hermione.events, testResult)); | ||
|
||
await reportBuilder.addTestResult(formattedResult); | ||
}).catch(reject)); | ||
}); | ||
}); | ||
|
||
hermione.on(hermione.events.RUNNER_END, () => { | ||
return Promise.all(promises).then(() => resolve(), reject); | ||
}); | ||
}); | ||
} | ||
export = pluginHandler; |
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.
Export default export of ./index.ts
as module.exports
, because old versions of hermione do not support export default
await Promise.all( | ||
_(customGui) | ||
.flatMap<CustomGuiItem>(_.identity) | ||
.map((ctx) => ctx.initialize?.({hermione, ctx})) | ||
.map((ctx) => ctx.initialize?.({testplane, hermione: testplane, ctx})) |
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.
testplane
and hermione
props are Object.is
copies for backward compatibility
const {sectionName, groupIndex, controlIndex} = payload; | ||
const ctx = customGui[sectionName][groupIndex]; | ||
const control = ctx.controls[controlIndex]; | ||
|
||
await ctx.action({hermione, control, ctx}); | ||
await ctx.action({testplane, hermione: testplane, control, ctx}); |
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.
testplane
and hermione
props are Object.is
copies for backward compatibility
const getRegisterFn = tool => `function __${tool}_html_reporter_register_plugin__(p) {return p;};`; | ||
|
||
const exec = new Function(`${getRegisterFn('testplane')} ${getRegisterFn('hermione')} return ${code};`); | ||
|
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.
Add support for testplane html reporter plugins
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.
This will work if both are present, right?
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.
Yes, it will.
I tested it locally with "some plugins are built for testplane, while others stay for hermione" and remote with "all plugins are built for hermione": https://nda.ya.ru/t/1ELAEazw75GviJ
|
||
export interface HermioneTestResult extends HermioneTestResultOriginal { | ||
export interface HermioneTestResult extends TestResult { |
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.
Not a part of public API, no JSDoc deprecated's is needed
// eslint-disable-next-line @typescript-eslint/no-empty-interface | ||
export interface TestplaneTestResult extends HermioneTestResult {} |
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.
These two are separated, so we could add something to TestplaneTestResult
while it still does not exist on HermioneTestResult
testplane: Testplane, | ||
/** | ||
* @deprecated Use `testplane` instead | ||
*/ | ||
hermione: Testplane, |
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.
Add some JSDoc deprecated comments
test/unit/index.js
Outdated
const runHtmlReporter = proxyquire('../../', { | ||
'./lib/sqlite-client': {SqliteClient}, | ||
'./lib/server-utils': utils, | ||
'./lib/report-builder/static': {StaticReportBuilder}, | ||
'./lib/plugin-api': {HtmlReporter} | ||
}); | ||
}).default; |
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.
use default-exported value here
|
||
createTestRunner = sinon.stub(); | ||
createTestRunner = sandbox.stub(); |
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.
replaced sinon
with sandbox
in the file, because some other tests (in another files) were failing without it
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.
Looks good to me except making testplane a default export from the package, I am strongly against that.
const getRegisterFn = tool => `function __${tool}_html_reporter_register_plugin__(p) {return p;};`; | ||
|
||
const exec = new Function(`${getRegisterFn('testplane')} ${getRegisterFn('hermione')} return ${code};`); | ||
|
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.
This will work if both are present, right?
98a63e8
to
362a457
Compare
Merging straight to master
What is done
gui
likenpx testplane gui
if it was launched withnpx testplane
)testplane
property in addition tohermione
)TBD