From 030ad7b2f4475fafe880081355c99a5a396e414c Mon Sep 17 00:00:00 2001 From: Dmitrii Gridnev Date: Tue, 1 Oct 2024 17:36:09 +0200 Subject: [PATCH 1/2] feature: added support parameters from data files ```javascript // qase.parameters: userId, user.name pm.test("Status code is 201", function() { pm.response.to.have.status(201); }); ``` --- qase-newman/README.md | 2 + qase-newman/changelog.md | 7 ++ qase-newman/docs/usage.md | 59 ++++++++++++++++ qase-newman/package.json | 4 +- qase-newman/src/reporter.ts | 132 +++++++++++++++++++++++++++++++++--- 5 files changed, 194 insertions(+), 10 deletions(-) create mode 100644 qase-newman/docs/usage.md diff --git a/qase-newman/README.md b/qase-newman/README.md index 0746604c..a7db2670 100644 --- a/qase-newman/README.md +++ b/qase-newman/README.md @@ -48,6 +48,8 @@ https://app.qase.io/run/QASE_PROJECT_CODE

+You can find more information about using the reporter [here](./docs/usage.md). + ## Configuration Qase Newman reporter can be configured in multiple ways: diff --git a/qase-newman/changelog.md b/qase-newman/changelog.md index 6c80dd64..b627b1cd 100644 --- a/qase-newman/changelog.md +++ b/qase-newman/changelog.md @@ -1,3 +1,10 @@ +# qase-newman@2.1.0 + +## What's new + +Added support parameters from data files in Newman. +How to use parameters from data files in Newman, see [here](./docs/usage.md). + # qase-newman@2.0.0 ## What's new diff --git a/qase-newman/docs/usage.md b/qase-newman/docs/usage.md new file mode 100644 index 00000000..9a8a40ed --- /dev/null +++ b/qase-newman/docs/usage.md @@ -0,0 +1,59 @@ +# How to Use Parameters from Data Files in Newman + +Newman allows you to leverage parameters from data files to make your API tests more dynamic and efficient. By utilizing +the `--data` or `-d` option when running a collection, you can feed your tests with various input sets. The data files +can be formatted as either JSON or CSV. + +### Example Data File + +Consider the following `data.json` file, which contains user data structured as complex objects: + +```json +[ + { + "userid": 1, + "user": { + "name": "John", + "age": 30 + } + }, + { + "userid": 2, + "user": { + "name": "Jane", + "age": 25 + } + } +] +``` + +### Example Tests + +Below are example tests that utilize the data parameters defined in the data file: + +```javascript +// qase.parameters: userId, user.name +pm.test("Status code is 201", function() { + pm.response.to.have.status(201); +}); + +// qase.parameters: userId +pm.test("Response has correct userId", function() { + var jsonData = pm.response.json(); + pm.expect(jsonData.userId).to.eql(pm.iterationData.get("userid")); +}); + +pm.test("Response has correct name", function() { + var jsonData = pm.response.json(); + pm.expect(jsonData.user.name).to.eql(pm.iterationData.get("user.name")); +}); +``` + +### Expected Behavior + +When you run the tests, the following behavior is expected: + +- In the **`Status code is 201`** test, both `userId` and `user.name` will be passed as parameters. +- In the **`Response has correct userId`** test, only the `userId` parameter will be passed. +- In the **`Response has correct name`** test, all relevant parameters from the data file will be passed, including + `userId`, `user.name`, and `user.age`. diff --git a/qase-newman/package.json b/qase-newman/package.json index 07675a91..28961502 100644 --- a/qase-newman/package.json +++ b/qase-newman/package.json @@ -1,6 +1,6 @@ { "name": "newman-reporter-qase", - "version": "2.0.1", + "version": "2.1.0", "description": "Qase TMS Newman Reporter", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -45,7 +45,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@types/jest": "^29.5.2", - "@types/newman": "^5.3.3", + "@types/newman": "^5.3.6", "@types/postman-collection": "^3.5.7", "jest": "^29.5.0", "postman-collection": "^4.1.7", diff --git a/qase-newman/src/reporter.ts b/qase-newman/src/reporter.ts index 3058bd77..ed3ad628 100644 --- a/qase-newman/src/reporter.ts +++ b/qase-newman/src/reporter.ts @@ -1,10 +1,8 @@ import { EventEmitter } from 'events'; import semver from 'semver'; -import { NewmanRunExecution } from 'newman'; -import { - EventList, PropertyBase, PropertyBaseDefinition, -} from 'postman-collection'; +import { NewmanRunExecution, NewmanRunOptions } from 'newman'; +import { EventList, PropertyBase, PropertyBaseDefinition } from 'postman-collection'; import { ConfigType, QaseReporter, @@ -13,7 +11,9 @@ import { TestResultType, getPackageVersion, ConfigLoader, - composeOptions, Relation, SuiteData, + composeOptions, + Relation, + SuiteData, } from 'qase-javascript-commons'; export type NewmanQaseOptionsType = ConfigType; @@ -27,6 +27,11 @@ export class NewmanQaseReporter { */ static qaseIdRegExp = /\/\/\s*?[qQ]ase:\s?((?:[\d]+[\s,]{0,})+)/; + /** + * @type {RegExp} + */ + static qaseParamRegExp = /qase\.parameters:\s*([\w.]+(?:\s*,\s*[\w.]+)*)/i; + /** * @param {EventList} eventList * @returns {number[]} @@ -50,9 +55,33 @@ export class NewmanQaseReporter { return ids; } + /** + * @param {EventList} eventList + * @returns {string[]} + * @private + */ + private static getParameters(eventList: EventList) { + const params: string[] = []; + + eventList.each((event) => { + if (event.listen === 'test' && event.script.exec) { + event.script.exec.forEach((line) => { + const match = line.match(NewmanQaseReporter.qaseParamRegExp); + + if (match) { + const parameters: string[] = match[1]?.split(/\s*,\s*/) ?? []; + + params.push(...parameters); + } + }); + } + }); + + return params; + } + /** * @param {PropertyBase} item - * @param {string[]} titles * @returns {string[]} * @private */ @@ -88,17 +117,22 @@ export class NewmanQaseReporter { * @private */ private timerMap = new Map(); + /** + * @type {Record[]} + * @private + */ + private parameters: Record[] = []; /** * @param {EventEmitter} emitter * @param {NewmanQaseOptionsType} options - * @param {unknown} _ + * @param {NewmanRunOptions} collectionOptions * @param {ConfigLoaderInterface} configLoader */ public constructor( emitter: EventEmitter, options: NewmanQaseOptionsType, - _: unknown, + collectionOptions: NewmanRunOptions, configLoader = new ConfigLoader(), ) { const config = configLoader.load(); @@ -110,6 +144,7 @@ export class NewmanQaseReporter { reporterName: 'newman-reporter-qase', }); + this.parameters = this.getParameters(collectionOptions.iterationData); this.addRunnerListeners(emitter); } @@ -199,6 +234,8 @@ export class NewmanQaseReporter { pendingResult.execution.duration = now - timer; } + pendingResult.params = this.prepareParameters(item.events, exec.cursor.iteration); + void this.reporter.addTestResult(pendingResult); } }); @@ -252,4 +289,83 @@ export class NewmanQaseReporter { return signature; } + + /** + * @param {EventList} events + * @param {number} iteration + * @returns {Record} + * @private + */ + private prepareParameters(events: EventList, iteration: number): Record { + if (this.parameters.length === 0) { + return {}; + } + + const availableParameters = this.parameters[iteration] ?? {}; + const params = NewmanQaseReporter.getParameters(events); + + if (params.length === 0) { + return availableParameters; + } + + return params.reduce>((filteredParams, param) => { + const value = availableParameters[param]; + if (value) { + filteredParams[param] = value; + } + return filteredParams; + }, {}); + } + + + /** + * @param {any} iterationData + * @private + */ + private getParameters(iterationData: any): Record[] { + if (!iterationData) { + return []; + } + + if (Array.isArray(iterationData) && iterationData.every(item => typeof item === 'object' && item !== null)) { + return iterationData.map((item: Record) => this.convertToRecord(item)); + } + + return []; + } + + /** + * @param {unknown} obj + * @param parentKey + * @returns {Record} + * @private + */ + private convertToRecord(obj: unknown, parentKey = ''): Record { + const record: Record = {}; + + if (this.isRecord(obj)) { + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + const value = obj[key]; + const newKey = parentKey ? `${parentKey}.${key}` : key; + + if (this.isRecord(value)) { + Object.assign(record, this.convertToRecord(value, newKey)); + } else { + record[newKey] = String(value); + } + } + } + } + + return record; + } + + /** + * @param {unknown} obj + * @private + */ + private isRecord(obj: unknown): obj is Record { + return typeof obj === 'object' && obj !== null; + } } From bafe2634496770ff6d044a869c8cecf784a4842a Mon Sep 17 00:00:00 2001 From: Dmitrii Gridnev Date: Wed, 2 Oct 2024 10:16:13 +0200 Subject: [PATCH 2/2] feature: added new parameter "autoCollectParams" This parameter affects the behavior of the reporter when collecting parameters. --- package-lock.json | 32 +++++++++++++++++++------------- qase-newman/changelog.md | 2 +- qase-newman/docs/usage.md | 25 +++++++++++++++++++++++-- qase-newman/package.json | 3 ++- qase-newman/src/configSchema.ts | 31 +++++++++++++++++++++++++++++++ qase-newman/src/options.ts | 3 +++ qase-newman/src/reporter.ts | 26 +++++++++++++++++++++----- 7 files changed, 100 insertions(+), 22 deletions(-) create mode 100644 qase-newman/src/configSchema.ts create mode 100644 qase-newman/src/options.ts diff --git a/package-lock.json b/package-lock.json index 8edd7d76..1d0832b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3924,14 +3924,14 @@ } }, "node_modules/ajv": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", - "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dependencies": { "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.4.1" + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -6922,6 +6922,11 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-uri": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.2.tgz", + "integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==" + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -15168,7 +15173,7 @@ }, "qase-cucumberjs": { "name": "cucumberjs-qase-reporter", - "version": "2.0.1", + "version": "2.0.2", "license": "Apache-2.0", "dependencies": { "@cucumber/messages": "^22.0.0", @@ -15212,7 +15217,7 @@ } }, "qase-javascript-commons": { - "version": "2.2.0", + "version": "2.2.1", "license": "Apache-2.0", "dependencies": { "ajv": "^8.12.0", @@ -15275,12 +15280,12 @@ }, "qase-jest": { "name": "jest-qase-reporter", - "version": "2.0.2", + "version": "2.0.3", "license": "Apache-2.0", "dependencies": { "lodash.get": "^4.4.2", "lodash.has": "^4.5.2", - "qase-javascript-commons": "~2.2.0", + "qase-javascript-commons": "~2.2.1", "uuid": "^9.0.0" }, "devDependencies": { @@ -15336,7 +15341,7 @@ }, "qase-newman": { "name": "newman-reporter-qase", - "version": "2.0.1", + "version": "2.1.0", "license": "Apache-2.0", "dependencies": { "qase-javascript-commons": "~2.2.0", @@ -15345,8 +15350,9 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@types/jest": "^29.5.2", - "@types/newman": "^5.3.3", + "@types/newman": "^5.3.6", "@types/postman-collection": "^3.5.7", + "ajv": "^8.17.1", "jest": "^29.5.0", "postman-collection": "^4.1.7", "ts-jest": "^29.1.0" @@ -15360,7 +15366,7 @@ }, "qase-playwright": { "name": "playwright-qase-reporter", - "version": "2.0.13", + "version": "2.0.15", "license": "Apache-2.0", "dependencies": { "chalk": "^4.1.2", @@ -15382,7 +15388,7 @@ }, "qase-testcafe": { "name": "testcafe-reporter-qase", - "version": "2.0.2", + "version": "2.0.3", "license": "Apache-2.0", "dependencies": { "qase-javascript-commons": "~2.2.0", diff --git a/qase-newman/changelog.md b/qase-newman/changelog.md index b627b1cd..75766c68 100644 --- a/qase-newman/changelog.md +++ b/qase-newman/changelog.md @@ -1,4 +1,4 @@ -# qase-newman@2.1.0 +# qase-newman@2.0.2 ## What's new diff --git a/qase-newman/docs/usage.md b/qase-newman/docs/usage.md index 9a8a40ed..a5b85172 100644 --- a/qase-newman/docs/usage.md +++ b/qase-newman/docs/usage.md @@ -55,5 +55,26 @@ When you run the tests, the following behavior is expected: - In the **`Status code is 201`** test, both `userId` and `user.name` will be passed as parameters. - In the **`Response has correct userId`** test, only the `userId` parameter will be passed. -- In the **`Response has correct name`** test, all relevant parameters from the data file will be passed, including - `userId`, `user.name`, and `user.age`. +- In the **`Response has correct name`** test, by default, test will not have any parameters passed. But you can enable + specific option in config file to pass all parameters from data file if test have not commented `qase.parameters` + line. + + ```json + { + "debug": true, + "testops": { + "api": { + "token": "api_key" + }, + "project": "project_code", + "run": { + "complete": true + } + }, + "framework": { + "newman": { + "autoCollectParams": true + } + } + } + ``` diff --git a/qase-newman/package.json b/qase-newman/package.json index 28961502..b0e60e82 100644 --- a/qase-newman/package.json +++ b/qase-newman/package.json @@ -1,6 +1,6 @@ { "name": "newman-reporter-qase", - "version": "2.1.0", + "version": "2.0.2", "description": "Qase TMS Newman Reporter", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -47,6 +47,7 @@ "@types/jest": "^29.5.2", "@types/newman": "^5.3.6", "@types/postman-collection": "^3.5.7", + "ajv": "^8.17.1", "jest": "^29.5.0", "postman-collection": "^4.1.7", "ts-jest": "^29.1.0" diff --git a/qase-newman/src/configSchema.ts b/qase-newman/src/configSchema.ts new file mode 100644 index 00000000..d7328746 --- /dev/null +++ b/qase-newman/src/configSchema.ts @@ -0,0 +1,31 @@ +import { JSONSchemaType } from 'ajv'; + +import { FrameworkOptionsType } from 'qase-javascript-commons'; + +import { ReporterOptionsType } from './options'; + +export const configSchema: JSONSchemaType> = { + type: 'object', + nullable: true, + + properties: { + framework: { + type: 'object', + nullable: true, + + properties: { + newman: { + type: 'object', + nullable: true, + + properties: { + autoCollectParams: { + type: 'boolean', + nullable: true, + }, + }, + }, + }, + }, + }, +}; diff --git a/qase-newman/src/options.ts b/qase-newman/src/options.ts new file mode 100644 index 00000000..177a8ec1 --- /dev/null +++ b/qase-newman/src/options.ts @@ -0,0 +1,3 @@ +export interface ReporterOptionsType { + autoCollectParams?: boolean; +} diff --git a/qase-newman/src/reporter.ts b/qase-newman/src/reporter.ts index ed3ad628..4bc44326 100644 --- a/qase-newman/src/reporter.ts +++ b/qase-newman/src/reporter.ts @@ -1,5 +1,6 @@ import { EventEmitter } from 'events'; +import { configSchema } from './configSchema'; import semver from 'semver'; import { NewmanRunExecution, NewmanRunOptions } from 'newman'; import { EventList, PropertyBase, PropertyBaseDefinition } from 'postman-collection'; @@ -107,21 +108,30 @@ export class NewmanQaseReporter { * @private */ private reporter: ReporterInterface; + /** * @type {Map} * @private */ - private pendingResultMap = new Map(); + private pendingResultMap: Map = new Map(); + /** * @type {Map} * @private */ - private timerMap = new Map(); + private timerMap: Map = new Map(); + /** * @type {Record[]} * @private */ - private parameters: Record[] = []; + private readonly parameters: Record[] = []; + + /** + * @type {boolean} + * @private + */ + private autoCollectParams: boolean; /** * @param {EventEmitter} emitter @@ -133,7 +143,7 @@ export class NewmanQaseReporter { emitter: EventEmitter, options: NewmanQaseOptionsType, collectionOptions: NewmanRunOptions, - configLoader = new ConfigLoader(), + configLoader = new ConfigLoader(configSchema), ) { const config = configLoader.load(); @@ -144,6 +154,8 @@ export class NewmanQaseReporter { reporterName: 'newman-reporter-qase', }); + this.autoCollectParams = config?.framework?.newman?.autoCollectParams ?? false; + this.parameters = this.getParameters(collectionOptions.iterationData); this.addRunnerListeners(emitter); } @@ -305,7 +317,11 @@ export class NewmanQaseReporter { const params = NewmanQaseReporter.getParameters(events); if (params.length === 0) { - return availableParameters; + if (this.autoCollectParams) { + return availableParameters; + } + + return {}; } return params.reduce>((filteredParams, param) => {