@virag8/azure-devops-test-publisher 1.2.1 → 1.2.2

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
@@ -5,12 +5,17 @@ Publish automated test results and failure screenshots from WebdriverIO (Mocha o
5
5
  ## Features
6
6
 
7
7
  - **Single shared Test Run** — creates one Azure DevOps Test Run for the whole suite in the launcher process and shares its id with all parallel workers via an environment variable, so results from every spec land in the same run.
8
+ - **Only executed tests appear in the run** — the run is created empty and test points are attached as tests finish, so cases in the suite that never ran are not left sitting in an _In progress_ state.
8
9
  - **Mocha support** — extracts the Azure DevOps test case id from a test title (e.g. `C1234 login works`) via the `afterTest` hook.
9
10
  - **Cucumber / BDD support** — extracts the test case id from a scenario's `@C1234` tag, falling back to the scenario name, via the `afterScenario` hook.
10
11
  - **Custom case id pattern** — override the default `C123`/`#123` matcher with your own regex (e.g. `TC-(\d+)`) via `caseIdPattern`.
11
12
  - **Automatic failure screenshots** — captures a browser screenshot and attaches it to the Azure DevOps result whenever a test/scenario fails (toggle with `screenshotOnFailure`).
12
- - **Run reuse** — publish into an existing run (`runId`), or keep a single run open across multiple publishes (`reuseTestRun` / `keepRunOpen`) instead of creating a new run every time.
13
+ - **Configuration-aware publishing** — scope a publish to a single Azure DevOps configuration (Android vs iOS, Chrome vs Firefox, …) so parallel jobs never overwrite each other's results for the same test case.
14
+ - **Run reuse** — publish into an existing run (`runId`), or keep a single run open across multiple publishes (`reuseTestRun` / `keepRunOpen`) instead of creating a new run every time. A run is only created when no run id is supplied — `0` counts as "not supplied".
15
+ - **"Run by" populated** — results are stamped with the identity that owns the PAT instead of showing an empty _Run by_ column.
13
16
  - **Point pre-fetch** — pass already-fetched test points via `PublishOptions.points` to skip a redundant Azure DevOps API call.
17
+ - **Fail-fast option validation** — a missing `orgUrl`, `projectId`, `planId` or `suiteId` throws a typed `AzureDevOpsConfigError` listing every offending key, at construction time rather than mid-run.
18
+ - **Actionable diagnostics** — warnings name the project, plan, suite, configuration and the exact test case ids that could not be matched.
14
19
  - **Resilient publishing** — publish failures are caught and logged so a flaky Azure DevOps API never fails the test run itself.
15
20
  - **Standalone reporter service** — `AzureDevOpsReporterService` for custom/non-service integrations that just need `afterTest` + `onComplete` hooks.
16
21
  - **Quiet by default** — set `debug: true` to log raw Azure DevOps API payloads while troubleshooting.
@@ -85,6 +90,97 @@ If no tag is present, the case id is parsed from the scenario name instead.
85
90
 
86
91
  The pattern must contain exactly one capturing group for the numeric id.
87
92
 
93
+ ### Test configurations (Android vs iOS, Chrome vs Firefox, …)
94
+
95
+ If the same test case exists in your suite under several Azure DevOps _configurations_, tell each worker which configuration it represents. Without this, a result published for one configuration can overwrite another configuration's result for the same case.
96
+
97
+ ```js
98
+ {
99
+ configurationId: Number(process.env.ADO_CONFIGURATION_ID), // e.g. 1042 = Android
100
+ }
101
+ ```
102
+
103
+ With `configurationId` set, the service:
104
+
105
+ - attaches only the test point belonging to that configuration to the run, and
106
+ - updates only the result for that configuration, leaving the others untouched.
107
+
108
+ Run one WebdriverIO process per configuration:
109
+
110
+ ```bash
111
+ ADO_CONFIGURATION_ID=1042 npx wdio run wdio.android.conf.ts
112
+ ADO_CONFIGURATION_ID=1043 npx wdio run wdio.ios.conf.ts
113
+ ```
114
+
115
+ Find the id under **Test Plans → Configurations**, or at `https://dev.azure.com/<org>/<project>/_apis/test/configurations`.
116
+
117
+ > While `configurationId` is set, points and results that have no configuration are skipped. Leave it unset for suites that don't use configurations.
118
+
119
+ ### Reusing an existing Test Run
120
+
121
+ A run is created in `onPrepare` **only** when no run id is available. The id is looked up in this order:
122
+
123
+ 1. the `AZURE_DEVOPS_TEST_RUN_ID` environment variable, then
124
+ 2. the `runId` service option.
125
+
126
+ Both `0` and an unset value mean "create a new run". To publish into a run created elsewhere (e.g. by an earlier pipeline stage):
127
+
128
+ ```bash
129
+ # PowerShell
130
+ $env:AZURE_DEVOPS_TEST_RUN_ID = "12345"; npx wdio run wdio.conf.ts
131
+
132
+ # bash
133
+ AZURE_DEVOPS_TEST_RUN_ID=12345 npx wdio run wdio.conf.ts
134
+ ```
135
+
136
+ The variable name is exported as a constant so you don't have to hardcode it:
137
+
138
+ ```ts
139
+ import { RUN_ID_ENV_VAR } from "@virag8/azure-devops-test-publisher";
140
+
141
+ console.log(`Publishing into run ${process.env[RUN_ID_ENV_VAR]}`);
142
+ ```
143
+
144
+ Workers inherit the variable from the launcher process, so it is readable inside specs and hooks. It is cleared again in `onComplete` and does not propagate back to the shell that started WebdriverIO.
145
+
146
+ ### Validating configuration early
147
+
148
+ `orgUrl`, `projectId`, `planId` and `suiteId` are mandatory. If any is missing or malformed, construction throws `AzureDevOpsConfigError` before a single test runs:
149
+
150
+ ```ts
151
+ import {
152
+ AzureDevOpsService,
153
+ AzureDevOpsConfigError,
154
+ } from "@virag8/azure-devops-test-publisher";
155
+
156
+ try {
157
+ new AzureDevOpsService(options);
158
+ } catch (err) {
159
+ if (err instanceof AzureDevOpsConfigError) {
160
+ console.error("Bad Azure DevOps config:", err.missing); // e.g. ["planId", "suiteId"]
161
+ }
162
+ }
163
+ ```
164
+
165
+ ```
166
+ AzureDevOpsConfigError: Missing or invalid Azure DevOps option(s): planId, suiteId.
167
+ Provide them when constructing the service or in the wdio service options.
168
+ ```
169
+
170
+ A missing `token` is **not** an error — it simply disables publishing with a warning, which keeps local runs working without a PAT.
171
+
172
+ ### Troubleshooting unmatched test cases
173
+
174
+ When a case id from a title or tag has no matching test point, the exact ids are logged:
175
+
176
+ ```
177
+ No test point found for test case id(s) 9999 in project "MyProject", plan 123, suite 456, configuration 1042.
178
+ The suite exposes 12 point(s) for case id(s) 1001, 1002, … Check that the case ids in your test
179
+ titles belong to this plan/suite and configuration.
180
+ ```
181
+
182
+ Common causes: the case lives in a different suite, the suite id belongs to another plan, or the worker's `configurationId` doesn't match the point's configuration. Set `debug: true` to also dump the raw API payloads.
183
+
88
184
  ## Usage as a standalone reporter
89
185
 
90
186
  For custom runners that aren't WebdriverIO services, use `AzureDevOpsReporterService` directly:
@@ -132,22 +228,37 @@ await ado.publishResults([
132
228
  ]);
133
229
  ```
134
230
 
231
+ Scope a publish to one configuration, and keep the run open for later batches:
232
+
233
+ ```ts
234
+ const runId = await ado.createRun();
235
+
236
+ await ado.publishResults(
237
+ [{ testCaseId: 1234, outcome: "Failed", errorMessage: "boom" }],
238
+ { runId, configurationId: 1042, keepRunOpen: true },
239
+ );
240
+
241
+ await ado.completeRun(runId);
242
+ ```
243
+
244
+ `createRun()` creates an **empty** run; points are attached by `publishResults` as results come in, so cases that never executed stay out of the run.
245
+
135
246
  ## Configuration reference
136
247
 
137
248
  ### `AzureDevOpsOptions`
138
249
 
139
- | Option | Type | Description |
140
- | --------------- | ---------- | ------------------------------------------------------------------- |
141
- | `orgUrl` | `string` | Azure DevOps organization URL. |
142
- | `token` | `string` | Personal access token with Test Plan read/write permissions. |
143
- | `projectId` | `string` | Azure DevOps project **name or id (GUID)** — both are accepted. |
144
- | `planId` | `number` | Test plan id. |
145
- | `suiteId` | `number` | Test suite id within the plan. |
146
- | `runName` | `string?` | Custom name for created runs. |
147
- | `runId` | `number?` | Reuse this existing run instead of creating a new one. |
148
- | `reuseTestRun` | `boolean?` | Keep a single run open across multiple `publishResults` calls. |
149
- | `caseIdPattern` | `RegExp?` | Custom regex (one capturing group) for extracting the test case id. |
150
- | `debug` | `boolean?` | Log raw Azure DevOps API payloads. Defaults to `false`. |
250
+ | Option | Type | Description |
251
+ | --------------- | ---------- | ------------------------------------------------------------------------------------ |
252
+ | `orgUrl` | `string` | Azure DevOps organization URL. |
253
+ | `token` | `string` | Personal access token with Test Plan read/write permissions. |
254
+ | `projectId` | `string` | Azure DevOps project **name or id (GUID)** — both are accepted. |
255
+ | `planId` | `number` | Test plan id. |
256
+ | `suiteId` | `number` | Test suite id within the plan. |
257
+ | `runName` | `string?` | Custom name for created runs. |
258
+ | `runId` | `number?` | Reuse this existing run instead of creating a new one. `0` means "create a new run". |
259
+ | `reuseTestRun` | `boolean?` | Keep a single run open across multiple `publishResults` calls. |
260
+ | `caseIdPattern` | `RegExp?` | Custom regex (one capturing group) for extracting the test case id. |
261
+ | `debug` | `boolean?` | Log raw Azure DevOps API payloads. Defaults to `false`. |
151
262
 
152
263
  > **Note on `projectId`** — despite the name, this accepts either the project's display name (`"MyProject"`) or its GUID (`"b9e8c7cb-..."`). Prefer the GUID: it stays stable if the project is ever renamed, and it avoids URL-encoding issues with names that contain spaces. You can find it at `https://dev.azure.com/<org>/_apis/projects`.
153
264
 
@@ -160,11 +271,19 @@ await ado.publishResults([
160
271
 
161
272
  ### `PublishOptions`
162
273
 
163
- | Option | Type | Description |
164
- | ------------- | -------------- | ----------------------------------------------------------------------- |
165
- | `runId` | `number?` | Publish into this existing run instead of creating a new one. |
166
- | `points` | `TestPoint[]?` | Reuse already-fetched test points instead of calling `getPoints` again. |
167
- | `keepRunOpen` | `boolean?` | Leave the run in progress so more results can be added later. |
274
+ | Option | Type | Description |
275
+ | ----------------- | -------------- | --------------------------------------------------------------------------------------------------------- |
276
+ | `runId` | `number?` | Publish into this existing run instead of creating a new one. `0` is treated as "not set". |
277
+ | `points` | `TestPoint[]?` | Reuse already-fetched test points instead of calling `getPoints` again. |
278
+ | `configurationId` | `number?` | Only publish to the test point/result of this configuration id; points and results in others are skipped. |
279
+ | `keepRunOpen` | `boolean?` | Leave the run in progress so more results can be added later. |
280
+
281
+ ### `AzureDevOpsConfigError`
282
+
283
+ | Member | Type | Description |
284
+ | --------- | ---------- | ------------------------------------------------------- |
285
+ | `missing` | `string[]` | The mandatory option keys that were missing or invalid. |
286
+ | `message` | `string` | Human readable summary listing every offending key. |
168
287
 
169
288
  ## Development
170
289
 
@@ -174,6 +293,8 @@ npm run test:real # integration tests against a real Azure DevOps org (require
174
293
  npm run build # compile to dist/
175
294
  ```
176
295
 
296
+ `npm run test:real` reads its credentials from a local `.env` file (`AZURE_ORG_URL`, `AZURE_PAT`, `AZURE_PROJECT`, `AZURE_PLAN_ID`, `AZURE_SUITE_ID`, `AZURE_TEST_CASE_ID`) and skips itself when they are absent.
297
+
177
298
  ## License
178
299
 
179
300
  MIT
@@ -36,6 +36,7 @@ export default class AzureDevOpsWdioService implements Services.ServiceInstance
36
36
  publish(): Promise<void>;
37
37
  private captureScreenshot;
38
38
  private extractCucumberCaseId;
39
+ /** `0` is treated as "not set", so a fresh run gets created. */
39
40
  private resolveRunId;
40
41
  private getService;
41
42
  }
@@ -19,6 +19,12 @@ class AzureDevOpsWdioService {
19
19
  }
20
20
  // --- launcher process hooks ---
21
21
  async onPrepare() {
22
+ const existingRunId = this.resolveRunId();
23
+ if (existingRunId) {
24
+ process.env[exports.RUN_ID_ENV_VAR] = existingRunId.toString();
25
+ console.log(`Reusing Azure DevOps test run: ${existingRunId}`);
26
+ return;
27
+ }
22
28
  const runId = await this.getService().createRun();
23
29
  if (runId === undefined)
24
30
  return;
@@ -140,10 +146,12 @@ class AzureDevOpsWdioService {
140
146
  });
141
147
  return fromName;
142
148
  }
149
+ /** `0` is treated as "not set", so a fresh run gets created. */
143
150
  resolveRunId() {
144
151
  const fromEnv = process.env[exports.RUN_ID_ENV_VAR];
145
152
  const parsed = fromEnv ? parseInt(fromEnv, 10) : NaN;
146
- return Number.isNaN(parsed) ? this._options.runId : parsed;
153
+ const runId = Number.isNaN(parsed) ? this._options.runId : parsed;
154
+ return runId || undefined;
147
155
  }
148
156
  getService() {
149
157
  this.service ??= new azureService_1.AzureDevOpsService(this._options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@virag8/azure-devops-test-publisher",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "Upload test results and screenshots to Azure DevOps Test Plans from WebdriverIO or custom TS runners",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",