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,595 @@
1
+ # Spec file formats
2
+
3
+ ---
4
+
5
+ ## Gherkin `.feature`
6
+
7
+ Set `local.type: "gherkin"`. Standard Gherkin syntax is supported:
8
+
9
+ - `Feature` / `Scenario` / `Scenario Outline` / `Background`
10
+ - `Given` / `When` / `Then` / `And` / `But`
11
+ - Feature-level and scenario-level tags
12
+ - `Scenario Outline` with `Examples` tables — creates a **single** parametrized Test Case in Azure, not one TC per row
13
+ - Inline data tables (synced as sub-steps or plain text — see [Format configuration](advanced.md#format-configuration))
14
+
15
+ ```gherkin
16
+ Feature: Checkout
17
+
18
+ Background:
19
+ Given I am on the storefront
20
+
21
+ @smoke
22
+ @tc:1041
23
+ Scenario: Add item and complete checkout
24
+ Given I am logged in as "standard_user"
25
+ When I add "Sauce Labs Backpack" to the cart
26
+ And I proceed through checkout with name "Test User" and zip "12345"
27
+ Then I see the order confirmation page
28
+
29
+ @tc:1042
30
+ Scenario Outline: Checkout with different users
31
+ Given I am logged in as "<user>"
32
+ When I complete a checkout
33
+ Then the result is "<result>"
34
+
35
+ Examples:
36
+ | user | result |
37
+ | standard_user | success |
38
+ | performance_glitch_user | success |
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Markdown `.md`
44
+
45
+ Set `local.type: "markdown"`. Each `### heading` is one test case. Scenarios are separated by `---`.
46
+
47
+ ```markdown
48
+ # My Feature Test Plan
49
+
50
+ ## Test scenarios
51
+
52
+ ### Login with valid credentials
53
+ @tc:1042 @smoke
54
+
55
+ Assumption: Fresh browser session.
56
+
57
+ Steps:
58
+ 1. Navigate to https://example.com/login
59
+ 2. Enter username "admin" and password "secret"
60
+ 3. Click the Login button
61
+
62
+ Expected results:
63
+ - The dashboard page is shown
64
+ - The username appears in the top navigation
65
+
66
+ ---
67
+
68
+ ### Login with invalid credentials
69
+
70
+ Steps:
71
+ 1. Navigate to https://example.com/login
72
+ 2. Enter username "wrong" and password "wrong"
73
+ 3. Click the Login button
74
+
75
+ Expected results:
76
+ - An error message "Invalid credentials" is displayed
77
+ - The user remains on the login page
78
+
79
+ ---
80
+ ```
81
+
82
+ Sections recognised (case-insensitive): `Steps:`, `Expected results:`. All other prose is captured as the test case description. Heading number prefixes (`### 1. Title` or `### Title`) are both supported.
83
+
84
+ ### Playwright test-plan markdown
85
+
86
+ Markdown files generated by the Playwright MCP agent are fully supported. Set `local.type: "markdown"` and point `local.include` at the generated files.
87
+
88
+ ---
89
+
90
+ ## C# MSTest / NUnit `.cs`
91
+
92
+ Set `local.type: "csharp"`. Both **MSTest** and **NUnit** frameworks are supported. Each test method becomes one Test Case.
93
+
94
+ ### Framework attribute mapping
95
+
96
+ | Concern | MSTest | NUnit |
97
+ |---------|--------|-------|
98
+ | Test marker | `[TestMethod]` | `[Test]` |
99
+ | Category / tag | `[TestCategory("name")]` | `[Category("name")]` |
100
+ | Custom property | `[TestProperty("key","val")]` | `[Property("key","val")]` |
101
+ | TC ID writeback | `[TestProperty("tc","ID")]` | `[Property("tc","ID")]` |
102
+
103
+ The framework is auto-detected per method — mixed files (e.g. a shared helper class) are handled correctly.
104
+
105
+ ### Source mapping (both frameworks)
106
+
107
+ | C# source | Azure TC field |
108
+ |-----------|---------------|
109
+ | XML doc `<summary>` first line | TC **Title** |
110
+ | Numbered lines `N. text` in summary | TC **Steps** (action) |
111
+ | Numbered lines `N. Check: text` | TC **Steps** (expected result, when `useExpectedResult: true`) |
112
+ | `[TestCategory("…")]` / `[Category("…")]` | TC **Tags** (string literals and `const string` constants resolved) |
113
+ | `[TestProperty("tc","ID")]` / `[Property("tc","ID")]` | TC ID (written back after first push) |
114
+ | `Namespace.Class.MethodName` | `AutomatedTestName` (for TRX result linking) |
115
+
116
+ ### MSTest example
117
+
118
+ ```csharp
119
+ /// <summary>
120
+ /// Verify CoCounsel dialog opens on Edge
121
+ /// Test case: 1883058
122
+ /// 1. Sign in to Westlaw Edge
123
+ /// 2. Click CoCounsel link from page header
124
+ /// 3. Check: Verify CoCounsel dialog opens with correct title
125
+ /// 4. Check: Verify Close button is displayed
126
+ /// </summary>
127
+ [TestMethod]
128
+ [TestProperty("tc", "1883058")] // written back by ado-sync after first push
129
+ [TestCategory("EdgeAalp")]
130
+ [TestCategory("EdgeAalpSmoke")]
131
+ public void EdgeAalpAccessTest()
132
+ {
133
+ // ...
134
+ }
135
+ ```
136
+
137
+ ### NUnit example
138
+
139
+ ```csharp
140
+ /// <summary>
141
+ /// Verify login with valid credentials
142
+ /// 1. Navigate to the login page
143
+ /// 2. Enter username and password
144
+ /// 3. Click the Login button
145
+ /// 4. Check: Dashboard page is shown
146
+ /// 5. Check: Username appears in the top navigation
147
+ /// </summary>
148
+ [Test]
149
+ [Property("tc", "2001")] // written back by ado-sync after first push
150
+ [Category("Smoke")]
151
+ [Category("Authentication")]
152
+ public void LoginWithValidCredentials()
153
+ {
154
+ // ...
155
+ }
156
+ ```
157
+
158
+ ### Recommended config
159
+
160
+ ```json
161
+ {
162
+ "local": {
163
+ "type": "csharp",
164
+ "include": ["**/RegressionTests/**/*.cs"],
165
+ "exclude": ["**/*BaseTest.cs", "**/*BaseFixture.cs", "**/*Helper.cs"]
166
+ },
167
+ "sync": {
168
+ "markAutomated": true,
169
+ "format": { "useExpectedResult": true }
170
+ }
171
+ }
172
+ ```
173
+
174
+ With `useExpectedResult: true`, `Check:` lines go into the Expected Result column of each TC step. With `markAutomated: true`, the TC's Associated Automation is set to the fully-qualified method name (`Namespace.Class.Method`), enabling `publish-test-results` to link TRX run outcomes back to TCs.
175
+
176
+ ### Notes
177
+
178
+ - **Constants** — ado-sync resolves `const string` declarations within the same file. Constants defined in a base class are not resolved; use string literals for reliable tagging.
179
+ - **`pull` is not supported** for C# files. Only `push` and `publish-test-results` apply.
180
+ - **Base classes / fixtures** — exclude them from `local.include` to avoid treating non-test methods as test cases.
181
+ - **NUnit `[TestCase]`** — parameterised data rows (`[TestCase(1, "foo")]`) are not expanded into separate TCs. Only the `[Test]` marker is treated as the test definition.
182
+
183
+ ---
184
+
185
+ ## Java JUnit / TestNG `.java`
186
+
187
+ Set `local.type: "java"`. Supports **JUnit 4**, **JUnit 5**, and **TestNG** (including Selenium-based tests). Each `@Test`-annotated method becomes one Test Case.
188
+
189
+ ### Framework attribute mapping
190
+
191
+ | Concern | JUnit 4 | JUnit 5 | TestNG |
192
+ |---------|---------|---------|--------|
193
+ | Test marker | `@Test` (`org.junit.Test`) | `@Test` (`org.junit.jupiter.api.Test`) | `@Test` (`org.testng.annotations.Test`) |
194
+ | Tag / category | `@Category(Smoke.class)` | `@Tag("smoke")` | `groups = {"smoke"}` in `@Test` |
195
+ | TC ID writeback | `// @tc:ID` above `@Test` | `@Tag("tc:ID")` above `@Test` | `// @tc:ID` above `@Test` |
196
+
197
+ The framework is auto-detected per file from import statements — no config required.
198
+
199
+ ### Source mapping (all frameworks)
200
+
201
+ | Java source | Azure TC field |
202
+ |-------------|---------------|
203
+ | Javadoc `/** ... */` first non-numbered line | TC **Title** |
204
+ | Numbered lines `N. text` in Javadoc | TC **Steps** (action) |
205
+ | Numbered lines `N. Check: text` in Javadoc | TC **Steps** (expected result, when `useExpectedResult: true`) |
206
+ | `@Category`, `@Tag`, `groups` in `@Test` | TC **Tags** |
207
+ | `// @tc:ID` or `@Tag("tc:ID")` | TC ID (written back after first push) |
208
+ | `com.example.MyClass.myMethod` | `AutomatedTestName` (for JUnit XML result linking) |
209
+
210
+ ### JUnit 5 example
211
+
212
+ ```java
213
+ import org.junit.jupiter.api.Tag;
214
+ import org.junit.jupiter.api.Test;
215
+
216
+ class CheckoutTests {
217
+
218
+ /**
219
+ * Add item and complete checkout
220
+ * 1. Sign in as standard_user
221
+ * 2. Add "Sauce Labs Backpack" to cart
222
+ * 3. Proceed through checkout
223
+ * 4. Check: Order confirmation page is shown
224
+ */
225
+ @Tag("tc:1041") // written back by ado-sync after first push
226
+ @Tag("smoke")
227
+ @Test
228
+ void addItemAndCompleteCheckout() {
229
+ // ...
230
+ }
231
+ }
232
+ ```
233
+
234
+ ### JUnit 4 example
235
+
236
+ ```java
237
+ import org.junit.Test;
238
+ import org.junit.experimental.categories.Category;
239
+
240
+ public class LoginTests {
241
+
242
+ /**
243
+ * Login with valid credentials
244
+ * 1. Navigate to the login page
245
+ * 2. Enter username and password
246
+ * 3. Check: Dashboard page is shown
247
+ */
248
+ // @tc:1042 // written back by ado-sync after first push
249
+ @Test
250
+ @Category(Smoke.class)
251
+ public void loginWithValidCredentials() {
252
+ // ...
253
+ }
254
+ }
255
+ ```
256
+
257
+ ### TestNG example
258
+
259
+ ```java
260
+ import org.testng.annotations.Test;
261
+
262
+ public class SearchTests {
263
+
264
+ /**
265
+ * Search returns relevant results
266
+ * 1. Open the search page
267
+ * 2. Enter "junit 5" in the search box
268
+ * 3. Check: Results list contains at least one entry
269
+ */
270
+ // @tc:1043 // written back by ado-sync after first push
271
+ @Test(groups = {"regression", "search"})
272
+ public void searchReturnsRelevantResults() {
273
+ // ...
274
+ }
275
+ }
276
+ ```
277
+
278
+ ### Recommended config
279
+
280
+ ```json
281
+ {
282
+ "local": {
283
+ "type": "java",
284
+ "include": ["**/src/test/**/*.java"],
285
+ "exclude": ["**/*BaseTest.java", "**/*Helper.java", "**/*Utils.java"]
286
+ },
287
+ "sync": {
288
+ "markAutomated": true
289
+ }
290
+ }
291
+ ```
292
+
293
+ ### Notes
294
+
295
+ - **`pull` is not supported** for Java files. Only `push` and `publish-test-results` apply.
296
+ - **Abstract base test classes** — exclude them from `local.include` to avoid treating base methods as test cases.
297
+ - **JUnit 5 `@Tag`** — the `org.junit.jupiter.api.Tag` import must be present or the file must already use Jupiter annotations; ado-sync adds the tag annotation but not the import if it's missing.
298
+ - **TestNG `groups`** — `groups = {"smoke", "regression"}` inside `@Test(...)` are extracted as TC tags.
299
+ - **`@ParameterizedTest` / `@DataProvider`** — parameterised data rows are not expanded into separate TCs; only the method definition is treated as the test case.
300
+
301
+ ---
302
+
303
+ ## Python pytest `.py`
304
+
305
+ Set `local.type: "python"`. Each `def test_*` function — at module level or inside a class — becomes one Test Case. No extra dependencies required beyond `pytest` itself.
306
+
307
+ ### Source mapping
308
+
309
+ | Python source | Azure TC field |
310
+ |---------------|---------------|
311
+ | Docstring first non-numbered line | TC **Title** |
312
+ | Numbered lines `N. text` in docstring | TC **Steps** (action) |
313
+ | Numbered lines `N. Check: text` in docstring | TC **Steps** (expected result, when `useExpectedResult: true`) |
314
+ | `@pytest.mark.<tag>` decorators | TC **Tags** (built-in pytest marks excluded) |
315
+ | `@pytest.mark.tc(ID)` | TC ID (written back after first push) |
316
+ | `module.path.ClassName.test_method` | `AutomatedTestName` (for JUnit XML result linking) |
317
+
318
+ ### Example
319
+
320
+ ```python
321
+ import pytest
322
+
323
+ class TestCheckout:
324
+
325
+ @pytest.mark.tc(1041) # written back by ado-sync after first push
326
+ @pytest.mark.smoke
327
+ def test_add_item_and_complete_checkout(self):
328
+ """
329
+ Add item and complete checkout
330
+ 1. Sign in as standard_user
331
+ 2. Add Sauce Labs Backpack to cart
332
+ 3. Proceed through checkout
333
+ 4. Check: Order confirmation page is shown
334
+ """
335
+ # ...
336
+
337
+ @pytest.mark.tc(1042)
338
+ @pytest.mark.regression
339
+ def test_checkout_with_invalid_card(self):
340
+ """
341
+ Checkout fails with invalid card
342
+ 1. Add an item to the cart
343
+ 2. Enter an invalid credit card number
344
+ 3. Click Place Order
345
+ 4. Check: Error message is displayed
346
+ """
347
+ # ...
348
+ ```
349
+
350
+ ### Recommended config
351
+
352
+ ```json
353
+ {
354
+ "local": {
355
+ "type": "python",
356
+ "include": ["tests/**/*.py"],
357
+ "exclude": ["tests/conftest.py", "tests/**/fixtures.py"]
358
+ },
359
+ "sync": {
360
+ "markAutomated": true
361
+ }
362
+ }
363
+ ```
364
+
365
+ ### Notes
366
+
367
+ - **`pull` is not supported** for Python files. Only `push` and `publish-test-results` apply.
368
+ - **Built-in pytest marks** (`parametrize`, `skip`, `skipif`, `xfail`, `usefixtures`, `filterwarnings`) are not pushed as TC tags.
369
+ - **`@pytest.mark.parametrize`** — parameterised data rows are not expanded into separate TCs; only the function definition is treated as the test case.
370
+ - **Class hierarchy** — only the immediately enclosing class is used for the `automatedTestName`. Nested classes are supported.
371
+
372
+ ---
373
+
374
+ ## JavaScript / TypeScript (Jest, Jasmine, WebdriverIO) `.js` / `.ts`
375
+
376
+ Set `local.type: "javascript"`. Supports **Jest**, **Jasmine**, and **WebdriverIO** (which uses Jest or Jasmine as its runner). All three share the same `describe()`/`it()`/`test()` API, so a single parser handles all of them.
377
+
378
+ > **Cucumber `.feature` files** are handled by `local.type: "gherkin"` — not this type.
379
+
380
+ ### Detected test functions
381
+
382
+ | Function | Description |
383
+ |----------|-------------|
384
+ | `it(title, fn)` | Standard test |
385
+ | `test(title, fn)` | Alias for `it` |
386
+ | `it.only` / `test.only` | Focused test |
387
+ | `it.skip` / `test.skip` | Skipped test (still synced) |
388
+ | `xit` / `xtest` | Jasmine-style skip |
389
+ | `it.concurrent` / `test.concurrent` | Concurrent test |
390
+
391
+ ### Source mapping
392
+
393
+ | JavaScript/TS source | Azure TC field |
394
+ |----------------------|---------------|
395
+ | JSDoc `/** ... */` first non-numbered line | TC **Title** |
396
+ | Numbered lines `N. text` in JSDoc | TC **Steps** (action) |
397
+ | Numbered lines `N. Check: text` in JSDoc | TC **Steps** (expected result, when `useExpectedResult: true`) |
398
+ | `// @tags: smoke, regression` above `it()` | TC **Tags** (comma-separated list) |
399
+ | `// @smoke` above `it()` (single-word) | TC **Tag** |
400
+ | `// @tc:ID` above `it()` | TC ID (written back after first push) |
401
+ | `{basename} > {describe} > {it title}` | `AutomatedTestName` (Jest report format) |
402
+
403
+ ### Example
404
+
405
+ ```typescript
406
+ describe('Checkout', () => {
407
+
408
+ /**
409
+ * Add item and complete checkout
410
+ * 1. Sign in as standard_user
411
+ * 2. Add Sauce Labs Backpack to cart
412
+ * 3. Proceed through checkout
413
+ * 4. Check: Order confirmation page is shown
414
+ */
415
+ // @tc:1041 // written back by ado-sync after first push
416
+ // @tags: smoke, regression
417
+ it('adds item and completes checkout', async () => {
418
+ // ...
419
+ });
420
+
421
+ // @tc:1042
422
+ // @smoke
423
+ it('shows error on invalid card', async () => {
424
+ // ...
425
+ });
426
+
427
+ });
428
+ ```
429
+
430
+ ### Recommended config
431
+
432
+ ```json
433
+ {
434
+ "local": {
435
+ "type": "javascript",
436
+ "include": ["src/**/*.spec.ts", "tests/**/*.test.js"],
437
+ "exclude": ["**/*.helper.ts", "**/*.fixture.ts"]
438
+ },
439
+ "sync": {
440
+ "markAutomated": true
441
+ }
442
+ }
443
+ ```
444
+
445
+ ### Notes
446
+
447
+ - **`pull` is not supported** for JavaScript/TypeScript files. Only `push` applies.
448
+ - **Dynamic titles** — `it` / `test` calls with template literals or computed values are skipped. Use string literals for reliable syncing.
449
+ - **`describe` nesting** — arbitrarily deep `describe()` nesting is supported. All enclosing describe titles are included in the `automatedTestName`.
450
+ - **Path-based auto-tagging** — directory segments starting with `@` (e.g. `tests/@smoke/`) are automatically applied as tags on all tests inside.
451
+
452
+ ---
453
+
454
+ ## CSV `.csv`
455
+
456
+ Set `local.type: "csv"` to parse Azure DevOps / SpecSync tabular CSV exports.
457
+
458
+ **Expected column layout (9 columns):**
459
+
460
+ | Col | Field | Description |
461
+ |-----|-------|-------------|
462
+ | A (0) | ID | Azure Test Case ID — empty for new, filled after first push |
463
+ | B (1) | Work Item Type | Always `Test Case` (ignored) |
464
+ | C (2) | Title | `Scenario: My test` — non-empty on header row, empty on step rows |
465
+ | D (3) | Test Step | Step number — empty on header row |
466
+ | E (4) | Step Action | Step text, optionally prefixed with a Gherkin keyword |
467
+ | F (5) | Step Expected | Expected result text (optional) |
468
+ | G–I (6–8) | Area Path, Assigned To, State | Preserved but not used |
469
+
470
+ The first row (column headers) is always skipped automatically.
471
+
472
+ > **Note:** `ado-sync pull` is **not supported** for CSV files. Only push is supported.
473
+
474
+ ---
475
+
476
+ ## Excel `.xlsx`
477
+
478
+ Set `local.type: "excel"`. Uses the same 9-column layout as CSV. ado-sync reads the raw worksheet XML (no external xlsx library) and handles both `t="str"` and `t="inlineStr"` cells from Azure DevOps exports.
479
+
480
+ > **Note:** `ado-sync pull` is **not supported** for Excel files. Only push is supported.
481
+
482
+ ---
483
+
484
+ ## ID tags
485
+
486
+ After a first push, ado-sync writes the Azure TC ID back into the local file.
487
+
488
+ | Format | Location |
489
+ |--------|----------|
490
+ | Gherkin | `@tc:12345` tag on its own line above the `Scenario:` line |
491
+ | Markdown | `@tc:12345` tag on the line immediately after the `### heading` |
492
+ | C# MSTest | `[TestProperty("tc", "12345")]` attribute on the test method |
493
+ | C# NUnit | `[Property("tc", "12345")]` attribute on the test method |
494
+ | Java JUnit 5 | `@Tag("tc:12345")` annotation above `@Test` |
495
+ | Java JUnit 4 / TestNG | `// @tc:12345` comment on the line above `@Test` |
496
+ | Python pytest | `@pytest.mark.tc(12345)` decorator above `def test_*` |
497
+ | JavaScript/TS | `// @tc:12345` comment on the line above `it()`/`test()` |
498
+ | CSV | Numeric ID in column A of the matching title row |
499
+ | Excel | Numeric ID in cell A of the matching title row |
500
+
501
+ ### Custom prefix
502
+
503
+ ```json
504
+ { "sync": { "tagPrefix": "azure" } }
505
+ ```
506
+
507
+ Result: `@azure:12345` in both Gherkin and Markdown files.
508
+
509
+ > **Warning:** Changing the prefix on an existing project means existing ID tags are no longer recognised. Do a project-wide find-and-replace before changing.
510
+
511
+ ---
512
+
513
+ ## Tag filtering
514
+
515
+ All commands accept `--tags` to limit which scenarios are processed. Uses the standard Cucumber tag expression language.
516
+
517
+ ```bash
518
+ ado-sync push --tags "@smoke"
519
+ ado-sync push --tags "@smoke and not @wip"
520
+ ado-sync pull --tags "@regression or @critical"
521
+ ```
522
+
523
+ Tags are evaluated against all tags on a scenario, including:
524
+
525
+ - Tags on the `Feature` block (Gherkin)
526
+ - Tags on the `Scenario` / `Scenario Outline` block
527
+ - Tags on individual `Examples` tables
528
+ - Tags on the Markdown tag line (same `### heading` line or line below it)
529
+ - **Path-based auto-tags** — directory segments starting with `@` are automatically applied as tags:
530
+
531
+ ```
532
+ specs/
533
+ @smoke/
534
+ login.feature ← all scenarios get tag 'smoke'
535
+ @regression/
536
+ @slow/
537
+ checkout.feature ← all scenarios get tags 'regression' and 'slow'
538
+ ```
539
+
540
+ ### `local.condition` — permanent filter
541
+
542
+ Use `local.condition` in config to permanently exclude scenarios without needing `--tags` on every command:
543
+
544
+ ```json
545
+ { "local": { "condition": "@done and not (@ignored or @planned)" } }
546
+ ```
547
+
548
+ This filter is applied before `--tags`, so it acts as a baseline inclusion gate.
549
+
550
+ ---
551
+
552
+ ## Work item linking
553
+
554
+ Tags matching a configured `links` prefix are synced as Azure DevOps work item relations on the Test Case.
555
+
556
+ ### Config
557
+
558
+ ```json
559
+ {
560
+ "sync": {
561
+ "links": [
562
+ { "prefix": "story", "relationship": "System.LinkTypes.Related" },
563
+ { "prefix": "bug", "relationship": "System.LinkTypes.Related" },
564
+ { "prefix": "req", "relationship": "Microsoft.VSTS.Common.TestedBy-Reverse" }
565
+ ]
566
+ }
567
+ }
568
+ ```
569
+
570
+ ### Usage in Gherkin
571
+
572
+ ```gherkin
573
+ @tc:1042 @story:555 @bug:789
574
+ Scenario: User can add items to cart
575
+ ...
576
+ ```
577
+
578
+ ### Usage in Markdown
579
+
580
+ ```markdown
581
+ ### User can add items to cart
582
+ @tc:1042 @story:555 @bug:789
583
+
584
+ Steps:
585
+ 1. Add an item to the cart
586
+ ...
587
+ ```
588
+
589
+ On each `push`, relations are synced: new links are added, stale links (whose tag was removed) are deleted. The `relationship` value is the ADO relation type — `"System.LinkTypes.Related"` is the default if omitted.
590
+
591
+ ---
592
+
593
+ ## Attachments
594
+
595
+ Files can be attached to Azure Test Cases via tags. See [Attachments](advanced.md#attachments).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ado-sync",
3
- "version": "0.1.24",
3
+ "version": "0.1.27",
4
4
  "description": "Bidirectional sync between local test specs (Cucumber/Markdown) and Azure DevOps Test Cases",
5
5
  "bin": {
6
6
  "ado-sync": "./dist/cli.js"