Skip to content

Commit

Permalink
fix: Fix test steps when tests are running in parallel (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
sauravdas1997 committed Feb 27, 2024
1 parent f8c09f3 commit 140048e
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 8 deletions.
16 changes: 11 additions & 5 deletions nightwatch/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ module.exports = {

Object.values(workerList).forEach((worker) => {
worker.process.on('message', async (data) => {
if (data.POST_SESSION_EVENT) {
helper.storeSessionsData(data);
}
if (data.eventType === EVENTS.LOG_INIT) {
const testCaseStartedId = data.loggingData.message.replace('TEST-OBSERVABILITY-PID-TESTCASE-MAPPING-', '').slice(1, -1);
const testCaseId = _testCasesData[testCaseStartedId]?.testCaseId;
Expand Down Expand Up @@ -152,7 +155,7 @@ module.exports = {
}
});

eventBroadcaster.on('TestStepStarted', (args) => {
eventBroadcaster.on('TestStepStarted', async (args) => {
if (!helper.isTestObservabilitySession()) {
return;
}
Expand All @@ -163,9 +166,10 @@ module.exports = {
const pickleData = reportData.pickle.find((pickle) => pickle.id === pickleId);
const testSteps = reportData.testCases.find((testCase) => testCase.id === testCaseId).testSteps;
const testStepId = reportData.testStepStarted[args.envelope.testCaseStartedId].testStepId;
await testObservability.sendHook(args, 'HookRunStarted', testSteps, testStepId, _tests[testCaseId]);
const pickleStepId = testSteps.find((testStep) => testStep.id === testStepId).pickleStepId;
if (pickleStepId && _tests['testStepId'] !== testStepId) {
_tests['testStepId'] = testStepId;
if (pickleStepId && _tests[testCaseId]?.['testStepId'] !== testStepId) {
_tests[testCaseId]['testStepId'] = testStepId;
const pickleStepData = pickleData.steps.find((pickle) => pickle.id === pickleStepId);
const testMetaData = _tests[testCaseId] || {steps: []};
if (testMetaData && !testMetaData.steps) {
Expand All @@ -190,12 +194,14 @@ module.exports = {
}
try {
const reportData = args.report;
helper.storeSessionsData(args);
const testCaseId = _testCasesData[args.envelope.testCaseStartedId].testCaseId;
const testStepFinished = reportData.testStepFinished[args.envelope.testCaseStartedId];
const pickleId = reportData.testCases.find((testCase) => testCase.id === testCaseId).pickleId;
const pickleData = reportData.pickle.find((pickle) => pickle.id === pickleId);
const testSteps = reportData.testCases.find((testCase) => testCase.id === testCaseId).testSteps;
const testStepId = reportData.testStepFinished[args.envelope.testCaseStartedId].testStepId;
await testObservability.sendHook(args, 'HookRunFinished', testSteps, testStepId, _tests[testCaseId]);
const pickleStepId = testSteps.find((testStep) => testStep.id === testStepId).pickleStepId;
let failure;
let failureType;
Expand All @@ -204,7 +210,7 @@ module.exports = {
failureType = (testStepFinished.testStepResult?.exception === undefined) ? 'UnhandledError' : testStepFinished.testStepResult?.message;
}

if (pickleStepId && _tests['testStepId']) {
if (pickleStepId && _tests[testCaseId]['testStepId']) {
const pickleStepData = pickleData.steps.find((pickle) => pickle.id === pickleStepId);
const testMetaData = _tests[testCaseId] || {steps: []};
if (!testMetaData.steps) {
Expand All @@ -229,7 +235,7 @@ module.exports = {
});
}
_tests[testCaseId] = testMetaData;
delete _tests['testStepId'];
delete _tests[testCaseId]['testStepId'];
if (testStepFinished.httpOutput && testStepFinished.httpOutput.length > 0) {
for (const [index, output] of testStepFinished.httpOutput.entries()) {
if (index % 2 === 0) {
Expand Down
151 changes: 148 additions & 3 deletions src/testObservability.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const {makeRequest} = require('./utils/requestHelper');
const CrashReporter = require('./utils/crashReporter');
const Logger = require('./utils/logger');
const {API_URL} = require('./utils/constants');
const hooksMap = {};

class TestObservability {
configure(settings = {}) {
Expand Down Expand Up @@ -414,9 +415,9 @@ class TestObservability {

try {
if (eventType === 'TestRunFinished') {
const currentSessionCapabilities = reportData.session[args.envelope.testCaseStartedId];
if (currentSessionCapabilities.error) {
throw new Error(`Error in driver capabilities: ${JSON.stringify(currentSessionCapabilities.error)}`);
let currentSessionCapabilities = reportData.session[args.envelope.testCaseStartedId];
if (currentSessionCapabilities === undefined || currentSessionCapabilities.error) {
currentSessionCapabilities = helper.generateCapabilityDetails(args);
}

const sessionCapabilities = currentSessionCapabilities.capabilities;
Expand Down Expand Up @@ -469,6 +470,14 @@ class TestObservability {
}
}

if (eventType === 'TestRunFinished') {
const hooksList = this.getHooksListForTest(args);
if (hooksList && hooksList.length > 0) {
testData.hooks = hooksList;
this.updateTestStatus(args, testData);
}
}

const uploadData = {
event_type: eventType,
test_run: testData
Expand All @@ -477,6 +486,142 @@ class TestObservability {

}

updateTestStatus(args, testData) {
const testCaseStartedId = args.envelope.testCaseStartedId;
const hookList = hooksMap[testCaseStartedId];
if (hookList instanceof Array) {
for (const hook of hookList) {
if (hook.result === 'failed') {
testData.result = hook.result;
testData.failure = hook.failure_data;
testData.failure_reason = (hook.failure_data instanceof Array) ? hook.failure_data[0]?.backtrace.join('\n') : '';
testData.failure_type = hook.failure_type;

return testData;
}
}
};
}

getHooksListForTest(args) {
const testCaseStartedId = args.envelope.testCaseStartedId;
if (hooksMap[testCaseStartedId]) {
return hooksMap[testCaseStartedId].map(hookDetail => hookDetail.uuid);
}

return [];
}

getHookRunEventData(args, eventType, hookData, testMetaData, hookType) {
if (eventType === 'HookRunFinished') {
const finishedAt = new Date().toISOString();
const testCaseStartedId = args.envelope.testCaseStartedId;
const hookList = hooksMap[testCaseStartedId];
if (!hookList) {
return;
}

const hookEventData = hookList.find(hook => hook.uuid === hookData.id);
if (!hookEventData) {
return;
}
const result = this.getHookResult(args);
hookEventData.result = result.status;
hookEventData.finished_at = finishedAt;
hookEventData.failure_type = result.failureType;
hookEventData.failure_data = [{backtrace: result.failureData}];

return hookEventData;
}
const hookDetails = args.report.hooks.find(hookDetail => hookDetail.id === hookData.hookId);
const relativeFilePath = hookDetails?.sourceReference?.uri;
if (!relativeFilePath) {
return;
} else if (relativeFilePath.includes('setup_cucumber_runner')) {
return;
}
const startedAt = new Date().toISOString();
const result = 'pending';
const hookTagsList = hookDetails.tagExpression ? hookDetails.tagExpression.split(' ').filter(val => val.includes('@')) : null;

const hookEventData = {
uuid: hookData.id,
type: 'hook',
hook_type: hookType,
name: hookDetails?.name || '',
body: {
lang: 'NodeJs',
code: null
},
tags: hookTagsList,
scope: testMetaData?.feature?.name,
scopes: [testMetaData?.feature?.name || ''],
file_name: relativeFilePath,
location: relativeFilePath,
vc_filepath: (this._gitMetadata && this._gitMetadata.root) ? path.relative(this._gitMetadata.root, relativeFilePath) : null,
result: result.status,
started_at: startedAt,
framework: 'nightwatch'
};

return hookEventData;
}

async sendHook(args, eventType, testSteps, testStepId, testMetaData) {
const hookData = testSteps.find((testStep) => testStep.id === testStepId);
if (!hookData.hookId) {
return;
}
const testCaseStartedId = args.envelope.testCaseStartedId;
const hookType = this.getCucumberHookType(testSteps, hookData);
const hookRunEvent = this.getHookRunEventData(args, eventType, hookData, testMetaData, hookType);
if (!hookRunEvent) {
return;
}
if (eventType === 'HookRunStarted') {
if (hooksMap[testCaseStartedId]) {
hooksMap[testCaseStartedId].push(hookRunEvent);
} else {
hooksMap[testCaseStartedId] = [hookRunEvent];
}
}
const hookEventUploadData = {
event_type: eventType,
hook_run: hookRunEvent
};
await helper.uploadEventData(hookEventUploadData);
}

getHookResult(args) {
const testCaseStartedId = args.envelope.testCaseStartedId;
const hookResult = args.report.testStepFinished[testCaseStartedId].testStepResult;
let failure;
let failureType;
if (hookResult?.status.toString().toLowerCase() === 'failed') {
failure = (hookResult?.exception === undefined) ? hookResult?.message : hookResult?.exception?.message;
failureType = (hookResult?.exception === undefined) ? 'UnhandledError' : hookResult?.message.match(/Assert/) ? 'AssertionError' : 'UnhandledError';
}

return {
status: hookResult.status.toLowerCase(),
failureType: failureType || null,
failureData: (!failure) ? null : [failure]
};
}

// BEFORE_ALL and AFTER_ALL are not implemented for TO
getCucumberHookType(testSteps, hookData) {
let isStep = false;
for (const step of testSteps) {
if (step.pickleStepId) {
isStep = true;
}
if (hookData.id === step.id) {
return (isStep) ? 'AFTER_EACH' : 'BEFORE_EACH';
}
}
}

async appendTestItemLog (log, testUuid) {
try {
if (testUuid) {
Expand Down
57 changes: 57 additions & 0 deletions src/utils/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const requestQueueHandler = require('./requestQueueHandler');
const Logger = require('./logger');
const LogPatcher = require('./logPatcher');
const BSTestOpsPatcher = new LogPatcher({});
const sessions = {};

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

return platformVersion;
};

exports.generateCapabilityDetails = (args) => {
if (!this.isUndefined(browser)) {
return {
host: browser.options.webdriver.host,
port: browser.options.webdriver.port,
capabilities: browser.capabilities,
sessionId: browser.sessionId,
testCaseStartedId: args.envelope.testCaseStartedId
};
}
if (sessions[args.envelope.testCaseStartedId]) {
return sessions[args.envelope.testCaseStartedId];
}
};

exports.storeSessionsData = (data) => {
if (data.POST_SESSION_EVENT) {
const sessionDetails = JSON.parse(data.POST_SESSION_EVENT);
if (!sessionDetails.session) {
return;
}
if (!Object.keys(sessions).includes(sessionDetails.session.testCaseStartedId)) {
sessions[sessionDetails.session.testCaseStartedId] = sessionDetails.session;
}
} else {
if (!data.report.session) {
return;
}

Object.keys(data.report.session).forEach(key => {
if (!Object.keys(sessions).includes(key)) {
sessions[key] = data.report.session[key];
}
});
}
};

exports.deepClone = (obj) => {
if (obj === null || typeof obj !== 'object') {
return obj;
}

if (Array.isArray(obj)) {
return obj.map(exports.deepClone);
}

const cloned = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloned[key] = exports.deepClone(obj[key]);
}
}

return cloned;
};

0 comments on commit 140048e

Please sign in to comment.