Skip to content

Commit

Permalink
EPMRPP-88826 || Fix reporting of feature(suite) for parallel execution (
Browse files Browse the repository at this point in the history
  • Loading branch information
AliakseiLiasnitski authored May 10, 2024
1 parent f02c7e4 commit f5b2af5
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 126 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
### Fixed
- Reporting of feature (suite) for parallel execution [#142](https://github.com/reportportal/agent-js-cucumber/issues/142).

## [5.3.1] - 2024-04-30
### Security
Expand Down
77 changes: 40 additions & 37 deletions modules/cucumber-reportportal-formatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const createRPFormatterClass = (config) =>
}

onGherkinDocumentEvent(data) {
this.storage.setDocument(data);
this.storage.setFeature(data.uri, data.feature);
this.storage.setAstNodesData(data, utils.findAstNodesData(data.feature.children));
}

Expand Down Expand Up @@ -146,6 +146,33 @@ const createRPFormatterClass = (config) =>
this.storage.setSteps(testCaseId, stepsMap);
}

startFeature({ pickleFeatureUri, feature }) {
if (this.storage.getFeatureTempId(pickleFeatureUri)) return;

const launchTempId = this.storage.getLaunchTempId();
const suiteData = {
name: `${feature.keyword}: ${feature.name}`,
startTime: this.reportportal.helpers.now(),
type: this.isScenarioBasedStatistics ? TEST_ITEM_TYPES.TEST : TEST_ITEM_TYPES.SUITE,
description: (feature.description || '').trim(),
attributes: utils.createAttributes(feature.tags),
codeRef: utils.formatCodeRef(pickleFeatureUri, feature.name),
};

const { tempId } = this.reportportal.startTestItem(suiteData, launchTempId);
this.storage.updateFeature(pickleFeatureUri, { tempId });
}

finishFeature(pickleFeatureUri) {
const { tempId, endTime } = this.storage.getFeature(pickleFeatureUri);

this.reportportal.finishTestItem(tempId, {
endTime: endTime || this.reportportal.helpers.now(),
});

this.storage.deleteFeature(pickleFeatureUri);
}

onTestCaseStartedEvent(data) {
const { id, testCaseId, attempt } = data;
this.storage.setTestCaseStartedId(id, testCaseId);
Expand All @@ -155,36 +182,10 @@ const createRPFormatterClass = (config) =>
uri: pickleFeatureUri,
astNodeIds: [scenarioId, parametersId],
} = this.storage.getPickle(pickleId);
const currentFeatureUri = this.storage.getCurrentFeatureUri();
const feature = this.storage.getFeature(pickleFeatureUri);
const launchTempId = this.storage.getLaunchTempId();
const isNeedToStartFeature = currentFeatureUri !== pickleFeatureUri;

// start FEATURE if no currentFeatureUri or new feature
// else finish old one
const featureCodeRef = utils.formatCodeRef(pickleFeatureUri, feature.name);
if (isNeedToStartFeature) {
const isFirstFeatureInLaunch = currentFeatureUri === null;
const suiteData = {
name: `${feature.keyword}: ${feature.name}`,
startTime: this.reportportal.helpers.now(),
type: this.isScenarioBasedStatistics ? TEST_ITEM_TYPES.TEST : TEST_ITEM_TYPES.SUITE,
description: (feature.description || '').trim(),
attributes: utils.createAttributes(feature.tags),
codeRef: featureCodeRef,
};

if (!isFirstFeatureInLaunch) {
const previousFeatureTempId = this.storage.getFeatureTempId();
this.reportportal.finishTestItem(previousFeatureTempId, {
endTime: this.reportportal.helpers.now(),
});
}

this.storage.setCurrentFeatureUri(pickleFeatureUri);
const { tempId } = this.reportportal.startTestItem(suiteData, launchTempId, '');
this.storage.setFeatureTempId(tempId);
}
this.startFeature({ pickleFeatureUri, feature });

// current feature node rule(this entity is for grouping several
// scenarios in one logical block) || scenario
Expand All @@ -209,7 +210,7 @@ const createRPFormatterClass = (config) =>
attributes: utils.createAttributes(tags),
codeRef: currentNodeCodeRef,
};
const parentId = this.storage.getFeatureTempId();
const parentId = this.storage.getFeatureTempId(pickleFeatureUri);
const { tempId } = this.reportportal.startTestItem(testData, launchTempId, parentId);
ruleTempId = tempId;

Expand Down Expand Up @@ -273,7 +274,7 @@ const createRPFormatterClass = (config) =>
});
testData.parameters = this.storage.getParameters(parametersId);
}
const parentId = ruleTempId || this.storage.getFeatureTempId();
const parentId = ruleTempId || this.storage.getFeatureTempId(pickleFeatureUri);
const { tempId } = this.reportportal.startTestItem(testData, launchTempId, parentId);
this.storage.setScenarioTempId(testCaseId, tempId);
this.storage.updateTestCase(testCaseId, {
Expand All @@ -289,7 +290,7 @@ const createRPFormatterClass = (config) =>

// start step
if (step) {
const currentFeatureUri = this.storage.getCurrentFeatureUri();
const currentFeatureUri = (this.storage.getPickle(testCase.pickleId) || {}).uri;
const astNodesData = this.storage.getAstNodesData(currentFeatureUri);

const { text: stepName, type, astNodeIds } = step;
Expand Down Expand Up @@ -423,6 +424,7 @@ const createRPFormatterClass = (config) =>
onTestStepFinishedEvent(data) {
const { testCaseStartedId, testStepId, testStepResult } = data;
const testCaseId = this.storage.getTestCaseId(testCaseStartedId);
const testCase = this.storage.getTestCase(testCaseId);
const step = this.storage.getStep(testCaseId, testStepId);
const tempStepId = this.storage.getStepTempId(testStepId);
let status;
Expand Down Expand Up @@ -476,7 +478,7 @@ const createRPFormatterClass = (config) =>
this.config.takeScreenshot && this.config.takeScreenshot === 'onFailure';

if (isBrowserAvailable && isTakeScreenshotOptionProvidedInRPConfig) {
const currentFeatureUri = this.storage.getCurrentFeatureUri();
const currentFeatureUri = (this.storage.getPickle(testCase.pickleId) || {}).uri;
const astNodesData = this.storage.getAstNodesData(currentFeatureUri);
const screenshotName = utils.getScreenshotName(astNodesData, step.astNodeIds);

Expand Down Expand Up @@ -575,12 +577,15 @@ const createRPFormatterClass = (config) =>
this.storage.removeTestCase(testCaseId);
this.storage.removeScenarioTempId(testCaseStartedId);
}

const { uri: pickleFeatureUri } = this.storage.getPickle(testCase.pickleId);
this.storage.updateFeature(pickleFeatureUri, { endTime: this.reportportal.helpers.now() });
}

onTestRunFinishedEvent() {
const featureTempId = this.storage.getFeatureTempId();
this.reportportal.finishTestItem(featureTempId, {
endTime: this.reportportal.helpers.now(),
const featureUris = this.storage.getActiveFeatureUris();
featureUris.forEach((featureUri) => {
this.finishFeature(featureUri);
});

const launchId = this.storage.getLaunchTempId();
Expand All @@ -589,8 +594,6 @@ const createRPFormatterClass = (config) =>
...(this.customLaunchStatus && { status: this.customLaunchStatus }),
});
this.storage.setLaunchTempId(null);
this.storage.setCurrentFeatureUri(null);
this.storage.setFeatureTempId(null);
this.customLaunchStatus = null;
});
}
Expand Down
52 changes: 23 additions & 29 deletions modules/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@
module.exports = class Storage {
constructor() {
this.launchTempId = null;
this.currentFeatureUri = null;
this.featureTempId = null;
this.documents = new Map();
this.features = new Map();
this.pickles = new Map();
this.hooks = new Map();
this.testCases = new Map();
Expand All @@ -43,28 +41,6 @@ module.exports = class Storage {
return this.launchTempId;
}

getCurrentFeatureUri() {
return this.currentFeatureUri;
}

setCurrentFeatureUri(value) {
this.currentFeatureUri = value;
}

setDocument(gherkinDocument) {
this.documents.set(gherkinDocument.uri, gherkinDocument);
}

getDocument(uri) {
return this.documents.get(uri);
}

getFeature(uri) {
const document = this.getDocument(uri);

return document && document.feature;
}

setPickle(pickle) {
this.pickles.set(pickle.id, pickle);
}
Expand Down Expand Up @@ -139,12 +115,30 @@ module.exports = class Storage {
return this.parameters.get(id);
}

setFeatureTempId(value) {
this.featureTempId = value;
updateFeature(id, newData) {
const feature = this.features.get(id) || {};
this.features.set(id, { ...feature, ...newData });
}

getFeature(id) {
return this.features.get(id);
}

setFeature(id, feature) {
this.features.set(id, feature);
}

deleteFeature(id) {
this.features.delete(id);
}

getFeatureTempId(id) {
const feature = this.features.get(id);
return feature && feature.tempId;
}

getFeatureTempId() {
return this.featureTempId;
getActiveFeatureUris() {
return Array.from(this.features.keys());
}

setScenarioTempId(testCaseStartedId, scenarioTempId) {
Expand Down
82 changes: 36 additions & 46 deletions tests/cucumber-reportportal-formatter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ const {
pickle,
pickleWithParameters,
testCaseWithParameters,
ruleId,
ruleTempId,
hook,
testCase,
testCaseStarted,
Expand All @@ -38,6 +36,8 @@ const {
feature,
scenario,
step,
launchTempId,
featureTempId,
} = require('./data');
const {
STATUSES,
Expand Down Expand Up @@ -179,8 +179,8 @@ describe('cucumber-reportportal-formatter', () => {
beforeEach(() => {
formatter.onGherkinDocumentEvent(gherkinDocument);
});
it('should set document to storage', () => {
expect(formatter.storage.getDocument(uri)).toBe(gherkinDocument);
it('should set feature to storage', () => {
expect(formatter.storage.getFeature(uri)).toBe(gherkinDocument.feature);
});

it('should set document feature.children', () => {
Expand Down Expand Up @@ -272,46 +272,6 @@ describe('cucumber-reportportal-formatter', () => {
expect(formatter.storage.getTestCaseId(testCaseStarted.id)).toBe(testCase.id);
});

it('should start FEATURE if no currentFeatureUri or new feature', () => {
formatter.onTestCaseStartedEvent(testCaseStarted);

expect(formatter.storage.getFeatureTempId()).toBe('testItemId');
});

it('should finish previous FEATURE if currentFeatureUri is different from pickleFeatureUri', () => {
const finishTestItem = jest.spyOn(formatter.reportportal, 'finishTestItem');
const tempFeatureId = 'tempFeatureId';
formatter.storage.setFeatureTempId(tempFeatureId);
formatter.storage.setCurrentFeatureUri('currentFeatureUri');

formatter.onTestCaseStartedEvent(testCaseStarted);

expect(finishTestItem).toHaveBeenCalledWith(tempFeatureId, {
endTime: mockedDate,
});
});

it('should not finish FEATURE if pickleFeatureUri the same as currentFeatureUrl', () => {
const finishTestItem = jest.spyOn(formatter.reportportal, 'finishTestItem');
const tempFeatureId = 'tempFeatureId';
formatter.storage.setFeatureTempId(tempFeatureId);
formatter.storage.setCurrentFeatureUri(pickle.uri);

formatter.onTestCaseStartedEvent(testCaseStarted);

expect(finishTestItem).not.toHaveBeenCalled();
});

it('should start new FEATURE if it is first feature in this launch', () => {
const finishTestItem = jest.spyOn(formatter.reportportal, 'finishTestItem');
const tempFeatureId = 'tempFeatureId';
formatter.storage.setFeatureTempId(tempFeatureId);

formatter.onTestCaseStartedEvent(testCaseStarted);

expect(finishTestItem).not.toHaveBeenCalled();
});

it('start scenario flow', () => {
formatter.onTestCaseStartedEvent(testCaseStarted);

Expand Down Expand Up @@ -832,6 +792,36 @@ describe('cucumber-reportportal-formatter', () => {
});
});

describe('startFeature', () => {
it('startTestItem should be called', () => {
const spyStartTestItem = jest.spyOn(formatter.reportportal, 'startTestItem');
formatter.storage.setLaunchTempId(launchTempId);

formatter.startFeature({ pickleFeatureUri: uri, feature });

expect(spyStartTestItem).lastCalledWith(
{
name: `Feature: ${feature.name}`,
startTime: mockedDate,
type: 'SUITE',
codeRef: `${uri}/${feature.name}`,
attributes: [],
description: '',
},
launchTempId,
);
});

it('should be skipped if suite is already running', () => {
const spyStartTestItem = jest.spyOn(formatter.reportportal, 'startTestItem');
formatter.storage.setFeature(uri, { tempId: featureTempId });

formatter.startFeature({ pickleFeatureUri: uri, feature });

expect(spyStartTestItem).not.toHaveBeenCalled();
});
});

describe('onTestRunFinishedEvent', () => {
beforeEach(() => {
formatter.onGherkinDocumentEvent(gherkinDocument);
Expand Down Expand Up @@ -859,8 +849,8 @@ describe('cucumber-reportportal-formatter', () => {
expect(spyGetPromiseFinishAllItems).toBeCalledWith('tempLaunchId');

expect(formatter.storage.getLaunchTempId()).toBeNull();
expect(formatter.storage.getCurrentFeatureUri()).toBeNull();
expect(formatter.storage.getFeatureTempId()).toBeNull();
expect(formatter.storage.getActiveFeatureUris().length).toBe(0);
expect(formatter.storage.getFeatureTempId(uri)).toBeUndefined();
});
});
});
Loading

0 comments on commit f5b2af5

Please sign in to comment.