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) => {