Skip to content

Commit

Permalink
Feat: event setup for nightwatch hooks (#3706)
Browse files Browse the repository at this point in the history
* test observability event handling

* added support of onEvent in plugins

* event data

* code refactoring

* lint fixes

* Added LogCreated and ScreenshotCreated events

* cucumber event initial commit

* code refactoring and httpoutput added

* Added httpoutput at step finished level as well

* event refactoring and added writeReportFile function for cucumber

* changed 2 events name in cucumber to avoid  conflicts

* test fix

* setup default formatter for cucumber

* rename eventHubSetup to `setupEventHub`

* onevent optimization

* test case fix and added shouldUseNightwatchFormatter

* added tests

* screenshot event test added

* function name changed and test updated

* test case fix and added shouldUseNightwatchFormatter

* added tests

* screenshot event test added

* Update test/src/runner/testEventReporter.js

* Update test/cucumber-integration-tests/testCucumberTestsWithExpect.js

* Update test/extra/cucumber-config-events.js

* test observability event handling

* Added LogCreated and ScreenshotCreated events

* events name code refactoring

* code refactoring and merge issues fix

* cucumber report compatibilty with TO

* beforeEach, afterEach data added inside the test

* fixed pickle data and step data

* function name refactoring

* added try catch to escape terminating the nightwatch server

* emiiting only required data

* removed unneccessary state file

* test fixes

* test case

* cucumber rerun functinality fix

* src_folders error fix

* src_folders error fix

* rerurn failed file fix for cucumber

* rerun fix

* added a check if NightwatchEventHub present already

* small refactoring

* removed unnecessary import

* multiple gherkinDocument envelope fix

* paased nightwatch formatter as array

* moving nightwatch format to last

* sessionid and capabilities setup if auto_start_session set as false

* small fix for hooks

* added deepcopy of envelope

* added lodash deepcopy package

* refactor cucumber to work with typescript changes

* package.json changes to make this repo installable

* fix missing tsc

* fix missing tsc

* fix missing tsc

* fix missing tsc

* added support for parallelism

* write report error fix in cucumber parallelism

* write report error fix in cucumber parallelism

* adding host, port in cucumber report

* session capabilities for parallel tests

* merge require cli and config options

* lint fix

* test fixes

* removing not necessary only

* removing clonedeep

* SessionCapabilities handling in parallel mode

* bug fix: require cucumber only when needed

* session capabilities fix for parallel in cucumber

(cherry picked from commit a787b37b37307d8e30ce2539721697c611235bb8)

* code refactoring

* Feat/to integration (#18)

* session capabilities fix for parallel in cucumber

(cherry picked from commit a787b37b37307d8e30ce2539721697c611235bb8)

* code refactoring

* logs session capability logs

* logs session capability logs

(cherry picked from commit 9842ff7)

* capabilities session issue fix

* capabilities session issue fix

(cherry picked from commit be6a6ec)

* capabilities issue fix

(cherry picked from commit 323c3940b9a57a6c750bc1ffc168de1cca711c1a)

* handling the case when auto_start_session will be false

* code refactoring for capabilities issue

(cherry picked from commit 370c01e)
(cherry picked from commit 0c2e119)

* fix tests

* fix few tests

* remove color param from mocha cli runner tests

---------

Co-authored-by: Binayak Ghosh <ghoshbinayak@gmail.com>
Co-authored-by: Ravi Sawlani <ravisawlani04@gmail.com>
  • Loading branch information
3 people committed Sep 26, 2023
1 parent 917fc7d commit 460d28a
Show file tree
Hide file tree
Showing 35 changed files with 1,152 additions and 320 deletions.
5 changes: 5 additions & 0 deletions lib/api/client-commands/saveScreenshot.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const ClientCommand = require('./_base-command.js');
const {Logger, writeToFile} = require('../../utils');
const {COMMON_EVENTS: {ScreenshotCreated}, NightwatchEventHub} = require('../../runner/eventHub.js');

/**
* Take a screenshot of the current page and saves it as the given filename.
Expand All @@ -24,6 +25,10 @@ class SaveScreenshot extends ClientCommand {
if (client.transport.isResultSuccess(result) && result.value) {
writeToFile(fileName, result.value, 'base64').then(() => {
callback.call(this, result);

NightwatchEventHub.emit(ScreenshotCreated, {
path: fileName
});
}).catch(err => {
Logger.error(`Couldn't save screenshot to "${fileName}":`);
Logger.error(err);
Expand Down
2 changes: 1 addition & 1 deletion lib/api/web-element/waitUntil.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class WaitUntil {
node.deferred.resolve(result);
}
}.bind(this);

const node = this.scopedElement.queueAction({name: 'waitUntil', createAction});

return node;
Expand Down
7 changes: 7 additions & 0 deletions lib/http/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const Formatter = require('./formatter.js');
const HttpResponse = require('./response.js');
const Utils = require('./../utils');
const {Logger, isString} = Utils;
const {DEFAULT_RUNNER_EVENTS: {LogCreated}, NightwatchEventHub} = require('../runner/eventHub.js');

// To handle Node v17 issue. Refer https://github.com/nodejs/node/issues/40702 for details.
if (dns.setDefaultResultOrder && (typeof dns.setDefaultResultOrder === 'function')) {
Expand Down Expand Up @@ -306,6 +307,12 @@ class HttpRequest extends EventEmitter {
return;
}

if (NightwatchEventHub.runner !== 'cucumber') {
NightwatchEventHub.emit(LogCreated, {
httpOutput: Logger.collectCommandOutput()
});
}

this.emit('complete', result);
}

Expand Down
20 changes: 19 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const Settings = require('./settings/settings.js');
const ElementGlobal = require('./api/_loaders/element-global.js');
const NightwatchClient = require('./core/client.js');
const namespacedApi = require('./core/namespaced-api.js');
const {NightwatchEventHub} = require('./runner/eventHub');

const {Logger} = Utils;

Expand Down Expand Up @@ -146,8 +147,25 @@ module.exports.createClient = function({
.then(() => {
return client.createSession({argv});
})
.then(_ => {
.then((data) => {
if (this.settings.test_runner?.type === 'cucumber' && NightwatchEventHub.isAvailable) {
const NightwatchFormatter = require('./runner/test-runners/cucumber/nightwatch-format');

NightwatchFormatter.setCapabilities(data);
}

return client.api;
})
.catch((error) => {
if (this.settings.test_runner?.type === 'cucumber' && NightwatchEventHub.isAvailable) {
const NightwatchFormatter = require('./runner/test-runners/cucumber/nightwatch-format');

NightwatchFormatter.setCapabilities({
error: error
});
}

throw error;
});
},

Expand Down
8 changes: 8 additions & 0 deletions lib/reporter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ class Reporter extends SimplifiedReporter {
this.testResults.setCurrentSection(testcase);
}

markHookRun(hookName) {
this.testResults.markHookRun(hookName);
}

unmarkHookRun(hookName) {
this.testResults.unmarkHookRun(hookName);
}

setAxeResults(results) {
this.currentTest.results.a11y = results;
}
Expand Down
52 changes: 52 additions & 0 deletions lib/reporter/results.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ module.exports = class Results {
this.skippedAtRuntime = tests.slice(0);
this.testcases = {};
this.testSections = {};
this.testSectionHook = {};
this.isHookRunning = false;
this.suiteName = opts.suiteName;
this.moduleKey = opts.moduleKey;
this.modulePath = opts.modulePath;
Expand Down Expand Up @@ -57,6 +59,41 @@ module.exports = class Results {
this.initCount(allScreenedTests);
}

markHookRun(hookName) {
this.isHookRunning = true;
this.currentHookName = hookName;
this.testSectionHook = this.createTestCaseResults({testName: `${this.currentSectionName}__${hookName}`});

// reset test hooks output
Logger.collectTestHooksOutput();
}

unmarkHookRun() {
this.isHookRunning = false;
if (!this.currentHookName) {
throw new Error('Hook run not started yet');
}

const currentSection = this.getTestSection(this.currentSectionName);
const hookdata = this.testSectionHook;

const startTime = hookdata.startTimestamp;
const endTime = new Date().getTime();
const elapsedTime = endTime - startTime;
hookdata.endTimestamp = endTime;

hookdata.time = (elapsedTime / 1000).toPrecision(4);
hookdata.timeMs = elapsedTime;

hookdata.httpOutput = Logger.collectTestHooksOutput();

if (hookdata.errors > 0 || hookdata.failed > 0) {
hookdata.status = Results.TEST_FAIL;
}

currentSection[this.currentHookName] = hookdata;
}

get initialResult() {
return this.__initialResult;
}
Expand Down Expand Up @@ -408,6 +445,10 @@ module.exports = class Results {
const result = this.getTestSection(this.currentSectionName);
result.commands.push(command);

if (this.isHookRunning) {
this.testSectionHook.commands.push(command);
}

return this;
}

Expand Down Expand Up @@ -599,4 +640,15 @@ module.exports = class Results {
return prev;
}, initialReport);
}

get eventDataToEmit() {
const {testEnv, sessionCapabilities, sessionId, tags, modulePath, name, host} = this;

return {
envelope: this.testSections,
metadata: {
testEnv, sessionCapabilities, sessionId, tags, modulePath, name, host
}
};
}
};
57 changes: 56 additions & 1 deletion lib/runner/cli/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ const ProcessListener = require('../process-listener.js');
const analyticsCollector = require('../../utils/analytics.js');
const {RealIosDeviceIdError, iosRealDeviceUDID, isRealIos, isMobile, killSimulator, isAndroid} = require('../../utils/mobile.js');
const {Logger, singleSourceFile, isSafari, isLocalhost} = Utils;
const NightwatchEvent = require('../eventHub.js');
const {NightwatchEventHub, DEFAULT_RUNNER_EVENTS} = NightwatchEvent;
const {GlobalHook} = DEFAULT_RUNNER_EVENTS;

class CliRunner {
static get CONFIG_FILE_JS() {
Expand Down Expand Up @@ -264,6 +267,38 @@ class CliRunner {
is_bstack: this.test_settings.desiredCapabilities['bstack:options'] !== undefined,
test_runner: this.test_settings.test_runner ? this.test_settings.test_runner.type : null
});

this.setupEventHub();
}

isRegisterEventHandlersCallbackExistsInGlobal() {
const {globals} = this.test_settings;
const {plugins = []} = this.globals;

return Utils.isFunction(globals.registerEventHandlers) || plugins.some(plugin => plugin.globals && Utils.isFunction(plugin.globals.registerEventHandlers));
}

setupEventHub() {
if (this.isRegisterEventHandlersCallbackExistsInGlobal() && !NightwatchEventHub.isAvailable) {
NightwatchEventHub.runner = this.testRunner.type;
NightwatchEventHub.isAvailable = true;

const {globals, output_folder} = this.test_settings;
NightwatchEventHub.output_folder = output_folder;
const {plugins} = this.globals;

if (Utils.isFunction(globals.registerEventHandlers)) {
globals.registerEventHandlers(NightwatchEventHub);
}

if (plugins.length > 0) {
plugins.forEach((plugin) => {
if (plugin.globals && Utils.isFunction(plugin.globals.registerEventHandlers)){
plugin.globals.registerEventHandlers(NightwatchEventHub);
}
});
}
}
}

loadTypescriptTranspiler() {
Expand Down Expand Up @@ -359,8 +394,28 @@ class CliRunner {
}

runGlobalHook(key, args = [], isParallelHook = false) {
let promise;
const {globals} = this.test_settings;

if (isParallelHook && Concurrency.isWorker() || !isParallelHook && !Concurrency.isWorker()) {
return this.globals.runGlobalHook(key, args);
const start_time = new Date();

if (Utils.isFunction(globals[key])) {
NightwatchEventHub.emit(GlobalHook[key].started, {
start_time: start_time
});
}

promise = this.globals.runGlobalHook(key, args);

return promise.finally(() => {
if (Utils.isFunction(globals[key])) {
NightwatchEventHub.emit(GlobalHook[key].finished, {
start_time: start_time,
end_time: new Date()
});
}
});
}

return Promise.resolve();
Expand Down
140 changes: 140 additions & 0 deletions lib/runner/eventHub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
const fs = require('fs');
const path = require('path');
const EventEmitter = require('events');
const {Logger, createFolder} = require('../utils');

class NightwatchEventHub extends EventEmitter {
emit(eventName, data) {
if (this.isAvailable) {
try {
super.emit(eventName, data);
} catch (err) {
Logger.error(err);
}
}

if (eventName === 'TestFinished' && this.output_folder && this.runner === 'cucumber') {
let {output_folder} = this;
output_folder = path.join(output_folder, 'cucumber');
const filename = path.join(output_folder, 'cucumber-report.json');

this.writeReportFile(filename, JSON.stringify(data.report, null, 2), true, output_folder)
.then(_ => {
Logger.info(Logger.colors.stack_trace(`Wrote JSON report file to: ${path.resolve(filename)}`));
});
}
}

get isAvailable() {
return this.eventFnExist;
}

set isAvailable(eventFnExist) {
this.eventFnExist = eventFnExist;
}

set runner(type) {
this.runnerType = type;
}

get runner() {
return this.runnerType;
}

writeReportFile(filename, rendered, shouldCreateFolder, output_folder) {
return (shouldCreateFolder ? createFolder(output_folder) : Promise.resolve())
.then(() => {
return new Promise((resolve, reject) => {
fs.writeFile(filename, rendered, function(err) {
if (err) {
return reject(err);
}

resolve();
});
});
});
}
}

const instance = new NightwatchEventHub();

module.exports = {
NightwatchEventHub: instance,
COMMON_EVENTS: {
ScreenshotCreated: 'ScreenshotCreated'
},
DEFAULT_RUNNER_EVENTS: {
GlobalHook: {
before: {
started: 'GlobalBeforeStarted',
finished: 'GlobalBeforeFinished'
},

beforeChildProcess: {
started: 'GlobalBeforeChildProcessStarted',
finished: 'GlobalBeforeChildProcessFinished'
},

beforeEach: {
started: 'GlobalBeforeEachStarted',
finished: 'GlobalBeforeEachFinished'
},

afterEach: {
started: 'GlobalAfterEachStarted',
finished: 'GlobalAfterEachFinished'
},

afterChildProcess: {
started: 'GlobalAfterChildProcessStarted',
finished: 'GlobalAfterChildProcessFinished'
},

after: {
started: 'GlobalAfterStarted',
finished: 'GlobalAfterFinished'
}
},

TestSuiteHook: {
started: 'TestSuiteStarted',
finished: 'TestSuiteFinished',

before: {
started: 'BeforeStarted',
finished: 'BeforeFinished'
},

beforeEach: {
started: 'BeforeEachStarted',
finished: 'BeforeEachFinished'
},

test: {
started: 'TestRunStarted',
finished: 'TestRunFinished'
},

afterEach: {
started: 'AfterEachStarted',
finished: 'AfterEachFinished'
},

after: {
started: 'AfterStarted',
finished: 'AfterFinished'
}
},

LogCreated: 'LogCreated'
},
CUCUMBER_RUNNER_EVENTS: {
TestStarted: 'TestStarted',
TestFinished: 'TestFinished',
TestCaseStarted: 'TestCaseStarted',
TestCaseFinished: 'TestCaseFinished',
TestStepStarted: 'TestStepStarted',
TestStepFinished: 'TestStepFinished'
}
};

0 comments on commit 460d28a

Please sign in to comment.