newman-reporter-qase 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,90 +1,274 @@
1
- # Qase TMS Newman reporter
1
+ # [Qase TestOps](https://qase.io) Newman Reporter
2
2
 
3
- Publish results simple and easy.
3
+ [![License](https://lxgaming.github.io/badges/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
4
+ [![npm downloads](https://img.shields.io/npm/dm/newman-qase-reporter.svg)](https://www.npmjs.com/package/newman-qase-reporter)
4
5
 
5
- To install the latest version, run:
6
+ Qase Newman Reporter enables seamless integration between your Newman/Postman collection tests and [Qase TestOps](https://qase.io), providing automatic test result reporting, test case management, and comprehensive test analytics.
6
7
 
7
- ```bash
8
- npm install newman-reporter-qase
9
- ```
10
-
11
- ## Example of usage
8
+ ## Features
12
9
 
13
- ### Define in tests
10
+ - Link automated tests to Qase test cases by ID using special comments
11
+ - Auto-create test cases from your Postman test scripts
12
+ - Report test results with parameterized data
13
+ - Multi-project reporting support
14
+ - Flexible configuration (file, environment variables)
14
15
 
15
- The Newman reporter has the ability to auto-generate test cases
16
- and suites from your test data.
16
+ > **Note:** Newman integration is unique - tests are defined in Postman collections, and Qase IDs are specified via special comments in test scripts, not via programmatic API imports.
17
17
 
18
- But if necessary, you can independently register the ID of already
19
- existing test cases from TMS before the executing tests.
20
- Example:
18
+ ## Installation
21
19
 
22
- ```js
23
- //qase: 10
24
- // Qase: 1, 2, 3
25
- // qase: 4 5 6 14
26
- pm.test('expect response be 200', function() {
27
- pm.response.to.be.info
28
- })
20
+ ```bash
21
+ npm install --save-dev newman-reporter-qase
29
22
  ```
30
23
 
31
- ### Execute rom CLI:
24
+ ## Quick Start
32
25
 
33
- ```
34
- QASE_MODE=testops newman run ./sample-collection.json -r qase
26
+ **1. Create `qase.config.json` in your project root:**
27
+
28
+ ```json
29
+ {
30
+ "mode": "testops",
31
+ "testops": {
32
+ "project": "YOUR_PROJECT_CODE",
33
+ "api": {
34
+ "token": "YOUR_API_TOKEN"
35
+ }
36
+ }
37
+ }
35
38
  ```
36
39
 
37
- <p align="center">
38
- <img width="65%" src="./screenshots/screenshot.png">
39
- </p>
40
+ **2. Add Qase ID to your Postman test using special comments:**
40
41
 
41
- A test run will be performed and available at:
42
+ In your Postman collection test script:
42
43
 
43
- ```
44
- https://app.qase.io/run/QASE_PROJECT_CODE
44
+ ```javascript
45
+ // qase: 1
46
+ pm.test('Response status is 200', function() {
47
+ pm.response.to.have.status(200);
48
+ });
45
49
  ```
46
50
 
47
- <p align="center">
48
- <img src="./screenshots/demo.gif">
49
- </p>
51
+ **3. Run your Newman tests with Qase reporter:**
50
52
 
51
- You can find more information about using the reporter [here](./docs/usage.md).
53
+ ```bash
54
+ QASE_MODE=testops newman run ./collection.json -r qase
55
+ ```
52
56
 
53
57
  ## Configuration
54
58
 
55
- Qase Newman reporter can be configured in multiple ways:
59
+ The reporter is configured via (in order of priority):
60
+
61
+ 1. **Environment variables** (`QASE_*`)
62
+ 2. **Config file** (`qase.config.json`)
56
63
 
57
- - using a separate config file `qase.config.json`,
58
- - using environment variables (they override the values from the configuration files).
64
+ ### Minimal Configuration
59
65
 
60
- For a full list of configuration options, see
61
- the [Configuration reference](../qase-javascript-commons/README.md#configuration).
66
+ | Option | Environment Variable | Description |
67
+ |--------|---------------------|-------------|
68
+ | `mode` | `QASE_MODE` | Set to `testops` to enable reporting |
69
+ | `testops.project` | `QASE_TESTOPS_PROJECT` | Your Qase project code |
70
+ | `testops.api.token` | `QASE_TESTOPS_API_TOKEN` | Your Qase API token |
62
71
 
63
- Example `qase.config.json` config:
72
+ ### Example `qase.config.json`
64
73
 
65
74
  ```json
66
75
  {
67
76
  "mode": "testops",
68
- "debug": true,
77
+ "fallback": "report",
69
78
  "testops": {
79
+ "project": "YOUR_PROJECT_CODE",
70
80
  "api": {
71
- "token": "api_key"
81
+ "token": "YOUR_API_TOKEN"
72
82
  },
73
- "project": "project_code",
74
83
  "run": {
75
- "complete": true
84
+ "title": "Newman Automated Run"
85
+ },
86
+ "batch": {
87
+ "size": 100
88
+ }
89
+ },
90
+ "report": {
91
+ "driver": "local",
92
+ "connection": {
93
+ "local": {
94
+ "path": "./build/qase-report",
95
+ "format": "json"
96
+ }
76
97
  }
77
98
  }
78
99
  }
79
100
  ```
80
101
 
102
+ > **Full configuration reference:** See [qase-javascript-commons](../qase-javascript-commons/README.md) for all available options including logging, status mapping, execution plans, and more.
103
+
104
+ ## Usage
105
+
106
+ ### Link Tests with Test Cases
107
+
108
+ Associate your Postman tests with Qase test cases using special comments in your test scripts:
109
+
110
+ **Single ID:**
111
+ ```javascript
112
+ // qase: 10
113
+ pm.test('Response is successful', function() {
114
+ pm.response.to.be.info;
115
+ });
116
+ ```
117
+
118
+ **Multiple IDs:**
119
+ ```javascript
120
+ // Qase: 1, 2, 3
121
+ pm.test('Verify user data', function() {
122
+ pm.expect(pm.response.json()).to.have.property('user');
123
+ });
124
+ ```
125
+
126
+ **Alternative formats:**
127
+ ```javascript
128
+ // qase: 4 5 6 14
129
+ pm.test('Check multiple conditions', function() {
130
+ pm.response.to.have.status(200);
131
+ });
132
+ ```
133
+
134
+ > **Important:** The comment must be on the line immediately before the `pm.test()` call.
135
+
136
+ ### Add Parameters
137
+
138
+ Newman supports parameterized tests when using data files. Specify which parameters to report using special comments:
139
+
140
+ ```javascript
141
+ // qase.parameters: userId, user.name
142
+ pm.test('User ID is correct', function() {
143
+ var jsonData = pm.response.json();
144
+ pm.expect(jsonData.userId).to.eql(pm.iterationData.get('userid'));
145
+ });
146
+ ```
147
+
148
+ You can also specify parameters at the collection or folder level:
149
+
150
+ ```json
151
+ {
152
+ "item": [{
153
+ "name": "Folder Name",
154
+ "event": [{
155
+ "listen": "test",
156
+ "script": {
157
+ "exec": [
158
+ "// qase.parameters: userId, user.name"
159
+ ]
160
+ }
161
+ }]
162
+ }]
163
+ }
164
+ ```
165
+
166
+ ### Auto-collect All Parameters
167
+
168
+ To automatically report all parameters from data files without specifying them:
169
+
170
+ ```json
171
+ {
172
+ "framework": {
173
+ "newman": {
174
+ "autoCollectParams": true
175
+ }
176
+ }
177
+ }
178
+ ```
179
+
180
+ ### Ignore Tests
181
+
182
+ Newman does not support ignoring individual tests. All tests in the collection will be executed and reported (unless filtered by Newman's own mechanisms like `--folder` flag).
183
+
184
+ ### Test Result Statuses
185
+
186
+ | Newman Result | Qase Status |
187
+ |---------------|-------------|
188
+ | Passed | passed |
189
+ | Failed | failed |
190
+ | Skipped | skipped |
191
+
192
+ > For more usage examples, see the [Usage Guide](docs/usage.md).
193
+
194
+ ## Running Tests
195
+
196
+ ### Basic Execution with CLI
197
+
198
+ ```bash
199
+ # Run with Qase reporter
200
+ QASE_MODE=testops newman run ./collection.json -r qase
201
+
202
+ # Run with multiple reporters
203
+ QASE_MODE=testops newman run ./collection.json -r cli,qase
204
+
205
+ # Run specific folder
206
+ QASE_MODE=testops newman run ./collection.json -r qase --folder "API Tests"
207
+ ```
208
+
209
+ ### Using Data Files
210
+
211
+ ```bash
212
+ # Run with JSON data file
213
+ newman run ./collection.json -r qase -d ./data.json
214
+
215
+ # Run with CSV data file
216
+ newman run ./collection.json -r qase -d ./users.csv
217
+ ```
218
+
219
+ ### Using Environment Variables
220
+
221
+ ```bash
222
+ # Set environment variables
223
+ newman run ./collection.json -r qase -e ./environment.json
224
+
225
+ # Override config with environment variables
226
+ QASE_MODE=testops \
227
+ QASE_TESTOPS_PROJECT=DEMO \
228
+ QASE_TESTOPS_API_TOKEN=your_token \
229
+ newman run ./collection.json -r qase
230
+ ```
231
+
232
+ ### Programmatic Usage
233
+
234
+ ```javascript
235
+ const newman = require('newman');
236
+
237
+ newman.run({
238
+ collection: require('./collection.json'),
239
+ reporters: ['qase'],
240
+ reporter: {
241
+ qase: {
242
+ // Reporter options can be passed here
243
+ // But prefer using qase.config.json for consistency
244
+ }
245
+ }
246
+ }, function(err) {
247
+ if (err) { throw err; }
248
+ console.log('Collection run complete');
249
+ });
250
+ ```
251
+
81
252
  ## Requirements
82
253
 
83
- We maintain the reporter on LTS versions of Node. You can find the current versions by following
84
- the [link](https://nodejs.org/en/about/releases/)
254
+ - Node.js >= 14
255
+ - Newman >= 5.3.0
256
+
257
+ ## Documentation
258
+
259
+ | Guide | Description |
260
+ |-------|-------------|
261
+ | [Usage Guide](docs/usage.md) | Complete usage reference with parameters and examples |
262
+ | [Attachments](docs/ATTACHMENTS.md) | Adding screenshots, logs, and files to test results |
263
+ | [Steps](docs/STEPS.md) | Defining test steps for detailed reporting |
264
+ | [Multi-Project Support](docs/MULTI_PROJECT.md) | Reporting to multiple Qase projects |
265
+ | [Upgrade Guide](docs/UPGRADE.md) | Migration guide for breaking changes |
266
+ | [Configuration Reference](../qase-javascript-commons/README.md) | Full configuration options |
267
+
268
+ ## Examples
85
269
 
86
- `newman >= 5.3.0`
270
+ See the [examples directory](../examples/single/newman/) for complete working examples.
87
271
 
88
- <!-- references -->
272
+ ## License
89
273
 
90
- [auth]: https://developers.qase.io/#authentication
274
+ Apache License 2.0. See [LICENSE](LICENSE) for details.
package/changelog.md CHANGED
@@ -1,3 +1,14 @@
1
+ # newman-reporter-qase@2.3.0
2
+
3
+ ## Changed
4
+
5
+ - Decomposed `reporter.ts` (433 LOC) into a thin event-driven orchestrator plus 3 focused modules under `src/modules/` (`MetadataExtractor`, `IterationDataParser`, `ResultBuilder`). Public contract preserved 1:1: `NewmanQaseReporter` named export, all 3 static regexps (`qaseIdRegExp`, `qaseParamRegExp`, `qaseProjectRegExp`), all 4 static methods (`getCaseIds`, `getProjectMapping`, `getParameters`, `getParentTitles`), constructor signature, and `NewmanQaseOptionsType` re-export.
6
+ - Bumped `qase-javascript-commons` peer dependency to `~2.7.0` (sync with monorepo; no helper migrations — newman doesn't import from `qase-javascript-commons/internal`).
7
+
8
+ ## Internal
9
+
10
+ - Added per-module unit tests for the three new modules (37 new tests, 55 total).
11
+
1
12
  # qase-newman@2.2.0
2
13
 
3
14
  ## What's new
@@ -0,0 +1,5 @@
1
+ export declare class IterationDataParser {
2
+ parse(iterationData: unknown): Record<string, string>[];
3
+ private convertToRecord;
4
+ private isRecord;
5
+ }
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IterationDataParser = void 0;
4
+ class IterationDataParser {
5
+ parse(iterationData) {
6
+ if (!iterationData) {
7
+ return [];
8
+ }
9
+ if (Array.isArray(iterationData) &&
10
+ iterationData.every((item) => typeof item === 'object' && item !== null)) {
11
+ return iterationData.map((item) => this.convertToRecord(item));
12
+ }
13
+ return [];
14
+ }
15
+ convertToRecord(obj, parentKey = '') {
16
+ const record = {};
17
+ if (this.isRecord(obj)) {
18
+ for (const key in obj) {
19
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
20
+ const value = obj[key];
21
+ const newKey = parentKey ? `${parentKey}.${key}` : key;
22
+ if (this.isRecord(value)) {
23
+ Object.assign(record, this.convertToRecord(value, newKey));
24
+ }
25
+ else {
26
+ record[newKey.toLowerCase()] = String(value);
27
+ }
28
+ }
29
+ }
30
+ }
31
+ return record;
32
+ }
33
+ isRecord(obj) {
34
+ return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
35
+ }
36
+ }
37
+ exports.IterationDataParser = IterationDataParser;
@@ -0,0 +1,12 @@
1
+ import { EventList, Item, PropertyBase, PropertyBaseDefinition } from 'postman-collection';
2
+ import { TestopsProjectMapping } from 'qase-javascript-commons';
3
+ export declare class MetadataExtractor {
4
+ static qaseIdRegExp: RegExp;
5
+ static qaseParamRegExp: RegExp;
6
+ /** Matches // qase PROJ1: 1,2 or // qase PROJ2: 3 for multi-project. */
7
+ static qaseProjectRegExp: RegExp;
8
+ static getCaseIds(eventList: EventList): number[];
9
+ static getProjectMapping(eventList: EventList): TestopsProjectMapping;
10
+ static getParameters(item: Item): string[];
11
+ static getParentTitles(item: PropertyBase<PropertyBaseDefinition>): string[];
12
+ }
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MetadataExtractor = void 0;
4
+ // eslint-disable-next-line @typescript-eslint/no-extraneous-class
5
+ class MetadataExtractor {
6
+ static qaseIdRegExp = /\/\/\s*?[qQ]ase:\s?((?:[\d]+[\s,]{0,})+)/;
7
+ static qaseParamRegExp = /qase\.parameters:\s*([\w.]+(?:\s*,\s*[\w.]+)*)/i;
8
+ /** Matches // qase PROJ1: 1,2 or // qase PROJ2: 3 for multi-project. */
9
+ static qaseProjectRegExp = /\/\/\s*[qQ]ase\s+([A-Za-z0-9_]+):\s*([\d,\s]+)/g;
10
+ static getCaseIds(eventList) {
11
+ const ids = [];
12
+ eventList.each((event) => {
13
+ if (event.listen === 'test' && event.script.exec) {
14
+ event.script.exec.forEach((line) => {
15
+ const [, match] = line.match(MetadataExtractor.qaseIdRegExp) ?? [];
16
+ if (match) {
17
+ ids.push(...match.split(',').map((id) => Number(id)));
18
+ }
19
+ });
20
+ }
21
+ });
22
+ return ids;
23
+ }
24
+ static getProjectMapping(eventList) {
25
+ const projectMapping = {};
26
+ eventList.each((event) => {
27
+ if (event.listen === 'test' && event.script.exec) {
28
+ event.script.exec.forEach((line) => {
29
+ let m;
30
+ const re = new RegExp(MetadataExtractor.qaseProjectRegExp.source, 'gi');
31
+ while ((m = re.exec(line)) !== null) {
32
+ const projectCode = m[1]?.trim();
33
+ const idsStr = (m[2] ?? '').replace(/\s/g, '');
34
+ const ids = idsStr.split(',').map((s) => parseInt(s, 10)).filter((n) => !Number.isNaN(n));
35
+ if (projectCode && projectCode.toUpperCase() !== 'ID' && ids.length > 0) {
36
+ const existing = projectMapping[projectCode] ?? [];
37
+ projectMapping[projectCode] = [...existing, ...ids];
38
+ }
39
+ }
40
+ });
41
+ }
42
+ });
43
+ return projectMapping;
44
+ }
45
+ static getParameters(item) {
46
+ const params = [];
47
+ item.events.each((event) => {
48
+ if (event.listen === 'test' && event.script.exec) {
49
+ event.script.exec.forEach((line) => {
50
+ const match = line.match(MetadataExtractor.qaseParamRegExp);
51
+ if (match) {
52
+ const parameters = match[1]?.split(/\s*,\s*/) ?? [];
53
+ params.push(...parameters);
54
+ }
55
+ });
56
+ }
57
+ });
58
+ const parent = item.parent();
59
+ if (parent && 'events' in parent) {
60
+ params.push(...MetadataExtractor.getParameters(parent));
61
+ }
62
+ return params;
63
+ }
64
+ static getParentTitles(item) {
65
+ let titles = [];
66
+ const parent = item.parent();
67
+ if (parent) {
68
+ titles = titles.concat(MetadataExtractor.getParentTitles(parent));
69
+ }
70
+ if ('name' in item) {
71
+ titles.push(String(item.name));
72
+ }
73
+ return titles;
74
+ }
75
+ }
76
+ exports.MetadataExtractor = MetadataExtractor;
@@ -0,0 +1,12 @@
1
+ import { Item } from 'postman-collection';
2
+ import { TestopsProjectMapping, TestResultType } from 'qase-javascript-commons';
3
+ export interface BuildPendingArgs {
4
+ item: Item;
5
+ suites: string[];
6
+ ids: number[];
7
+ projectMapping: TestopsProjectMapping;
8
+ signature: string;
9
+ }
10
+ export declare class ResultBuilder {
11
+ static buildPending(args: BuildPendingArgs): TestResultType;
12
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ResultBuilder = void 0;
4
+ const qase_javascript_commons_1 = require("qase-javascript-commons");
5
+ // eslint-disable-next-line @typescript-eslint/no-extraneous-class
6
+ class ResultBuilder {
7
+ static buildPending(args) {
8
+ const { item, suites, ids, projectMapping, signature } = args;
9
+ let relation = null;
10
+ if (suites.length > 0) {
11
+ const data = suites.map((title) => ({ title, public_id: null }));
12
+ relation = { suite: { data } };
13
+ }
14
+ return {
15
+ attachments: [],
16
+ author: null,
17
+ execution: {
18
+ status: qase_javascript_commons_1.TestStatusEnum.passed,
19
+ start_time: null,
20
+ end_time: null,
21
+ duration: 0,
22
+ stacktrace: null,
23
+ thread: null,
24
+ },
25
+ fields: {},
26
+ message: null,
27
+ muted: false,
28
+ params: {},
29
+ group_params: {},
30
+ relations: relation,
31
+ run_id: null,
32
+ signature,
33
+ steps: [],
34
+ testops_id: ids.length > 0 ? ids : null,
35
+ id: item.id,
36
+ title: item.name,
37
+ testops_project_mapping: Object.keys(projectMapping).length > 0 ? projectMapping : null,
38
+ };
39
+ }
40
+ }
41
+ exports.ResultBuilder = ResultBuilder;
@@ -1,113 +1,26 @@
1
1
  import { EventEmitter } from 'events';
2
2
  import { NewmanRunOptions } from 'newman';
3
- import { EventList, Item, PropertyBase, PropertyBaseDefinition } from 'postman-collection';
4
- import { ConfigType, TestopsProjectMapping, ConfigLoader } from 'qase-javascript-commons';
3
+ import { ConfigType, ConfigLoader } from 'qase-javascript-commons';
4
+ import { MetadataExtractor } from './modules/metadataExtractor';
5
5
  export type NewmanQaseOptionsType = ConfigType;
6
6
  /**
7
7
  * @class NewmanQaseReporter
8
8
  */
9
9
  export declare class NewmanQaseReporter {
10
- /**
11
- * @type {RegExp}
12
- */
13
10
  static qaseIdRegExp: RegExp;
14
- /**
15
- * @type {RegExp}
16
- */
17
11
  static qaseParamRegExp: RegExp;
18
- /** Matches // qase PROJ1: 1,2 or // qase PROJ2: 3 for multi-project. */
19
12
  static qaseProjectRegExp: RegExp;
20
- /**
21
- * Parse multi-project mapping from test script comments (e.g. // qase PROJ1: 1,2).
22
- * @param {EventList} eventList
23
- * @returns {TestopsProjectMapping}
24
- */
25
- static getProjectMapping(eventList: EventList): TestopsProjectMapping;
26
- /**
27
- * @param {EventList} eventList
28
- * @returns {number[]}
29
- */
30
- static getCaseIds(eventList: EventList): number[];
31
- /**
32
- * @param {Item} item
33
- * @returns {string[]}
34
- */
35
- static getParameters(item: Item): string[];
36
- /**
37
- * @param {PropertyBase<PropertyBaseDefinition>} item
38
- * @returns {string[]}
39
- */
40
- static getParentTitles(item: PropertyBase<PropertyBaseDefinition>): string[];
41
- /**
42
- * @type {ReporterInterface}
43
- * @private
44
- */
13
+ static getCaseIds: typeof MetadataExtractor.getCaseIds;
14
+ static getProjectMapping: typeof MetadataExtractor.getProjectMapping;
15
+ static getParameters: typeof MetadataExtractor.getParameters;
16
+ static getParentTitles: typeof MetadataExtractor.getParentTitles;
45
17
  private reporter;
46
- /**
47
- * @type {Map<string, TestResultType>}
48
- * @private
49
- */
50
18
  private pendingResultMap;
51
- /**
52
- * @type {Map<string, number>}
53
- * @private
54
- */
55
19
  private timerMap;
56
- /**
57
- * @type {Record<string, string>[]}
58
- * @private
59
- */
60
20
  private readonly parameters;
61
- /**
62
- * @type {boolean}
63
- * @private
64
- */
65
21
  private autoCollectParams;
66
- /**
67
- * @param {EventEmitter} emitter
68
- * @param {NewmanQaseOptionsType} options
69
- * @param {NewmanRunOptions} collectionOptions
70
- * @param {ConfigLoaderInterface} configLoader
71
- */
72
22
  constructor(emitter: EventEmitter, options: NewmanQaseOptionsType, collectionOptions: NewmanRunOptions, configLoader?: ConfigLoader<import("qase-javascript-commons").FrameworkOptionsType<"newman", import("./options").ReporterOptionsType>>);
73
- /**
74
- * @param {EventEmitter} runner
75
- * @private
76
- */
77
23
  private addRunnerListeners;
78
- /**
79
- * @private
80
- */
81
- private preventExit;
82
- /**
83
- * @param {string[]} suites
84
- * @param {string} title
85
- * @param {number[]} ids
86
- * @private
87
- */
88
- private getSignature;
89
- /**
90
- * @param {Item} item
91
- * @param {number} iteration
92
- * @returns {Record<string, string>}
93
- * @private
94
- */
95
24
  private prepareParameters;
96
- /**
97
- * @param {unknown} iterationData
98
- * @private
99
- */
100
- private getParameters;
101
- /**
102
- * @param {unknown} obj
103
- * @param parentKey
104
- * @returns {Record<string, string>}
105
- * @private
106
- */
107
- private convertToRecord;
108
- /**
109
- * @param {unknown} obj
110
- * @private
111
- */
112
- private isRecord;
25
+ private preventExit;
113
26
  }