From 2066a71cd6e1fa0ac87b5d70d53909109a1d26a3 Mon Sep 17 00:00:00 2001 From: Dzmitry Kosarau Date: Tue, 10 Oct 2023 17:26:43 +0500 Subject: [PATCH] EPMRPP-78272 || Implement attaching data using attach --- .eslintrc | 5 +- .prettierignore | 1 + CHANGELOG.md | 2 + README.md | 63 ++++++++ package-lock.json | 137 ++++++++++++++++-- package.json | 4 +- .../reporter/finishTestItemReporting.spec.ts | 93 +++++++++++- src/__tests__/utils.spec.ts | 137 ++++++++++++++++++ src/constants/index.ts | 1 + src/constants/testInfo.ts | 23 +++ src/index.ts | 1 + src/models/reporting.ts | 14 ++ src/reporter.ts | 20 ++- src/utils.ts | 41 +++++- 14 files changed, 518 insertions(+), 24 deletions(-) create mode 100644 .prettierignore create mode 100644 src/constants/testInfo.ts diff --git a/.eslintrc b/.eslintrc index dbb4ec4..a61a91f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -18,7 +18,7 @@ "parserOptions": { "ecmaVersion": 2018, "sourceType": "module", - "project": "./tsconfig.eslint.json" + "project": "./tsconfig.eslint.json" }, "rules": { "no-plusplus": 0, @@ -33,6 +33,7 @@ "@typescript-eslint/ban-ts-comment": 0, "@typescript-eslint/no-implied-eval": 0, "dot-notation": 0, - "@typescript-eslint/naming-convention": 0 + "@typescript-eslint/naming-convention": 0, + "consistent-return": "warn" } } diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..dd44972 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +*.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f0ca94b..fe1e124 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +### Added +- Add ability to provide `attributes`,`status`,`description`,`testCaseId` for test using `testInfo.attach` ## [5.1.4] - 2023-10-05 ## Changed diff --git a/README.md b/README.md index 444e6ac..ca66321 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,69 @@ test('basic test', async ({ page }, testInfo) => { }); ``` +Also, we can attach `attributes`,`status`,`description`,`testCaseId` + +`Description` +```typescript +import { test, expect } from '@playwright/test'; +import { RPTestInfo } from '@reportportal/agent-js-playwright' + +test('basic test', async ({ page }, testInfo) => { + await testInfo.attach(RPTestInfo.description, { body: 'Description', contentType: 'plain/text' }); + + expect(true).toBe(true); +}); +``` +Description provided this way will be merged with description provided by `ReportingAPI`. + +`Attributes` +```typescript +import { test, expect } from '@playwright/test'; +import { RPTestInfo } from '@reportportal/agent-js-playwright' + +test('basic test', async ({ page }, testInfo) => { + await testInfo.attach(RPTestInfo.status, { body: JSON.stringify([ + { + key: 'testKey', + value: 'testValue', + }, + { + value: 'testValueTwo', + } + ]), + contentType: 'application/json' }); + + expect(true).toBe(true); +}); +``` +Attributes provided this way will be merged with Attributes provided by `ReportingAPI`. + +`Status` +```typescript +import { test, expect } from '@playwright/test'; +import { RPTestInfo } from '@reportportal/agent-js-playwright' + +test('basic test', async ({ page }, testInfo) => { + await testInfo.attach(RPTestInfo.status, { body: 'passed', contentType:'text/plain'}) + + expect(true).toBe(true); +}); +``` +Status provided this way will be replaced by status provided by `ReportingAPI`. You can provide as many statuses as you want, but only the last will be applied. + +`testCaseId` +```typescript +import { test, expect } from '@playwright/test'; +import { RPTestInfo } from '@reportportal/agent-js-playwright' + +test('basic test', async ({ page }, testInfo) => { + await testInfo.attach(RPTestInfo.testCaseId, { body: 'testCaseId', contentType:'text/plain'}) + + expect(true).toBe(true); +}); +``` +TestCaseId provided this way will be replaced by testCaseId provided by `ReportingAPI`. + *Note:* attachment path can be provided instead of body. As an alternative to this approach the [`ReportingAPI`](#log) methods can be used. diff --git a/package-lock.json b/package-lock.json index ba2c436..0121a4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "@reportportal/agent-js-playwright", - "version": "5.1.3", + "version": "5.1.4", "license": "Apache-2.0", "dependencies": { "@reportportal/client-javascript": "^5.0.14", @@ -26,6 +26,7 @@ "eslint-plugin-import": "^2.27.5", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.4.1", + "examples": "../examples-js/example-playwright", "jest": "^27.5.1", "prettier": "^2.8.4", "rimraf": "^3.0.2", @@ -36,6 +37,101 @@ "node": ">=14.x" } }, + "../bigpanda/automation-tests": { + "name": "@bp/automation-tests", + "version": "1.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "@currents/playwright": "0.6.2", + "@datadog/datadog-ci": "2.4.0", + "@playwright/test": "1.35.0", + "@reportportal/agent-js-playwright": "../../agent-js-playwright", + "@slack/types": "2.8.0", + "@slack/web-api": "6.8.1", + "axios": "1.4.0", + "dotenv": "16.0.3", + "eslint-plugin-playwright": "0.11.2", + "monocart-reporter": "1.7.1", + "playwright-slack-report": "1.1.16", + "typescript": "4.9.4", + "uuid": "9.0.0" + }, + "devDependencies": { + "@deploysentinel/playwright": "0.3.4" + } + }, + "../bigpanda/automation-tests_second": { + "name": "@bp/automation-tests", + "version": "1.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "@currents/playwright": "0.6.2", + "@playwright/test": "1.35.0", + "@reportportal/agent-js-playwright": "../../agent-js-playwright", + "@slack/types": "2.8.0", + "@slack/web-api": "^6.8.1", + "axios": "1.4.0", + "dotenv": "16.0.3", + "eslint-plugin-playwright": "0.11.2", + "monocart-reporter": "1.7.1", + "playwright-slack-report": "1.1.16", + "tsc": "^2.0.4", + "typescript": "4.9.4", + "uuid": "9.0.0" + } + }, + "../client-javascript": { + "name": "@reportportal/client-javascript", + "version": "5.0.13", + "extraneous": true, + "license": "Apache-2.0", + "dependencies": { + "axios": "^0.27.2", + "axios-retry": "^3.4.0", + "glob": "^7.2.3", + "ini": "^2.0.0", + "uniqid": "^5.4.0", + "uuid": "^9.0.0" + }, + "devDependencies": { + "@types/jasmine": "^4.3.1", + "@types/node": "^18.14.2", + "@typescript-eslint/eslint-plugin": "^5.54.0", + "@typescript-eslint/parser": "^5.54.0", + "eslint": "^7.32.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-config-prettier": "^8.6.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^4.2.1", + "jasmine": "^3.10.0", + "jasmine-ts": "^0.4.0", + "lodash": "^4.17.21", + "nock": "^13.3.0", + "nyc": "^15.1.0", + "prettier": "^2.8.4", + "rimraf": "^3.0.2", + "ts-node": "^10.9.1", + "typescript": "^4.9.5" + }, + "engines": { + "node": ">=10.x" + } + }, + "../examples-js/example-playwright": { + "dev": true, + "dependencies": { + "playwright": "^1.31.2" + }, + "devDependencies": { + "@axe-core/playwright": "^4.7.2", + "@playwright/test": "1.32.3", + "@reportportal/agent-js-playwright": "../../agent-js-playwright" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "dev": true, @@ -530,10 +626,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.22.5", - "license": "MIT", + "version": "7.23.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz", + "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -1554,8 +1651,9 @@ } }, "node_modules/axios-retry": { - "version": "3.5.0", - "license": "Apache-2.0", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.8.0.tgz", + "integrity": "sha512-CfIsQyWNc5/AE7x/UEReRUadiBmQeoBpSEC+4QyGLJMswTsP1tz0GW2YYPnE7w9+ESMef5zOgLDFpHynNyEZ1w==", "dependencies": { "@babel/runtime": "^7.15.4", "is-retry-allowed": "^2.2.0" @@ -2714,6 +2812,10 @@ "node": ">=0.10.0" } }, + "node_modules/examples": { + "resolved": "../examples-js/example-playwright", + "link": true + }, "node_modules/execa": { "version": "5.1.1", "dev": true, @@ -3302,7 +3404,8 @@ }, "node_modules/ini": { "version": "2.0.0", - "license": "ISC", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", "engines": { "node": ">=10" } @@ -3490,7 +3593,8 @@ }, "node_modules/is-retry-allowed": { "version": "2.2.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", "engines": { "node": ">=10" }, @@ -4959,8 +5063,9 @@ "license": "MIT" }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "license": "MIT" + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", @@ -5691,7 +5796,8 @@ }, "node_modules/uniqid": { "version": "5.4.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/uniqid/-/uniqid-5.4.0.tgz", + "integrity": "sha512-38JRbJ4Fj94VmnC7G/J/5n5SC7Ab46OM5iNtSstB/ko3l1b5g7ALt4qzHFgGciFkyiRNtDXtLNb+VsxtMSE77A==" }, "node_modules/universalify": { "version": "0.2.0", @@ -5744,8 +5850,13 @@ } }, "node_modules/uuid": { - "version": "9.0.0", - "license": "MIT", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } diff --git a/package.json b/package.json index b822edd..389ec7f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "lint": "eslint \"src/**/*.ts\"", "format": "npm run lint -- --fix", "test": "jest", - "test:coverage": "jest --coverage" + "test:coverage": "jest --coverage", + "examples": "npm run build && npm explore examples -- npm run test:basic" }, "dependencies": { "@reportportal/client-javascript": "^5.0.14", @@ -21,6 +22,7 @@ ], "devDependencies": { "@playwright/test": "^1.37.1", + "examples": "../examples-js/example-playwright", "@types/jest": "^27.5.2", "@types/node": "^18.14.2", "@typescript-eslint/eslint-plugin": "^4.33.0", diff --git a/src/__tests__/reporter/finishTestItemReporting.spec.ts b/src/__tests__/reporter/finishTestItemReporting.spec.ts index 46ed03a..8911e6c 100644 --- a/src/__tests__/reporter/finishTestItemReporting.spec.ts +++ b/src/__tests__/reporter/finishTestItemReporting.spec.ts @@ -18,7 +18,7 @@ import { RPReporter } from '../../reporter'; import { mockConfig } from '../mocks/configMock'; import { RPClientMock } from '../mocks/RPClientMock'; import { FinishTestItemObjType } from '../../models'; -import { STATUSES } from '../../constants'; +import { RPTestInfo, STATUSES } from '../../constants'; const rootSuite = 'rootSuite'; const suiteName = 'suiteName'; @@ -27,6 +27,8 @@ describe('finish test reporting', () => { const testCase = { title: 'testTitle', id: 'testItemId', + //@ts-ignore + results: [{ attachments: [] }], parent: { title: rootSuite, project: () => ({ name: rootSuite }), @@ -189,4 +191,93 @@ describe('finish test reporting', () => { expect(reporter.client.finishTestItem).toHaveBeenCalledWith('1214r1', finishStepObject); }); + + test('client.finishTestItem should call reporter.client.finishTestItem with correct values', async () => { + const result = { status: 'passed' }; + + reporter.testItems = new Map([ + [ + `${testCase.id}`, + { + id: 'testItemId', + name: 'name', + description: 'savedTestItemDescription', + status: STATUSES.PASSED, + attributes: [ + { value: 'savedTestItemAttrValue' }, + { key: 'savedTestItemAttrKey', value: 'savedTestItemAttrValue', system: false }, + ], + }, + ], + ]); + + await reporter.onTestEnd( + { + ...testCase, + outcome: () => 'expected', + results: [ + // @ts-ignore + { + attachments: [ + { + name: RPTestInfo.attributes, + contentType: 'application/json', + body: Buffer.from( + JSON.stringify([ + { key: 'key1', value: 'value1', system: false }, + { key: 'key2', value: 'value2', system: false }, + ]), + ), + }, + { + name: RPTestInfo.description, + contentType: 'plain/text', + body: Buffer.from('Description'), + }, + { + name: RPTestInfo.status, + contentType: 'plain/text', + body: Buffer.from('skipped'), + }, + { + name: RPTestInfo.status, + contentType: 'plain/text', + body: Buffer.from('interrupted'), + }, + { + name: RPTestInfo.testCaseId, + contentType: 'plain/text', + body: Buffer.from('testCaseId'), + }, + { + name: 'notAllowedField', + contentType: 'plain/text', + body: Buffer.from('notAllowedValue'), + }, + ], + }, + ], + }, + result, + ); + + const finishStepObject: FinishTestItemObjType = { + endTime: reporter.client.helpers.now(), + status: STATUSES.PASSED, + attributes: [ + { key: 'key1', value: 'value1', system: false }, + { key: 'key2', value: 'value2', system: false }, + { value: 'savedTestItemAttrValue' }, + { key: 'savedTestItemAttrKey', value: 'savedTestItemAttrValue', system: false }, + ], + description: 'savedTestItemDescription\nDescription', + testCaseId: 'testCaseId', + }; + + expect(reporter.client.finishTestItem).toHaveBeenNthCalledWith( + 1, + 'testItemId', + finishStepObject, + ); + }); }); diff --git a/src/__tests__/utils.spec.ts b/src/__tests__/utils.spec.ts index 72addaf..95b5f8e 100644 --- a/src/__tests__/utils.spec.ts +++ b/src/__tests__/utils.spec.ts @@ -27,6 +27,8 @@ import { getAttachments, isErrorLog, calculateRpStatus, + getAdditionalInfo, + getDescription, } from '../utils'; import fs from 'fs'; import path from 'path'; @@ -35,7 +37,10 @@ import { TestOutcome, BASIC_ATTACHMENT_CONTENT_TYPES, BASIC_ATTACHMENT_NAMES, + RPTestInfo, } from '../constants'; +import { TestCase } from '@playwright/test/reporter'; +import { TestAdditionalInfo } from '../models/reporting'; describe('testing utils', () => { test('isFalse', () => { @@ -418,4 +423,136 @@ describe('testing utils', () => { expect(status).toBe(STATUSES.PASSED); }); }); + + describe('getAdditionalInfo', () => { + test('Should collect only allowed fields', () => { + const testCase = { + results: [ + { + attachments: [ + { + name: RPTestInfo.attributes, + contentType: 'application/json', + body: Buffer.from( + JSON.stringify([ + { key: 'key1', value: 'value1', system: true }, + { key: 'key2', value: 'value2', system: true }, + ]), + ), + }, + { + name: RPTestInfo.description, + contentType: 'plain/text', + body: Buffer.from('Description'), + }, + { + name: RPTestInfo.status, + contentType: 'plain/text', + body: Buffer.from('skipped'), + }, + { + name: RPTestInfo.status, + contentType: 'plain/text', + body: Buffer.from('failed'), + }, + { + name: RPTestInfo.testCaseId, + contentType: 'plain/text', + body: Buffer.from('testCaseId'), + }, + { + name: 'notAllowedField', + contentType: 'plain/text', + body: Buffer.from('notAllowedValue'), + }, + ], + }, + ], + }; + + const expectedResult: TestAdditionalInfo = { + attributes: [ + { key: 'key1', value: 'value1', system: true }, + { key: 'key2', value: 'value2', system: true }, + ], + description: 'Description', + status: 'failed', + testCaseId: 'testCaseId', + }; + + const additionalInfo = getAdditionalInfo(testCase as TestCase); + + expect(additionalInfo).toEqual(expectedResult); + }); + + test('Should recover from error in case if JSON is not valid', () => { + const testCase = { + results: [ + { + attachments: [ + { + name: RPTestInfo.attributes, + contentType: 'application/json', + body: Buffer.from( + `{ key: 'key1', value: 'value1', system: true }, + { key: 'key2', value: 'value2', system: true }`, + ), + }, + { + name: RPTestInfo.description, + contentType: 'plain/text', + body: Buffer.from('Description'), + }, + { + name: RPTestInfo.status, + contentType: 'plain/text', + body: Buffer.from('skipped'), + }, + ], + }, + ], + }; + + const expectedResult: TestAdditionalInfo = { + attributes: [], + description: 'Description', + status: 'skipped', + testCaseId: '', + }; + + const error = new Error('Unexpected token k in JSON at position 2'); + + console.error = jest.fn(); + + const additionalInfo = getAdditionalInfo(testCase as TestCase); + + expect(console.error).toBeCalledTimes(1); + expect(console.error).toHaveBeenCalledWith(error.message); + expect(additionalInfo).toEqual(expectedResult); + }); + }); + + describe('getDescription', () => { + test('It should return one description joined by \n', () => { + const description1 = 'description1'; + const description2 = 'description2'; + + const expectedResult = 'description1\ndescription2'; + + const description = getDescription(description1, description2); + + expect(expectedResult).toBe(description); + }); + + test('It should return one description joined by \n in case when empty string was provided', () => { + const description1 = 'description1'; + const description2 = ''; + + const expectedResult = 'description1'; + + const description = getDescription(description1, description2); + + expect(expectedResult).toBe(description); + }); + }); }); diff --git a/src/constants/index.ts b/src/constants/index.ts index 7eee66e..3c9d315 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -19,6 +19,7 @@ export { LAUNCH_MODES } from './launchModes'; export { TEST_ITEM_TYPES } from './testItemTypes'; export { STATUSES } from './statuses'; export { LOG_LEVELS } from './logLevels'; +export { RPTestInfo } from './testInfo'; export { TestAnnotation, TestOutcome, diff --git a/src/constants/testInfo.ts b/src/constants/testInfo.ts new file mode 100644 index 0000000..ba121c7 --- /dev/null +++ b/src/constants/testInfo.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2023 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +export const RPTestInfo = { + status: 'status', + attributes: 'attributes', + description: 'description', + testCaseId: 'testCaseId', +} as const; diff --git a/src/index.ts b/src/index.ts index ce7e3dc..25da34e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import { RPReporter } from './reporter'; export { ReportingApi } from './reportingApi'; export { LOG_LEVELS, STATUSES } from './constants'; +export { RPTestInfo } from './constants'; export default RPReporter; diff --git a/src/models/reporting.ts b/src/models/reporting.ts index c2021ae..572d05b 100644 --- a/src/models/reporting.ts +++ b/src/models/reporting.ts @@ -66,3 +66,17 @@ export interface LogRQ { export interface TestStepWithId extends TestStep { id: string; } + +export interface TestInfoAttachment { + name: string; + contentType?: string; + body?: string | Buffer; + path?: string; +} + +export interface TestAdditionalInfo { + status?: string; + attributes?: Attribute[]; + description?: string; + testCaseId?: string; +} diff --git a/src/reporter.ts b/src/reporter.ts index 152be5a..cb26f19 100644 --- a/src/reporter.ts +++ b/src/reporter.ts @@ -37,9 +37,11 @@ import { } from './constants'; import { calculateRpStatus, + getAdditionalInfo, getAgentInfo, getAttachments, getCodeRef, + getDescription, getSystemAttributes, isErrorLog, isFalse, @@ -452,6 +454,9 @@ export class RPReporter implements Reporter { if (!savedTestItem) { return Promise.resolve(); } + + const additionalInfo = getAdditionalInfo(test); + const { id: testItemId, attributes, @@ -460,9 +465,12 @@ export class RPReporter implements Reporter { status: predefinedStatus, } = savedTestItem; let withoutIssue; - let testDescription = description; + let testDescription = getDescription(description, additionalInfo.description); + const calculatedStatus = calculateRpStatus(test.outcome(), result.status, test.annotations); - const status = predefinedStatus || calculatedStatus; + const status = predefinedStatus || additionalInfo.status || calculatedStatus; + const calculatedTestCaseId = testCaseId || additionalInfo.testCaseId; + const mergedAttributes = additionalInfo.attributes.concat(attributes ?? []); if (status === STATUSES.SKIPPED) { withoutIssue = isFalse(this.config.skippedIssue); } @@ -489,7 +497,9 @@ export class RPReporter implements Reporter { level: LOG_LEVELS.ERROR, message: stacktrace, }); - testDescription = (description || '').concat(`\n\`\`\`error\n${stacktrace}\n\`\`\``); + testDescription = getDescription(description, additionalInfo.description).concat( + `\n\`\`\`error\n${stacktrace}\n\`\`\``, + ); } [...this.nestedSteps.entries()].forEach(([key, value]) => { @@ -511,9 +521,9 @@ export class RPReporter implements Reporter { endTime: this.client.helpers.now(), status, ...(withoutIssue && { issue: { issueType: 'NOT_ISSUE' } }), - ...(attributes && { attributes }), + ...(mergedAttributes.length !== 0 && { attributes: mergedAttributes }), ...(testDescription && { description: testDescription }), - ...(testCaseId && { testCaseId }), + ...(calculatedTestCaseId && { testCaseId: calculatedTestCaseId }), }; const { promise } = this.client.finishTestItem(testItemId, finishTestItemObj); diff --git a/src/utils.ts b/src/utils.ts index 1a29c85..68eb07e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -29,7 +29,9 @@ import { BASIC_ATTACHMENT_CONTENT_TYPES, TEST_ANNOTATION_TYPES, TEST_OUTCOME_TYPES, + RPTestInfo, } from './constants'; +import { TestAdditionalInfo } from './models/reporting'; const fsPromises = fs.promises; @@ -123,13 +125,13 @@ export const getAttachments = async ( fileContent = body; } else { if (!fs.existsSync(attachmentPath)) { - return; + return undefined; } fileContent = await fsPromises.readFile(attachmentPath); } } catch (e) { console.error(e); - return; + return undefined; } return { @@ -175,3 +177,38 @@ export const calculateRpStatus = ( return calculatedStatus; }; + +export const getAdditionalInfo = (test: TestCase): TestAdditionalInfo => { + const initialValue: TestAdditionalInfo = { + attributes: [], + description: '', + testCaseId: '', + status: '', + }; + + return test.results.reduce( + (additionalInfo, { attachments = [] }) => + Object.assign( + additionalInfo, + attachments.reduce((acc, { name, body }) => { + if (name in RPTestInfo) { + try { + const value = body.toString(); + + return Object.assign(acc, { + [name]: name === RPTestInfo.attributes ? JSON.parse(value) : value, + }); + } catch (error: unknown) { + console.error((error as Error).message); + } + } + + return acc; + }, initialValue), + ), + initialValue, + ); +}; + +export const getDescription = (...descriptions: string[]): string => + descriptions.filter(Boolean).join('\n');