diff --git a/package-lock.json b/package-lock.json index 1d0832b3..f9deb044 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,6 +56,22 @@ "eslint-plugin-cypress": "^2.13.3" } }, + "examples/cypress/node_modules/cypress-qase-reporter": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cypress-qase-reporter/-/cypress-qase-reporter-2.1.1.tgz", + "integrity": "sha512-0eiAEp9dLpkCdfrI860StR6bNtmyD8FTVua79XBnUGEimdLZgBCPV0Mj/8fvelyN8R8bKvpgu9Uzr7yuO+u02A==", + "dev": true, + "dependencies": { + "qase-javascript-commons": "~2.2.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "cypress": ">=8.0.0" + } + }, "examples/jest": { "name": "examples-jest", "devDependencies": { @@ -15194,7 +15210,7 @@ }, "qase-cypress": { "name": "cypress-qase-reporter", - "version": "2.1.1", + "version": "2.2.0-beta.1", "license": "Apache-2.0", "dependencies": { "qase-javascript-commons": "~2.2.0", diff --git a/qase-cypress/README.md b/qase-cypress/README.md index 62cc6387..982fd498 100644 --- a/qase-cypress/README.md +++ b/qase-cypress/README.md @@ -4,21 +4,12 @@ Publish results simple and easy. ## Installation -To install the latest release version (2.0.x), run: +To install the latest release version (2.2.x), run: ```sh npm install -D cypress-qase-reporter ``` - - -To install the latest beta version (2.1.x), run: - -```sh -npm install -D cypress-qase-reporter@beta -``` - ## Updating from v1 to v2.1 To update an existing test project using Qase reporter from version 1 to version 2.1, @@ -30,7 +21,7 @@ run the following steps: - import { qase } from 'cypress-qase-reporter/dist/mocha' + import { qase } from 'cypress-qase-reporter/mocha' ``` - + 2. Update reporter configuration in `cypress.config.js` and/or environment variables — see the [configuration reference](#configuration) below. @@ -68,6 +59,23 @@ run the following steps: ... ``` +## Updating from v2.1 to v2.2 + +To update an existing test project using Qase reporter from version 2.1 to version 2.2, +run the following steps: + +1. Add a metadata in the `e2e` section of `cypress.config.js` + + ```diff + ... + e2e: { + setupNodeEvents(on, config) { + require('cypress-qase-reporter/plugin')(on, config) + + require('cypress-qase-reporter/metadata')(on) + } + } + ... + ## Getting started The Cypress reporter can auto-generate test cases @@ -80,6 +88,17 @@ from Qase.io before executing tests. It's a more reliable way to bind autotests to test cases, that persists when you rename, move, or parameterize your tests. +### Metadata + +- `qase.title` - set the title of the test case +- `qase.fields` - set the fields of the test case +- `qase.suite` - set the suite of the test case +- `qase.comment` - set the comment of the test case +- `qase.parameters` - set the parameters of the test case +- `qase.groupParameters` - set the group parameters of the test case +- `qase.ignore` - ignore the test case in Qase. The test will be executed, but the results will not be sent to Qase. +- `qase.step` - create a step in the test case + For example: ```typescript @@ -88,6 +107,7 @@ import { qase } from 'cypress-qase-reporter/mocha'; describe('My First Test', () => { qase(1, it('Several ids', () => { + qase.title('My title'); expect(true).to.equal(true); }) ); @@ -145,8 +165,6 @@ Example `cypress.config.js` config: ```js import cypress from 'cypress'; -import plugins from './cypress/plugins/index.js'; - module.exports = cypress.defineConfig({ reporter: 'cypress-multi-reporters', reporterOptions: { @@ -180,7 +198,8 @@ module.exports = cypress.defineConfig({ video: false, e2e: { setupNodeEvents(on, config) { - return plugins(on, config); + require('cypress-qase-reporter/plugin')(on, config) + require('cypress-qase-reporter/metadata')(on) }, }, }); diff --git a/qase-cypress/changelog.md b/qase-cypress/changelog.md index 65e960f2..c8e58f3c 100644 --- a/qase-cypress/changelog.md +++ b/qase-cypress/changelog.md @@ -1,3 +1,71 @@ +# cypress-qase-reporter@2.2.0 + +## What's new + +Minor release of the Cypress reporter package + +# cypress-qase-reporter@2.2.0-beta.3 + +## What's new + +Added the ability to add attachments to tests or steps: + +- `qase.attach` - add an attachment to test or step + +```ts +it('test', () => { + qase.attach({ paths: '/path/to/file' }); + qase.step('Step 1', () => { + cy.visit('https://example.com'); + qase.attach({ name: 'attachment.txt', content: 'Hello, world!', contentType: 'text/plain' }); + }); +}); +``` + +# cypress-qase-reporter@2.2.0-beta.2 + +## What's new + +Added the ability to add steps in tests: + +- `qase.step` - add a step to the test + +```ts +it('test', () => { + qase.step('Step 1', () => { + cy.visit('https://example.com'); + }); +}); +``` + +# cypress-qase-reporter@2.2.0-beta.1 + +## What's new + +Added the ability to specify a test metadata in tests: + +- `qase.title` - set the test title +- `qase.fields` - set the test fields +- `qase.suite` - set the test suite +- `qase.comment` - set the test comment +- `qase.parameters` - set the test parameters +- `qase.groupParameters` - set the test group parameters +- `qase.ignore` - ignore the test in Qase + +```ts +it('test', () => { + qase.title('Title'); + qase.fields({ field: 'value' }); + qase.suite('Suite'); + qase.comment('Comment'); + qase.parameters({ param: 'value' }); + qase.groupParameters({ param: 'value' }); + qase.ignore(); + + cy.visit('https://example.com'); +}); +``` + # cypress-qase-reporter@2.1.0 ## What's new @@ -40,8 +108,8 @@ The reporter will wait for all results to be sent to Qase and will not block the ## What's new -1. Cypress kills the process after the last tests. -The reporter will wait for all results to be sent to Qase and will not block the process after sending. +1. Cypress kills the process after the last tests. + The reporter will wait for all results to be sent to Qase and will not block the process after sending. 2. The reporter will collect suites and add them to results. @@ -66,7 +134,7 @@ For more information about the new features and a guide for migration from v1, r # cypress-qase-reporter@2.0.0-beta.3 -Fixed an issue with multiple test runs created when Cypress is running +Fixed an issue with multiple test runs created when Cypress is running multiple tests in parallel. # cypress-qase-reporter@2.0.0-beta.2 diff --git a/qase-cypress/docs/usage.md b/qase-cypress/docs/usage.md new file mode 100644 index 00000000..211991fc --- /dev/null +++ b/qase-cypress/docs/usage.md @@ -0,0 +1,196 @@ +# Qase Integration in Cypress + +This guide demonstrates how to integrate Qase with Cypress, providing instructions on how to add Qase IDs, titles, +fields, suites, comments, and file attachments to your test cases. + +--- + +## Adding QaseID to a Test + +To associate a QaseID with a test in Cypress, use the `qase` function. This function accepts a single integer +representing the test's ID in Qase. + +### Example: + +```javascript +import { qase } from 'cypress-qase-reporter/mocha'; + +qase(1, it('simple test', () => { + cy.visit('https://example.com'); +})); +``` + +--- + +## Adding a Title to a Test + +You can provide a title for your test using the `qase.title` function. The function accepts a string, which will be +used as the test's title in Qase. If no title is provided, the test method name will be used by default. + +### Example: + +```javascript +import { qase } from 'cypress-qase-reporter/mocha'; + +it('test', () => { + qase.title('Title'); + cy.visit('https://example.com'); +}); +``` + +--- + +## Adding Fields to a Test + +The `qase.fields` function allows you to add additional metadata to a test case. You can specify multiple fields to +enhance test case information in Qase. + +### System Fields: + +- `description` — Description of the test case. +- `preconditions` — Preconditions for the test case. +- `postconditions` — Postconditions for the test case. +- `severity` — Severity of the test case (e.g., `critical`, `major`). +- `priority` — Priority of the test case (e.g., `high`, `low`). +- `layer` — Test layer (e.g., `UI`, `API`). + +### Example: + +```javascript +import { qase } from 'cypress-qase-reporter/mocha'; + +it('test', () => { + qase.fields({ description: "Description", preconditions: "Preconditions" }); + cy.visit('https://example.com'); +}); +``` + +--- + +## Adding a Suite to a Test + +To assign a suite or sub-suite to a test, use the `qase.suite` function. It can receive a suite name, and optionally a +sub-suite, both as strings. + +### Example: + +```javascript +import { qase } from 'cypress-qase-reporter/mocha'; + +it('test', () => { + qase.suite("Suite 01"); + cy.visit('https://example.com'); +}); + +it('test', () => { + qase.suite("Suite 01\tSuite 02"); + cy.visit('https://example.com'); +}); +``` + +--- + +## Ignoring a Test in Qase + +To exclude a test from being reported to Qase (while still executing the test in Cypress), use the `qase.ignore` +function. The test will run, but its result will not be sent to Qase. + +### Example: + +```javascript +import { qase } from 'cypress-qase-reporter/mocha'; + +it('test', () => { + qase.ignore(); + cy.visit('https://example.com'); +}); +``` + +--- + +## Adding a Comment to a Test + +You can attach comments to the test results in Qase using the `qase.comment` function. The comment will be displayed +alongside the test execution details in Qase. + +### Example: + +```javascript +import { qase } from 'cypress-qase-reporter/mocha'; + +it('test', () => { + qase.comment("Some comment"); + cy.visit('https://example.com'); +}); +``` + +--- + +## Attaching Files to a Test + +To attach files to a test result, use the `qase.attach` function. This method supports attaching one or multiple files, +along with optional file names, comments, and file types. + +### Example: + +```javascript +import { qase } from 'cypress-qase-reporter/mocha'; + +it('test', () => { + qase.attach({ name: 'attachment.txt', content: 'Hello, world!', contentType: 'text/plain' }); + qase.attach({ paths: '/path/to/file' }); + qase.attach({ paths: ['/path/to/file', '/path/to/another/file'] }); + cy.visit('https://example.com'); +}); +``` + +## Adding Parameters to a Test + +You can add parameters to a test case using the `qase.parameters` function. This function accepts an object with +parameter names and values. + +### Example: + +```javascript +import { qase } from 'cypress-qase-reporter/mocha'; + +it('test', () => { + qase.parameters({ param1: 'value1', param2: 'value2' }); + cy.visit('https://example.com'); +}); +``` + +## Adding Group Parameters to a Test + +To add group parameters to a test case, use the `qase.groupParameters` function. This function accepts an list with +group parameter names. + +### Example: + +```javascript +import { qase } from 'cypress-qase-reporter/mocha'; + +it('test', () => { + qase.parameters({ param1: 'value1', param2: 'value2' }); + qase.groupParameters(['param1']); + cy.visit('https://example.com'); +}); +``` + +## Adding Steps to a Test + +You can add steps to a test case using the `qase.step` function. This function accepts a string, which will be used as +the step description in Qase. + +### Example: + +```javascript +import { qase } from 'cypress-qase-reporter/mocha'; + +it('test', () => { + qase.step('Some step', () => { + // some actions + }); + cy.visit('https://example.com'); +}); +``` diff --git a/qase-cypress/package.json b/qase-cypress/package.json index 7bf505c1..367359ad 100644 --- a/qase-cypress/package.json +++ b/qase-cypress/package.json @@ -1,6 +1,6 @@ { "name": "cypress-qase-reporter", - "version": "2.1.1", + "version": "2.2.0", "description": "Qase Cypress Reporter", "homepage": "https://github.com/qase-tms/qase-javascript", "sideEffects": false, @@ -12,6 +12,7 @@ "./reporter": "./dist/reporter.js", "./package.json": "./package.json", "./plugin": "./dist/plugin.js", + "./metadata": "./dist/metadata.js", "./hooks": "./dist/hooks.js" }, "typesVersions": { diff --git a/qase-cypress/src/metadata.js b/qase-cypress/src/metadata.js new file mode 100644 index 00000000..984bb96d --- /dev/null +++ b/qase-cypress/src/metadata.js @@ -0,0 +1,73 @@ +import { MetadataManager } from './metadata/manager'; + +module.exports = function(on) { + on('task', { + qaseTitle(value) { + MetadataManager.setTitle(value); + return null; + }, + }); + + on('task', { + qaseFields(value) { + MetadataManager.setFields(value); + return null; + }, + }); + + on('task', { + qaseIgnore() { + MetadataManager.setIgnore(); + return null; + }, + }); + + on('task', { + qaseParameters(value) { + MetadataManager.setParameters(value); + return null; + }, + }); + + on('task', { + qaseGroupParameters(value) { + MetadataManager.setGroupParams(value); + return null; + }, + }); + + on('task', { + qaseSuite(value) { + MetadataManager.setSuite(value); + return null; + }, + }); + + on('task', { + qaseComment(value) { + MetadataManager.setComment(value); + return null; + }, + }); + + on('task', { + qaseStepStart(value) { + MetadataManager.addStepStart(value); + return null; + }, + }); + + on('task', { + qaseStepEnd(value) { + MetadataManager.addStepEnd(value); + return null; + }, + }); + + on('task', { + qaseAttach(value) { + MetadataManager.addAttach(value); + return null; + }, + }); +}; diff --git a/qase-cypress/src/metadata/manager.ts b/qase-cypress/src/metadata/manager.ts new file mode 100644 index 00000000..7f81ad55 --- /dev/null +++ b/qase-cypress/src/metadata/manager.ts @@ -0,0 +1,209 @@ +import { Attach, Metadata, StepStart } from './models'; +import { readFileSync, existsSync, unlinkSync, writeFileSync } from 'fs'; +import { v4 as uuidv4 } from 'uuid'; +import path from 'path'; +import { Attachment, getMimeTypes } from 'qase-javascript-commons'; + +const metadataPath = 'qaseMetadata'; + +// eslint-disable-next-line @typescript-eslint/no-extraneous-class +export class MetadataManager { + public static getMetadata(): Metadata | undefined { + if (!this.isExists()) { + return undefined; + } + + let metadata: Metadata = { + title: undefined, + fields: {}, + parameters: {}, + groupParams: {}, + ignore: false, + suite: undefined, + comment: undefined, + steps: [], + currentStepId: undefined, + firstStepName: undefined, + attachments: [], + stepAttachments: {}, + }; + + try { + const data = readFileSync(metadataPath, 'utf8'); + metadata = JSON.parse(data) as Metadata; + + return metadata; + } catch (err) { + console.error('Error reading metadata file:', err); + } + + return undefined; + } + + public static setIgnore(): void { + const metadata = this.getMetadata() ?? {}; + metadata.ignore = true; + this.setMetadata(metadata); + } + + public static addStepStart(name: string): void { + const metadata = this.getMetadata() ?? {}; + + if (metadata.firstStepName === name) { + return; + } + + if (!metadata.steps) { + metadata.steps = []; + } + const id = uuidv4(); + const parentId = metadata.currentStepId ?? undefined; + metadata.steps.push({ timestamp: Date.now(), name, id: id, parentId: parentId }); + metadata.currentStepId = id; + + if (!metadata.firstStepName) { + metadata.firstStepName = name; + } + + this.setMetadata(metadata); + } + + public static addStepEnd(status: string): void { + const metadata = this.getMetadata() ?? {}; + if (!metadata.steps || !metadata.currentStepId) { + return; + } + const parentId = metadata.steps.reverse().find((step): step is StepStart => step.id === metadata.currentStepId)?.parentId; + metadata.steps.push({ timestamp: Date.now(), status, id: metadata.currentStepId }); + metadata.currentStepId = parentId; + this.setMetadata(metadata); + } + + public static addAttach(attach: Attach) { + const metadata = this.getMetadata() ?? {}; + + if (!metadata.attachments) { + metadata.attachments = []; + } + if (!metadata.stepAttachments) { + metadata.stepAttachments = {}; + } + + const attachments = this.prepareAttach(attach); + + if (metadata.currentStepId) { + if (metadata.stepAttachments[metadata.currentStepId] === undefined) { + metadata.stepAttachments[metadata.currentStepId] = attachments; + } else { + metadata.stepAttachments[metadata.currentStepId]?.push(...attachments); + } + } else { + metadata.attachments.push(...attachments); + } + + this.setMetadata(metadata); + } + + public static setSuite(suite: string): void { + const metadata = this.getMetadata() ?? {}; + metadata.suite = suite; + this.setMetadata(metadata); + } + + public static setComment(comment: string): void { + const metadata = this.getMetadata() ?? {}; + metadata.comment = comment; + this.setMetadata(metadata); + } + + public static setTitle(title: string): void { + const metadata = this.getMetadata() ?? {}; + metadata.title = title; + this.setMetadata(metadata); + } + + public static setFields(fields: Record): void { + const metadata = this.getMetadata() ?? {}; + metadata.fields = fields; + this.setMetadata(metadata); + } + + public static setParameters(parameters: Record): void { + const metadata = this.getMetadata() ?? {}; + metadata.parameters = parameters; + this.setMetadata(metadata); + } + + public static setGroupParams(groupParams: Record): void { + const metadata = this.getMetadata() ?? {}; + metadata.groupParams = groupParams; + this.setMetadata(metadata); + } + + private static setMetadata(metadata: Metadata): void { + try { + const data = JSON.stringify(metadata); + writeFileSync(metadataPath, data); + } catch (err) { + console.error('Error writing metadata file:', err); + } + } + + public static clear() { + if (!this.isExists()) { + return; + } + + try { + unlinkSync(metadataPath); + } catch (err) { + console.error('Error clearing state file:', err); + } + } + + static isExists(): boolean { + return existsSync(metadataPath); + } + + static prepareAttach(attach: Attach): Attachment[] { + const attachments: Attachment[] = []; + if (attach.paths) { + if (Array.isArray(attach.paths)) { + attach.paths.forEach((file) => { + const attachmentName = path.basename(file); + const contentType: string = getMimeTypes(file); + attachments.push({ + file_name: attachmentName, + mime_type: contentType, + file_path: file, + content: '', + size: 0, + id: uuidv4(), + }); + }); + } else { + const attachmentName = path.basename(attach.paths); + const contentType: string = getMimeTypes(attach.paths); + attachments.push({ + file_name: attachmentName, + mime_type: contentType, + file_path: attach.paths, + content: '', + size: 0, + id: uuidv4(), + }); + } + } else if (attach.content) { + attachments.push({ + file_name: attach.name ?? 'attachment', + mime_type: attach.contentType ?? 'application/octet-stream', + file_path: null, + content: attach.content, + size: 0, + id: uuidv4(), + }); + } + + return attachments; + } +} diff --git a/qase-cypress/src/metadata/models.ts b/qase-cypress/src/metadata/models.ts new file mode 100644 index 00000000..e7184a18 --- /dev/null +++ b/qase-cypress/src/metadata/models.ts @@ -0,0 +1,37 @@ +import { Attachment } from 'qase-javascript-commons'; + +export interface Metadata { + title?: string | undefined; + fields?: Record; + parameters?: Record; + groupParams?: Record; + ignore?: boolean; + suite?: string | undefined; + comment?: string | undefined; + steps?: (StepStart | StepEnd)[]; + currentStepId?: string | undefined; + firstStepName?: string | undefined; + attachments?: Attachment[]; + stepAttachments?: Record; +} + +export interface StepStart { + id: string; + timestamp: number; + name: string; + parentId: string | undefined; +} + +export interface StepEnd { + id: string; + timestamp: number; + status: string; +} + + +export interface Attach { + name?: string; + paths?: string | string[]; + content?: Buffer | string; + contentType?: string; +} diff --git a/qase-cypress/src/mocha.ts b/qase-cypress/src/mocha.ts index cab30740..a60b73d8 100644 --- a/qase-cypress/src/mocha.ts +++ b/qase-cypress/src/mocha.ts @@ -10,3 +10,164 @@ export const qase = ( return test; }; + +/** + * Set a title for the test case + * @param {string} value + * @example + * it('test', () => { + * qase.title("Title"); + * cy.visit('https://example.com'); + * }); + */ +qase.title = ( + value: string, +) => { + return cy.task('qaseTitle', value).then(() => { + // + }); +}; + + +/** + * Set fields for the test case + * @param {Record} values + * @example + * it('test', () => { + * qase.fields({description: "Description"}); + * cy.visit('https://example.com'); + * }); + */ +qase.fields = ( + values: Record, +) => { + return cy.task('qaseFields', values).then(() => { + // + }); +}; + +/** + * Ignore the test case result in Qase + * @example + * it('test', () => { + * qase.ignore(); + * cy.visit('https://example.com'); + * }); + */ +qase.ignore = () => { + return cy.task('qaseIgnore').then(() => { + // + }); +}; + +/** + * Set parameters for the test case + * @param {Record} values + * @example + * it('test', () => { + * qase.parameters({param01: "value01"}); + * cy.visit('https://example.com'); + * }); + */ +qase.parameters = ( + values: Record, +) => { + return cy.task('qaseParameters', values).then(() => { + // + }); +}; + +/** + * Set group parameters for the test case + * @param {Record} values + * @example + * it('test', () => { + * qase.groupParameters({param01: "value01"}); + * cy.visit('https://example.com'); + * }); + */ +qase.groupParameters = ( + values: Record, +) => { + return cy.task('qaseGroupParameters', values).then(() => { + // + }); +}; + +/** + * Set a suite for the test case + * @param {string} value + * @example + * it('test', () => { + * qase.suite("Suite 01"); + * cy.visit('https://example.com'); + * }); + */ +qase.suite = ( + value: string, +) => { + return cy.task('qaseSuite', value).then(() => { + // + }); +}; + +/** + * Set a comment for the test case + * @param {string} value + * @example + * it('test', () => { + * qase.comment("Some comment"); + * cy.visit('https://example.com'); + * }); + */ +qase.comment = ( + value: string, +) => { + return cy.task('qaseComment', value).then(() => { + // + }); +}; + +/** + * Add a step to the test case + * @param {string} name + * @param {() => T | PromiseLike} body + * @example + * it('test', () => { + * qase.step("Some step", () => { + * // some actions + * }); + * cy.visit('https://example.com'); + * }); + */ +qase.step = (name: string, body: () => T | PromiseLike) => { + return cy.task('qaseStepStart', name).then(() => { + return Cypress.Promise.resolve(body()); + }).then(() => { + cy.task('qaseStepEnd', 'passed').then(() => { + // + }); + }); +}; + +/** + * Attach a file to the test case or the step + * @param attach + * @example + * it('test', () => { + * qase.attach({ name: 'attachment.txt', content: 'Hello, world!', contentType: 'text/plain' }); + * qase.attach({ paths: '/path/to/file'}); + * qase.attach({ paths: ['/path/to/file', '/path/to/another/file']}); + * cy.visit('https://example.com'); + * }); + */ +qase.attach = (attach: { + name?: string, + paths?: string | string[], + content?: Buffer | string, + contentType?: string, +}) => { + return cy.task('qaseAttach', attach).then(() => { + // + }); +}; diff --git a/qase-cypress/src/reporter.ts b/qase-cypress/src/reporter.ts index 18b5657a..33a4173e 100644 --- a/qase-cypress/src/reporter.ts +++ b/qase-cypress/src/reporter.ts @@ -14,11 +14,15 @@ import { Attachment, FrameworkOptionsType, ConfigType, + TestStepType, + StepStatusEnum, } from 'qase-javascript-commons'; import { traverseDir } from './utils/traverse-dir'; import { configSchema } from './configSchema'; import { ReporterOptionsType } from './options'; +import { MetadataManager } from './metadata/manager'; +import { StepEnd, StepStart } from './metadata/models'; const { EVENT_TEST_FAIL, @@ -166,12 +170,21 @@ export class CypressQaseReporter extends reporters.Base { * @private */ private addTestResult(test: Test) { + const metadata = MetadataManager.getMetadata(); + + if (metadata?.ignore) { + MetadataManager.clear(); + return; + } + const ids = CypressQaseReporter.getCaseId(test.title); const attachments = this.screenshotsFolder ? CypressQaseReporter.findAttachments(ids, this.screenshotsFolder) : undefined; + attachments?.push(...(metadata?.attachments ?? [])); + let relations = {}; if (test.parent !== undefined) { const data = []; @@ -189,18 +202,42 @@ export class CypressQaseReporter extends reporters.Base { }; } + if (metadata?.suite) { + relations = { + suite: { + data: [ + { + title: metadata.suite, + public_id: null, + }, + ], + }, + }; + } + + let message: string | null = null; + if (metadata?.comment) { + message = metadata.comment; + } + if (test.err?.message) { + if (message) { + message += '\n\n'; + } + message += test.err.message; + } + const result: TestResultType = { attachments: attachments ?? [], author: null, - fields: {}, - message: test.err?.message ?? null, + fields: metadata?.fields ?? {}, + message: message, muted: false, - params: {}, - group_params: {}, + params: metadata?.parameters ?? {}, + group_params: metadata?.groupParams ?? {}, relations: relations, run_id: null, signature: this.getSignature(test, ids), - steps: [], + steps: metadata?.steps ? this.getSteps(metadata.steps, metadata.stepAttachments ?? {}) : [], id: uuidv4(), execution: { status: test.state @@ -213,10 +250,12 @@ export class CypressQaseReporter extends reporters.Base { thread: null, }, testops_id: ids.length > 0 ? ids : null, - title: test.title, + title: metadata?.title ?? test.title, }; void this.reporter.addTestResult(result); + + MetadataManager.clear(); } /** @@ -262,4 +301,48 @@ export class CypressQaseReporter extends reporters.Base { return undefined; } + + private getSteps(steps: (StepStart | StepEnd)[], attachments: Record): TestStepType[] { + const result: TestStepType[] = []; + const stepMap = new Map(); + + for (const step of steps.sort((a, b) => a.timestamp - b.timestamp)) { + if (!('status' in step)) { + const newStep = new TestStepType(); + newStep.id = step.id; + newStep.execution.status = StepStatusEnum.failed; + newStep.execution.start_time = step.timestamp; + newStep.execution.end_time = Date.now(); + newStep.data = { + action: step.name, + expected_result: null, + }; + + if (attachments[step.id]) { + newStep.attachments = attachments[step.id] ?? []; + } + + const parentId = step.parentId; + if (parentId) { + newStep.parent_id = parentId; + const parent = stepMap.get(parentId); + if (parent) { + parent.steps.push(newStep); + } + } else { + result.push(newStep); + } + + stepMap.set(step.id, newStep); + } else { + const stepType = stepMap.get(step.id); + if (stepType) { + stepType.execution.status = step.status as StepStatusEnum; + stepType.execution.end_time = step.timestamp; + } + } + } + + return result; + } }