Skip to content

Commit 133faea

Browse files
authored
Merge branch 'nightwatchjs:main' into remove-request
2 parents 38436cd + bd3460a commit 133faea

5 files changed

Lines changed: 219 additions & 11 deletions

File tree

nightwatch/globals.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ module.exports = {
9595

9696
Object.values(workerList).forEach((worker) => {
9797
worker.process.on('message', async (data) => {
98+
if (data.POST_SESSION_EVENT) {
99+
helper.storeSessionsData(data);
100+
}
98101
if (data.eventType === EVENTS.LOG_INIT) {
99102
const testCaseStartedId = data.loggingData.message.replace('TEST-OBSERVABILITY-PID-TESTCASE-MAPPING-', '').slice(1, -1);
100103
const testCaseId = _testCasesData[testCaseStartedId]?.testCaseId;
@@ -152,7 +155,7 @@ module.exports = {
152155
}
153156
});
154157

155-
eventBroadcaster.on('TestStepStarted', (args) => {
158+
eventBroadcaster.on('TestStepStarted', async (args) => {
156159
if (!helper.isTestObservabilitySession()) {
157160
return;
158161
}
@@ -163,9 +166,10 @@ module.exports = {
163166
const pickleData = reportData.pickle.find((pickle) => pickle.id === pickleId);
164167
const testSteps = reportData.testCases.find((testCase) => testCase.id === testCaseId).testSteps;
165168
const testStepId = reportData.testStepStarted[args.envelope.testCaseStartedId].testStepId;
169+
await testObservability.sendHook(args, 'HookRunStarted', testSteps, testStepId, _tests[testCaseId]);
166170
const pickleStepId = testSteps.find((testStep) => testStep.id === testStepId).pickleStepId;
167-
if (pickleStepId && _tests['testStepId'] !== testStepId) {
168-
_tests['testStepId'] = testStepId;
171+
if (pickleStepId && _tests[testCaseId]?.['testStepId'] !== testStepId) {
172+
_tests[testCaseId]['testStepId'] = testStepId;
169173
const pickleStepData = pickleData.steps.find((pickle) => pickle.id === pickleStepId);
170174
const testMetaData = _tests[testCaseId] || {steps: []};
171175
if (testMetaData && !testMetaData.steps) {
@@ -190,12 +194,14 @@ module.exports = {
190194
}
191195
try {
192196
const reportData = args.report;
197+
helper.storeSessionsData(args);
193198
const testCaseId = _testCasesData[args.envelope.testCaseStartedId].testCaseId;
194199
const testStepFinished = reportData.testStepFinished[args.envelope.testCaseStartedId];
195200
const pickleId = reportData.testCases.find((testCase) => testCase.id === testCaseId).pickleId;
196201
const pickleData = reportData.pickle.find((pickle) => pickle.id === pickleId);
197202
const testSteps = reportData.testCases.find((testCase) => testCase.id === testCaseId).testSteps;
198203
const testStepId = reportData.testStepFinished[args.envelope.testCaseStartedId].testStepId;
204+
await testObservability.sendHook(args, 'HookRunFinished', testSteps, testStepId, _tests[testCaseId]);
199205
const pickleStepId = testSteps.find((testStep) => testStep.id === testStepId).pickleStepId;
200206
let failure;
201207
let failureType;
@@ -204,7 +210,7 @@ module.exports = {
204210
failureType = (testStepFinished.testStepResult?.exception === undefined) ? 'UnhandledError' : testStepFinished.testStepResult?.message;
205211
}
206212

207-
if (pickleStepId && _tests['testStepId']) {
213+
if (pickleStepId && _tests[testCaseId]['testStepId']) {
208214
const pickleStepData = pickleData.steps.find((pickle) => pickle.id === pickleStepId);
209215
const testMetaData = _tests[testCaseId] || {steps: []};
210216
if (!testMetaData.steps) {
@@ -229,7 +235,7 @@ module.exports = {
229235
});
230236
}
231237
_tests[testCaseId] = testMetaData;
232-
delete _tests['testStepId'];
238+
delete _tests[testCaseId]['testStepId'];
233239
if (testStepFinished.httpOutput && testStepFinished.httpOutput.length > 0) {
234240
for (const [index, output] of testStepFinished.httpOutput.entries()) {
235241
if (index % 2 === 0) {

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nightwatch/browserstack",
3-
"version": "3.2.2",
3+
"version": "3.2.3",
44
"description": "Nightwatch plugin for integration with browserstack.",
55
"main": "index.js",
66
"scripts": {

src/testObservability.js

Lines changed: 148 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const {makeRequest} = require('./utils/requestHelper');
88
const CrashReporter = require('./utils/crashReporter');
99
const Logger = require('./utils/logger');
1010
const {API_URL} = require('./utils/constants');
11+
const hooksMap = {};
1112

1213
class TestObservability {
1314
configure(settings = {}) {
@@ -414,9 +415,9 @@ class TestObservability {
414415

415416
try {
416417
if (eventType === 'TestRunFinished') {
417-
const currentSessionCapabilities = reportData.session[args.envelope.testCaseStartedId];
418-
if (currentSessionCapabilities.error) {
419-
throw new Error(`Error in driver capabilities: ${JSON.stringify(currentSessionCapabilities.error)}`);
418+
let currentSessionCapabilities = reportData.session[args.envelope.testCaseStartedId];
419+
if (currentSessionCapabilities === undefined || currentSessionCapabilities.error) {
420+
currentSessionCapabilities = helper.generateCapabilityDetails(args);
420421
}
421422

422423
const sessionCapabilities = currentSessionCapabilities.capabilities;
@@ -469,6 +470,14 @@ class TestObservability {
469470
}
470471
}
471472

473+
if (eventType === 'TestRunFinished') {
474+
const hooksList = this.getHooksListForTest(args);
475+
if (hooksList && hooksList.length > 0) {
476+
testData.hooks = hooksList;
477+
this.updateTestStatus(args, testData);
478+
}
479+
}
480+
472481
const uploadData = {
473482
event_type: eventType,
474483
test_run: testData
@@ -477,6 +486,142 @@ class TestObservability {
477486

478487
}
479488

489+
updateTestStatus(args, testData) {
490+
const testCaseStartedId = args.envelope.testCaseStartedId;
491+
const hookList = hooksMap[testCaseStartedId];
492+
if (hookList instanceof Array) {
493+
for (const hook of hookList) {
494+
if (hook.result === 'failed') {
495+
testData.result = hook.result;
496+
testData.failure = hook.failure_data;
497+
testData.failure_reason = (hook.failure_data instanceof Array) ? hook.failure_data[0]?.backtrace.join('\n') : '';
498+
testData.failure_type = hook.failure_type;
499+
500+
return testData;
501+
}
502+
}
503+
};
504+
}
505+
506+
getHooksListForTest(args) {
507+
const testCaseStartedId = args.envelope.testCaseStartedId;
508+
if (hooksMap[testCaseStartedId]) {
509+
return hooksMap[testCaseStartedId].map(hookDetail => hookDetail.uuid);
510+
}
511+
512+
return [];
513+
}
514+
515+
getHookRunEventData(args, eventType, hookData, testMetaData, hookType) {
516+
if (eventType === 'HookRunFinished') {
517+
const finishedAt = new Date().toISOString();
518+
const testCaseStartedId = args.envelope.testCaseStartedId;
519+
const hookList = hooksMap[testCaseStartedId];
520+
if (!hookList) {
521+
return;
522+
}
523+
524+
const hookEventData = hookList.find(hook => hook.uuid === hookData.id);
525+
if (!hookEventData) {
526+
return;
527+
}
528+
const result = this.getHookResult(args);
529+
hookEventData.result = result.status;
530+
hookEventData.finished_at = finishedAt;
531+
hookEventData.failure_type = result.failureType;
532+
hookEventData.failure_data = [{backtrace: result.failureData}];
533+
534+
return hookEventData;
535+
}
536+
const hookDetails = args.report.hooks.find(hookDetail => hookDetail.id === hookData.hookId);
537+
const relativeFilePath = hookDetails?.sourceReference?.uri;
538+
if (!relativeFilePath) {
539+
return;
540+
} else if (relativeFilePath.includes('setup_cucumber_runner')) {
541+
return;
542+
}
543+
const startedAt = new Date().toISOString();
544+
const result = 'pending';
545+
const hookTagsList = hookDetails.tagExpression ? hookDetails.tagExpression.split(' ').filter(val => val.includes('@')) : null;
546+
547+
const hookEventData = {
548+
uuid: hookData.id,
549+
type: 'hook',
550+
hook_type: hookType,
551+
name: hookDetails?.name || '',
552+
body: {
553+
lang: 'NodeJs',
554+
code: null
555+
},
556+
tags: hookTagsList,
557+
scope: testMetaData?.feature?.name,
558+
scopes: [testMetaData?.feature?.name || ''],
559+
file_name: relativeFilePath,
560+
location: relativeFilePath,
561+
vc_filepath: (this._gitMetadata && this._gitMetadata.root) ? path.relative(this._gitMetadata.root, relativeFilePath) : null,
562+
result: result.status,
563+
started_at: startedAt,
564+
framework: 'nightwatch'
565+
};
566+
567+
return hookEventData;
568+
}
569+
570+
async sendHook(args, eventType, testSteps, testStepId, testMetaData) {
571+
const hookData = testSteps.find((testStep) => testStep.id === testStepId);
572+
if (!hookData.hookId) {
573+
return;
574+
}
575+
const testCaseStartedId = args.envelope.testCaseStartedId;
576+
const hookType = this.getCucumberHookType(testSteps, hookData);
577+
const hookRunEvent = this.getHookRunEventData(args, eventType, hookData, testMetaData, hookType);
578+
if (!hookRunEvent) {
579+
return;
580+
}
581+
if (eventType === 'HookRunStarted') {
582+
if (hooksMap[testCaseStartedId]) {
583+
hooksMap[testCaseStartedId].push(hookRunEvent);
584+
} else {
585+
hooksMap[testCaseStartedId] = [hookRunEvent];
586+
}
587+
}
588+
const hookEventUploadData = {
589+
event_type: eventType,
590+
hook_run: hookRunEvent
591+
};
592+
await helper.uploadEventData(hookEventUploadData);
593+
}
594+
595+
getHookResult(args) {
596+
const testCaseStartedId = args.envelope.testCaseStartedId;
597+
const hookResult = args.report.testStepFinished[testCaseStartedId].testStepResult;
598+
let failure;
599+
let failureType;
600+
if (hookResult?.status.toString().toLowerCase() === 'failed') {
601+
failure = (hookResult?.exception === undefined) ? hookResult?.message : hookResult?.exception?.message;
602+
failureType = (hookResult?.exception === undefined) ? 'UnhandledError' : hookResult?.message.match(/Assert/) ? 'AssertionError' : 'UnhandledError';
603+
}
604+
605+
return {
606+
status: hookResult.status.toLowerCase(),
607+
failureType: failureType || null,
608+
failureData: (!failure) ? null : [failure]
609+
};
610+
}
611+
612+
// BEFORE_ALL and AFTER_ALL are not implemented for TO
613+
getCucumberHookType(testSteps, hookData) {
614+
let isStep = false;
615+
for (const step of testSteps) {
616+
if (step.pickleStepId) {
617+
isStep = true;
618+
}
619+
if (hookData.id === step.id) {
620+
return (isStep) ? 'AFTER_EACH' : 'BEFORE_EACH';
621+
}
622+
}
623+
}
624+
480625
async appendTestItemLog (log, testUuid) {
481626
try {
482627
if (testUuid) {

src/utils/helper.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const requestQueueHandler = require('./requestQueueHandler');
1313
const Logger = require('./logger');
1414
const LogPatcher = require('./logPatcher');
1515
const BSTestOpsPatcher = new LogPatcher({});
16+
const sessions = {};
1617

1718
console = {};
1819
Object.keys(consoleHolder).forEach(method => {
@@ -694,3 +695,59 @@ exports.getPlatformVersion = (driver) => {
694695

695696
return platformVersion;
696697
};
698+
699+
exports.generateCapabilityDetails = (args) => {
700+
if (!this.isUndefined(browser)) {
701+
return {
702+
host: browser.options.webdriver.host,
703+
port: browser.options.webdriver.port,
704+
capabilities: browser.capabilities,
705+
sessionId: browser.sessionId,
706+
testCaseStartedId: args.envelope.testCaseStartedId
707+
};
708+
}
709+
if (sessions[args.envelope.testCaseStartedId]) {
710+
return sessions[args.envelope.testCaseStartedId];
711+
}
712+
};
713+
714+
exports.storeSessionsData = (data) => {
715+
if (data.POST_SESSION_EVENT) {
716+
const sessionDetails = JSON.parse(data.POST_SESSION_EVENT);
717+
if (!sessionDetails.session) {
718+
return;
719+
}
720+
if (!Object.keys(sessions).includes(sessionDetails.session.testCaseStartedId)) {
721+
sessions[sessionDetails.session.testCaseStartedId] = sessionDetails.session;
722+
}
723+
} else {
724+
if (!data.report.session) {
725+
return;
726+
}
727+
728+
Object.keys(data.report.session).forEach(key => {
729+
if (!Object.keys(sessions).includes(key)) {
730+
sessions[key] = data.report.session[key];
731+
}
732+
});
733+
}
734+
};
735+
736+
exports.deepClone = (obj) => {
737+
if (obj === null || typeof obj !== 'object') {
738+
return obj;
739+
}
740+
741+
if (Array.isArray(obj)) {
742+
return obj.map(exports.deepClone);
743+
}
744+
745+
const cloned = {};
746+
for (const key in obj) {
747+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
748+
cloned[key] = exports.deepClone(obj[key]);
749+
}
750+
}
751+
752+
return cloned;
753+
};

0 commit comments

Comments
 (0)