ado-sync 0.1.24 → 0.1.27

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.
Files changed (46) hide show
  1. package/README.md +240 -678
  2. package/dist/azure/client.d.ts +3 -0
  3. package/dist/azure/client.js +6 -0
  4. package/dist/azure/client.js.map +1 -1
  5. package/dist/azure/test-cases.d.ts +8 -3
  6. package/dist/azure/test-cases.js +406 -25
  7. package/dist/azure/test-cases.js.map +1 -1
  8. package/dist/cli.js +51 -4
  9. package/dist/cli.js.map +1 -1
  10. package/dist/config.js +111 -5
  11. package/dist/config.js.map +1 -1
  12. package/dist/parsers/csharp.d.ts +30 -0
  13. package/dist/parsers/csharp.js +257 -0
  14. package/dist/parsers/csharp.js.map +1 -0
  15. package/dist/parsers/gherkin.d.ts +4 -1
  16. package/dist/parsers/gherkin.js +19 -4
  17. package/dist/parsers/gherkin.js.map +1 -1
  18. package/dist/parsers/java.d.ts +40 -0
  19. package/dist/parsers/java.js +329 -0
  20. package/dist/parsers/java.js.map +1 -0
  21. package/dist/parsers/javascript.d.ts +33 -0
  22. package/dist/parsers/javascript.js +261 -0
  23. package/dist/parsers/javascript.js.map +1 -0
  24. package/dist/parsers/markdown.d.ts +4 -1
  25. package/dist/parsers/markdown.js +5 -3
  26. package/dist/parsers/markdown.js.map +1 -1
  27. package/dist/parsers/python.d.ts +34 -0
  28. package/dist/parsers/python.js +305 -0
  29. package/dist/parsers/python.js.map +1 -0
  30. package/dist/parsers/shared.d.ts +18 -0
  31. package/dist/parsers/shared.js +40 -0
  32. package/dist/parsers/shared.js.map +1 -1
  33. package/dist/sync/engine.js +114 -5
  34. package/dist/sync/engine.js.map +1 -1
  35. package/dist/sync/publish-results.d.ts +49 -0
  36. package/dist/sync/publish-results.js +476 -0
  37. package/dist/sync/publish-results.js.map +1 -0
  38. package/dist/sync/writeback.d.ts +57 -1
  39. package/dist/sync/writeback.js +243 -0
  40. package/dist/sync/writeback.js.map +1 -1
  41. package/dist/types.d.ts +159 -2
  42. package/docs/advanced.md +350 -0
  43. package/docs/configuration.md +293 -0
  44. package/docs/publish-test-results.md +317 -0
  45. package/docs/spec-formats.md +595 -0
  46. package/package.json +1 -1
@@ -0,0 +1,350 @@
1
+ # Advanced configuration
2
+
3
+ ---
4
+
5
+ ## Format configuration
6
+
7
+ `sync.format` controls how test case content is structured when pushed to Azure DevOps.
8
+
9
+ | Field | Default | Description |
10
+ |-------|---------|-------------|
11
+ | `prefixTitle` | `true` | Prefix TC title with `"Scenario: "` or `"Scenario Outline: "`. Set `false` to use the raw scenario name. |
12
+ | `prefixBackgroundSteps` | `true` | Include Background steps in the TC steps list, prefixed with `"Background: "`. Set `false` to exclude them. |
13
+ | `useExpectedResult` | `false` | When `true`, `Then`/`Verify` steps are moved to the Expected Result column instead of the Action column. |
14
+ | `syncDataTableAsText` | `false` | When `true`, inline Gherkin data tables are appended to the step action as plain `\| cell \| cell \|` text instead of being handled as sub-steps. |
15
+ | `showParameterListStep` | `"whenUnusedParameters"` | Append a `Parameters: @p1@, @p2@, ...` step to parametrized TCs. `"always"` — always append. `"never"` — never append. `"whenUnusedParameters"` — append only when at least one parameter is not already referenced in a step. |
16
+ | `emptyActionValue` | *(blank)* | Value to use when a step action would be empty (e.g. when `useExpectedResult` moves a step to the expected column). |
17
+ | `emptyExpectedResultValue` | *(blank)* | Value to use when the expected result column would be empty. |
18
+
19
+ ### Example
20
+
21
+ ```json
22
+ {
23
+ "sync": {
24
+ "format": {
25
+ "prefixTitle": false,
26
+ "useExpectedResult": true,
27
+ "showParameterListStep": "always",
28
+ "emptyActionValue": "-"
29
+ }
30
+ }
31
+ }
32
+ ```
33
+
34
+ ---
35
+
36
+ ## State configuration
37
+
38
+ `sync.state` sets the Azure Test Case `State` field whenever a scenario is created or updated.
39
+
40
+ | Field | Description |
41
+ |-------|-------------|
42
+ | `setValueOnChangeTo` | The state value to set, e.g. `"Design"`, `"Ready"`. |
43
+ | `condition` | *(Optional)* Tag expression. Only scenarios matching this expression trigger the state change. |
44
+
45
+ ```json
46
+ {
47
+ "sync": {
48
+ "state": {
49
+ "setValueOnChangeTo": "Design",
50
+ "condition": "@active"
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Field updates
59
+
60
+ `sync.fieldUpdates` applies custom field values on push. Each key is an Azure DevOps field reference name (e.g. `"System.AreaPath"`) or display name.
61
+
62
+ ### Simple value (always set)
63
+
64
+ ```json
65
+ {
66
+ "sync": {
67
+ "fieldUpdates": {
68
+ "Custom.AutomationStatus": "Automated",
69
+ "System.AreaPath": "MyProject\\QA Team"
70
+ }
71
+ }
72
+ }
73
+ ```
74
+
75
+ ### Conditional value (switch by tag)
76
+
77
+ ```json
78
+ {
79
+ "sync": {
80
+ "fieldUpdates": {
81
+ "System.AreaPath": {
82
+ "conditionalValue": {
83
+ "@smoke": "MyProject\\Smoke",
84
+ "@regression": "MyProject\\Regression",
85
+ "otherwise": "MyProject\\General"
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ ### Tag wildcard capture
94
+
95
+ Wildcard `*` captures the matched portion and exposes it as `{1}`, `{2}`, ... in the value.
96
+
97
+ ```json
98
+ {
99
+ "sync": {
100
+ "fieldUpdates": {
101
+ "Custom.Priority": {
102
+ "condition": "@priority:*",
103
+ "value": "{1}"
104
+ }
105
+ }
106
+ }
107
+ }
108
+ ```
109
+
110
+ With tag `@priority:high`, this sets `Custom.Priority` to `"high"`.
111
+
112
+ ### Update event
113
+
114
+ Control when the update fires:
115
+
116
+ | `update` | Behaviour |
117
+ |----------|-----------|
118
+ | `"always"` *(default)* | Apply on every push (create and update). |
119
+ | `"onCreate"` | Apply only when the TC is being created for the first time. |
120
+ | `"onChange"` | Apply only when the TC already exists and is being updated. |
121
+
122
+ ```json
123
+ {
124
+ "sync": {
125
+ "fieldUpdates": {
126
+ "Custom.CreatedBySync": { "value": "true", "update": "onCreate" }
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ ### Placeholders
133
+
134
+ Value strings support these placeholders:
135
+
136
+ | Placeholder | Resolves to |
137
+ |-------------|-------------|
138
+ | `{scenario-name}` | Scenario title |
139
+ | `{feature-name}` | File name without extension |
140
+ | `{feature-file}` | File name with extension |
141
+ | `{scenario-description}` | Scenario description text |
142
+ | `{1}`, `{2}`, … | Wildcard captures from the `condition` |
143
+
144
+ ---
145
+
146
+ ## Customizations
147
+
148
+ ### Field defaults
149
+
150
+ Set default Azure field values applied only when a Test Case is **created** (not on updates).
151
+
152
+ ```json
153
+ {
154
+ "customizations": {
155
+ "fieldDefaults": {
156
+ "enabled": true,
157
+ "defaultValues": {
158
+ "System.State": "Design",
159
+ "Custom.AutomationStatus": "Planned"
160
+ }
161
+ }
162
+ }
163
+ }
164
+ ```
165
+
166
+ ### Ignore test case tags
167
+
168
+ Preserve Azure-side tags from being removed during push. Useful for tags managed by Azure DevOps workflows (e.g. `reviewed`, `approved`).
169
+
170
+ ```json
171
+ {
172
+ "customizations": {
173
+ "ignoreTestCaseTags": {
174
+ "enabled": true,
175
+ "tags": ["reviewed", "ado-*"]
176
+ }
177
+ }
178
+ }
179
+ ```
180
+
181
+ Patterns support a trailing `*` wildcard: `"ado-*"` matches any tag starting with `ado-`.
182
+
183
+ ### Tag text map transformation
184
+
185
+ Apply character or substring replacements to tags before they are pushed to Azure DevOps.
186
+
187
+ ```json
188
+ {
189
+ "customizations": {
190
+ "tagTextMapTransformation": {
191
+ "enabled": true,
192
+ "textMap": { "_": " " }
193
+ }
194
+ }
195
+ }
196
+ ```
197
+
198
+ With this config, `@my_feature_tag` is stored in Azure as `my feature tag`.
199
+
200
+ ---
201
+
202
+ ## Attachments
203
+
204
+ Attach files to Test Cases via tags.
205
+
206
+ ### Config
207
+
208
+ ```json
209
+ {
210
+ "sync": {
211
+ "attachments": {
212
+ "enabled": true,
213
+ "tagPrefixes": ["wireframe", "spec"],
214
+ "baseFolder": "specs/attachments"
215
+ }
216
+ }
217
+ }
218
+ ```
219
+
220
+ | Field | Default | Description |
221
+ |-------|---------|-------------|
222
+ | `enabled` | `false` | Enable attachment sync. |
223
+ | `tagPrefixes` | `[]` | Additional tag prefixes beyond the built-in `attachment`. |
224
+ | `baseFolder` | *(feature file dir)* | Base directory for resolving file paths. Relative to config file. |
225
+
226
+ ### Usage
227
+
228
+ ```gherkin
229
+ @tc:1042 @attachment:screenshots/login.png @wireframe:mockups/login.fig
230
+ Scenario: Login page
231
+ ...
232
+ ```
233
+
234
+ The default `attachment` prefix is always active when `enabled: true`. Additional prefixes are configured via `tagPrefixes`.
235
+
236
+ File paths support glob patterns: `@attachment:screenshots/*.png` attaches all matching files.
237
+
238
+ Files are uploaded to the Azure Work Item as attachments. Already-attached files (by name) are not re-uploaded.
239
+
240
+ ---
241
+
242
+ ## Pull configuration
243
+
244
+ ### Pull-create: generate local files from Azure
245
+
246
+ When `sync.pull.enableCreatingNewLocalTestCases` is `true`, a `pull` run will create new local spec files for Azure Test Cases that have no local counterpart (i.e. they exist in the configured suite but have no `@tc:ID` anywhere in the local files).
247
+
248
+ ```json
249
+ {
250
+ "sync": {
251
+ "pull": {
252
+ "enableCreatingNewLocalTestCases": true,
253
+ "targetFolder": "specs/pulled"
254
+ }
255
+ }
256
+ }
257
+ ```
258
+
259
+ | Field | Default | Description |
260
+ |-------|---------|-------------|
261
+ | `enableCreatingNewLocalTestCases` | `false` | When `true`, `pull` creates local files for unlinked Azure TCs. |
262
+ | `targetFolder` | `.` (config dir) | Directory where new files are created. Relative to config file. |
263
+
264
+ Generated files use the format matching `local.type` (`.feature` for Gherkin, `.md` for Markdown). The `@tc:ID` tag is written into the file so subsequent pushes link back to the same TC.
265
+
266
+ ---
267
+
268
+ ## Suite hierarchy
269
+
270
+ By default, all Test Cases go into a single flat suite (`suiteMapping: "flat"`). Setting `suiteMapping: "byFolder"` mirrors the local folder structure as nested child suites in Azure.
271
+
272
+ ```json
273
+ {
274
+ "testPlan": {
275
+ "id": 1234,
276
+ "suiteId": 5678,
277
+ "suiteMapping": "byFolder"
278
+ }
279
+ }
280
+ ```
281
+
282
+ ```
283
+ specs/
284
+ login/
285
+ basic.feature → suite "login" → TC "Successful login"
286
+ checkout/
287
+ happy.feature → suite "checkout" → TC "Add item and checkout"
288
+ ```
289
+
290
+ Child suites are created automatically if they do not exist. The suite hierarchy is re-used across runs.
291
+
292
+ ---
293
+
294
+ ## Conflict detection
295
+
296
+ ado-sync uses a local state cache (`.ado-sync-state.json`) to detect conflicts — cases where **both** the local file and the Azure Test Case were changed since the last sync.
297
+
298
+ The `sync.conflictAction` setting controls what happens:
299
+
300
+ | Value | Behaviour |
301
+ |-------|-----------|
302
+ | `"overwrite"` *(default)* | Push local version to Azure, overwriting the remote change. |
303
+ | `"skip"` | Emit a `!` conflict result and leave both sides unchanged. |
304
+ | `"fail"` | Throw an error listing all conflicting scenarios and abort. |
305
+
306
+ ```json
307
+ { "sync": { "conflictAction": "skip" } }
308
+ ```
309
+
310
+ **Commit `.ado-sync-state.json` to version control** so all team members and CI share the same last-synced state.
311
+
312
+ The cache also speeds up `push` — unchanged scenarios (same local hash + same Azure `changedDate`) are skipped without an API call.
313
+
314
+ To reset the cache: delete `.ado-sync-state.json`. The next push re-populates it from Azure.
315
+
316
+ ---
317
+
318
+ ## CI / build server mode
319
+
320
+ Set `sync.disableLocalChanges: true` to prevent ado-sync from writing back to local files:
321
+
322
+ - `push` — creates and updates Test Cases in Azure, but does **not** write ID tags to local files.
323
+ - `pull` — computes what would change but does **not** modify local files (behaves like `--dry-run`).
324
+
325
+ ```json
326
+ { "sync": { "disableLocalChanges": true } }
327
+ ```
328
+
329
+ Or per-run via `--config-override`:
330
+
331
+ ```bash
332
+ ado-sync push --config-override sync.disableLocalChanges=true
333
+ ```
334
+
335
+ ### GitHub Actions example
336
+
337
+ ```yaml
338
+ - name: Sync test cases to Azure DevOps
339
+ run: ado-sync push --config-override sync.disableLocalChanges=true
340
+ env:
341
+ AZURE_DEVOPS_TOKEN: ${{ secrets.AZURE_DEVOPS_TOKEN }}
342
+ ```
343
+
344
+ ---
345
+
346
+ ## Removed scenario detection
347
+
348
+ When a scenario is deleted from a local file but its Test Case still exists in the Azure suite, ado-sync detects this on the next `push` and appends the tag `ado-sync:removed` to the Azure Test Case (without deleting it). A `−` removed line is printed in the output.
349
+
350
+ To completely remove the Test Case from Azure, delete it manually in Test Plans after reviewing.
@@ -0,0 +1,293 @@
1
+ # Configuration reference
2
+
3
+ Config files can be JSON (`.json`) or YAML (`.yml` / `.yaml`). Run `ado-sync init` to generate a template.
4
+
5
+ ---
6
+
7
+ ## Full example (JSON)
8
+
9
+ ```json
10
+ {
11
+ "orgUrl": "https://dev.azure.com/YOUR_ORG",
12
+ "project": "YOUR_PROJECT",
13
+ "configurationKey": "my-repo-smoke",
14
+
15
+ "auth": {
16
+ "type": "pat",
17
+ "token": "$AZURE_DEVOPS_TOKEN"
18
+ },
19
+
20
+ "testPlan": {
21
+ "id": 1234,
22
+ "suiteId": 5678,
23
+ "suiteMapping": "flat"
24
+ },
25
+
26
+ "local": {
27
+ "type": "gherkin",
28
+ "include": "specs/**/*.feature",
29
+ "exclude": [],
30
+ "condition": "@done and not @wip"
31
+ },
32
+
33
+ "toolSettings": {
34
+ "parentConfig": "../base-ado-sync.json",
35
+ "outputLevel": "normal"
36
+ },
37
+
38
+ "sync": {
39
+ "tagPrefix": "tc",
40
+ "titleField": "System.Title",
41
+ "areaPath": "MyProject\\Team A",
42
+ "iterationPath": "MyProject\\Sprint 1",
43
+ "disableLocalChanges": false,
44
+ "conflictAction": "overwrite",
45
+ "links": [
46
+ { "prefix": "story", "relationship": "System.LinkTypes.Related" },
47
+ { "prefix": "bug", "relationship": "System.LinkTypes.Related" }
48
+ ],
49
+ "state": {
50
+ "setValueOnChangeTo": "Design",
51
+ "condition": "@active"
52
+ },
53
+ "fieldUpdates": {
54
+ "System.AreaPath": {
55
+ "conditionalValue": {
56
+ "@smoke": "MyProject\\Smoke",
57
+ "otherwise": "MyProject\\Regression"
58
+ }
59
+ },
60
+ "Custom.Priority": {
61
+ "condition": "@priority:*",
62
+ "value": "{1}",
63
+ "update": "onCreate"
64
+ }
65
+ },
66
+ "format": {
67
+ "prefixTitle": true,
68
+ "prefixBackgroundSteps": true,
69
+ "useExpectedResult": false,
70
+ "syncDataTableAsText": false,
71
+ "showParameterListStep": "whenUnusedParameters"
72
+ },
73
+ "attachments": {
74
+ "enabled": true,
75
+ "tagPrefixes": ["wireframe", "spec"],
76
+ "baseFolder": "specs/attachments"
77
+ },
78
+ "pull": {
79
+ "enableCreatingNewLocalTestCases": false,
80
+ "targetFolder": "specs/pulled"
81
+ }
82
+ },
83
+
84
+ "customizations": {
85
+ "fieldDefaults": {
86
+ "enabled": true,
87
+ "defaultValues": {
88
+ "System.State": "Design",
89
+ "Custom.AutomationStatus": "Planned"
90
+ }
91
+ },
92
+ "ignoreTestCaseTags": {
93
+ "enabled": true,
94
+ "tags": ["reviewed", "ado-*"]
95
+ },
96
+ "tagTextMapTransformation": {
97
+ "enabled": true,
98
+ "textMap": { "_": " " }
99
+ }
100
+ }
101
+ }
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Top-level fields
107
+
108
+ ### `orgUrl`
109
+
110
+ Azure DevOps organisation URL. Format: `https://dev.azure.com/YOUR_ORG`.
111
+
112
+ ---
113
+
114
+ ### `project`
115
+
116
+ Azure DevOps project name.
117
+
118
+ ---
119
+
120
+ ### `configurationKey`
121
+
122
+ *(Optional)* A unique string identifying this config within the ADO project. Used to prevent conflicts when multiple ado-sync configs push into the same Test Plan (e.g. `"smoke-suite"`, `"regression-suite"`). When set, ID writeback tags are namespaced so that one config's tags don't collide with another's.
123
+
124
+ ---
125
+
126
+ ### `auth`
127
+
128
+ | Field | Description |
129
+ |-------|-------------|
130
+ | `type` | `"pat"` · `"accessToken"` · `"managedIdentity"` |
131
+ | `token` | PAT or access token value. Prefix with `$` to read from an environment variable (e.g. `"$AZURE_DEVOPS_TOKEN"`). |
132
+ | `applicationIdURI` | Required only when `type` is `"managedIdentity"`. |
133
+
134
+ **PAT permissions required:** Test Management (Read & Write), Work Items (Read & Write).
135
+
136
+ ---
137
+
138
+ ### `testPlan`
139
+
140
+ | Field | Default | Description |
141
+ |-------|---------|-------------|
142
+ | `id` | *(required)* | ID of the Azure DevOps Test Plan. |
143
+ | `suiteId` | plan root | ID of the Test Suite within the plan. Defaults to the plan's root suite. |
144
+ | `suiteMapping` | `"flat"` | `"flat"` — all TCs go into one suite. `"byFolder"` — folder structure mirrored as nested child suites. |
145
+
146
+ ---
147
+
148
+ ### `testPlans` (multi-plan)
149
+
150
+ Use `testPlans` instead of `testPlan` to sync one repo against multiple Test Plans.
151
+
152
+ ```json
153
+ {
154
+ "testPlans": [
155
+ { "id": 1001, "suiteId": 2001, "include": "specs/smoke/**/*.feature" },
156
+ { "id": 1002, "suiteId": 2002, "include": "specs/regression/**/*.feature", "suiteMapping": "byFolder" }
157
+ ]
158
+ }
159
+ ```
160
+
161
+ | Field | Description |
162
+ |-------|-------------|
163
+ | `id` | Test Plan ID |
164
+ | `suiteId` | *(Optional)* Target suite ID |
165
+ | `suiteMapping` | *(Optional)* `"flat"` or `"byFolder"` |
166
+ | `include` | *(Optional)* Override `local.include` for this plan |
167
+ | `exclude` | *(Optional)* Override `local.exclude` for this plan |
168
+
169
+ ---
170
+
171
+ ### `local`
172
+
173
+ | Field | Default | Description |
174
+ |-------|---------|-------------|
175
+ | `type` | *(required)* | `"gherkin"` · `"markdown"` · `"csv"` · `"excel"` |
176
+ | `include` | *(required)* | Glob pattern(s) relative to the config file. String or array. |
177
+ | `exclude` | *(none)* | Glob pattern(s) to exclude. String or array. |
178
+ | `condition` | *(none)* | Tag expression filter applied before `--tags`. Only scenarios matching this expression are included in sync. e.g. `"@done and not (@ignored or @planned)"` |
179
+
180
+ ---
181
+
182
+ ### `toolSettings`
183
+
184
+ | Field | Default | Description |
185
+ |-------|---------|-------------|
186
+ | `parentConfig` | *(none)* | Relative path to a parent config file. Child values override parent values (deep merge). Circular references are detected and rejected. |
187
+ | `ignoreParentConfig` | `false` | When `true`, the `parentConfig` reference is ignored. Useful to opt a child config out of inheritance temporarily. |
188
+ | `outputLevel` | `"normal"` | `"normal"` — show all results per-line. `"quiet"` — suppress `skipped` lines, show only actionable results. `"verbose"` — reserved for future detailed output. |
189
+
190
+ ---
191
+
192
+ ### `sync`
193
+
194
+ | Field | Default | Description |
195
+ |-------|---------|-------------|
196
+ | `tagPrefix` | `"tc"` | Prefix used in ID tags (`@tc:12345`). |
197
+ | `titleField` | `"System.Title"` | Azure DevOps field used as the test case title. |
198
+ | `areaPath` | *(none)* | Area path for newly created Test Cases. |
199
+ | `iterationPath` | *(none)* | Iteration path for newly created Test Cases. |
200
+ | `disableLocalChanges` | `false` | When `true`, no local files are modified (no ID writeback, no pull apply). Use in CI. |
201
+ | `conflictAction` | `"overwrite"` | `"overwrite"` · `"skip"` · `"fail"` — see [Conflict detection](advanced.md#conflict-detection). |
202
+ | `links` | `[]` | Work item link configs — see [Work item linking](spec-formats.md#work-item-linking). |
203
+ | `state` | *(none)* | Set TC State field on change — see [State configuration](advanced.md#state-configuration). |
204
+ | `fieldUpdates` | *(none)* | Custom field update rules — see [Field updates](advanced.md#field-updates). |
205
+ | `format` | *(none)* | TC content formatting options — see [Format configuration](advanced.md#format-configuration). |
206
+ | `attachments` | *(none)* | File attachment sync — see [Attachments](advanced.md#attachments). |
207
+ | `pull` | *(none)* | Pull-specific options — see [Pull configuration](advanced.md#pull-configuration). |
208
+ | `suiteConditions` | *(none)* | Per-tag suite routing rules. |
209
+
210
+ ---
211
+
212
+ ### `customizations`
213
+
214
+ Controls tag transformations applied before push. See [Customizations](advanced.md#customizations).
215
+
216
+ | Field | Description |
217
+ |-------|-------------|
218
+ | `fieldDefaults` | Default field values applied only on create. |
219
+ | `ignoreTestCaseTags` | Preserve certain Azure-side tags from being removed on push. |
220
+ | `tagTextMapTransformation` | Character/substring replacements applied to tags before push. |
221
+
222
+ ---
223
+
224
+ ### `publishTestResults`
225
+
226
+ Configuration for the `publish-test-results` command. See [Publishing test results](publish-test-results.md#configuration).
227
+
228
+ ---
229
+
230
+ ## YAML example
231
+
232
+ ```yaml
233
+ orgUrl: https://dev.azure.com/my-org
234
+ project: MyProject
235
+
236
+ auth:
237
+ type: pat
238
+ token: $AZURE_DEVOPS_TOKEN
239
+
240
+ testPlan:
241
+ id: 1234
242
+ suiteId: 5678
243
+ suiteMapping: byFolder
244
+
245
+ local:
246
+ type: gherkin
247
+ include: specs/**/*.feature
248
+ exclude:
249
+ - specs/archive/**
250
+ condition: "@done and not @wip"
251
+
252
+ toolSettings:
253
+ outputLevel: quiet
254
+
255
+ sync:
256
+ tagPrefix: tc
257
+ areaPath: "MyProject\\QA Team"
258
+ conflictAction: skip
259
+ links:
260
+ - prefix: story
261
+ relationship: System.LinkTypes.Related
262
+ format:
263
+ prefixTitle: true
264
+ useExpectedResult: false
265
+ ```
266
+
267
+ ---
268
+
269
+ ## Hierarchical config
270
+
271
+ Use `toolSettings.parentConfig` to share common settings across multiple config files.
272
+
273
+ ```
274
+ repo/
275
+ ado-sync-base.json ← org, project, auth, shared sync settings
276
+ smoke/
277
+ ado-sync.json ← testPlan.id: 1001, local.include: specs/smoke/**
278
+ regression/
279
+ ado-sync.json ← testPlan.id: 1002, local.include: specs/regression/**
280
+ ```
281
+
282
+ Child config:
283
+ ```json
284
+ {
285
+ "toolSettings": { "parentConfig": "../ado-sync-base.json" },
286
+ "testPlan": { "id": 1002, "suiteId": 3001 },
287
+ "local": { "type": "gherkin", "include": "specs/regression/**/*.feature" }
288
+ }
289
+ ```
290
+
291
+ Child values override parent values using a deep merge (arrays are replaced, not concatenated). Circular references are detected and throw an error.
292
+
293
+ Set `toolSettings.ignoreParentConfig: true` in any child to opt out of inheritance for that config without editing the parent.