ado-sync 0.1.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 +527 -0
- package/dist/azure/client.d.ts +17 -0
- package/dist/azure/client.js +88 -0
- package/dist/azure/client.js.map +1 -0
- package/dist/azure/test-cases.d.ts +22 -0
- package/dist/azure/test-cases.js +247 -0
- package/dist/azure/test-cases.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +194 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +129 -0
- package/dist/config.js.map +1 -0
- package/dist/parsers/gherkin.d.ts +30 -0
- package/dist/parsers/gherkin.js +177 -0
- package/dist/parsers/gherkin.js.map +1 -0
- package/dist/parsers/markdown.d.ts +31 -0
- package/dist/parsers/markdown.js +185 -0
- package/dist/parsers/markdown.js.map +1 -0
- package/dist/sync/engine.d.ts +13 -0
- package/dist/sync/engine.js +296 -0
- package/dist/sync/engine.js.map +1 -0
- package/dist/sync/writeback.d.ts +36 -0
- package/dist/sync/writeback.js +134 -0
- package/dist/sync/writeback.js.map +1 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
# ado-sync
|
|
2
|
+
|
|
3
|
+
Bidirectional sync between local test specs and Azure DevOps Test Cases.
|
|
4
|
+
|
|
5
|
+
Supports **Cucumber / Gherkin** `.feature` files and **prose Markdown** `.md` spec files.
|
|
6
|
+
Inspired by [SpecSync](https://docs.specsolutions.eu/specsync/).
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## How it works
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
Local files ado-sync Azure DevOps
|
|
14
|
+
────────────── ─────────────── ────────────────
|
|
15
|
+
.feature files ── push ──► create / update ──────────► Test Cases
|
|
16
|
+
.md spec files ◄── pull ── apply changes ◄────────── (Work Items)
|
|
17
|
+
write ID back
|
|
18
|
+
@tc:12345 / <!-- tc: 12345 -->
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
On the **first push** of a scenario, a new Test Case is created in Azure DevOps and its ID is written back into the local file as a tag or comment. Every subsequent push uses that ID to update the existing Test Case. Pulling fetches the latest title and steps from Azure and overwrites the local file.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g ado-sync
|
|
29
|
+
# or use locally
|
|
30
|
+
npx ado-sync --help
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Quick start
|
|
36
|
+
|
|
37
|
+
### 1. Generate a config file
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
ado-sync init
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This creates `ado-sync.json` in the current directory.
|
|
44
|
+
|
|
45
|
+
### 2. Edit the config
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"orgUrl": "https://dev.azure.com/my-org",
|
|
50
|
+
"project": "MyProject",
|
|
51
|
+
"auth": {
|
|
52
|
+
"type": "pat",
|
|
53
|
+
"token": "$AZURE_DEVOPS_TOKEN"
|
|
54
|
+
},
|
|
55
|
+
"testPlan": {
|
|
56
|
+
"id": 1234,
|
|
57
|
+
"suiteId": 5678
|
|
58
|
+
},
|
|
59
|
+
"local": {
|
|
60
|
+
"type": "gherkin",
|
|
61
|
+
"include": "specs/**/*.feature"
|
|
62
|
+
},
|
|
63
|
+
"sync": {
|
|
64
|
+
"tagPrefix": "tc"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Set your token
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
export AZURE_DEVOPS_TOKEN=your_personal_access_token
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 4. Push
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
ado-sync push
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
New scenarios are created in Azure DevOps. Their IDs are written back into your local files automatically.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## CLI reference
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
ado-sync [options] [command]
|
|
89
|
+
|
|
90
|
+
Options:
|
|
91
|
+
-c, --config <path> Path to config file (default: ado-sync.json)
|
|
92
|
+
-V, --version Print version
|
|
93
|
+
-h, --help Show help
|
|
94
|
+
|
|
95
|
+
Commands:
|
|
96
|
+
init [options] Generate a starter config file
|
|
97
|
+
push [options] Push local specs to Azure DevOps
|
|
98
|
+
pull [options] Pull updates from Azure DevOps into local files
|
|
99
|
+
status Show what would change without making any modifications
|
|
100
|
+
help [command] Help for a specific command
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### `init`
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
ado-sync init
|
|
107
|
+
ado-sync init --output path/to/my-config.json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Creates a starter `ado-sync.json`. Safe — will not overwrite an existing file.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### `push`
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
ado-sync push
|
|
118
|
+
ado-sync push --dry-run
|
|
119
|
+
ado-sync push --tags "@smoke"
|
|
120
|
+
ado-sync push --tags "@smoke and not @wip"
|
|
121
|
+
ado-sync -c other-config.json push
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
For every scenario / heading in your local spec files:
|
|
125
|
+
|
|
126
|
+
| Scenario state | Action |
|
|
127
|
+
|----------------|--------|
|
|
128
|
+
| No ID tag yet | Creates a new Test Case, writes ID back to file |
|
|
129
|
+
| Has ID tag, no changes | Skipped |
|
|
130
|
+
| Has ID tag, title or steps changed | Updates the existing Test Case |
|
|
131
|
+
| Has ID tag but Test Case deleted in Azure | Reported as error |
|
|
132
|
+
|
|
133
|
+
`--dry-run` prints what would happen without modifying anything.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
### `pull`
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
ado-sync pull
|
|
141
|
+
ado-sync pull --dry-run
|
|
142
|
+
ado-sync pull --tags "@smoke"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
For every locally-linked scenario (has an ID tag):
|
|
146
|
+
|
|
147
|
+
| Remote state | Action |
|
|
148
|
+
|--------------|--------|
|
|
149
|
+
| Title or steps changed in Azure | Updates the local file |
|
|
150
|
+
| No changes | Skipped |
|
|
151
|
+
| Test Case not found | Reported as error |
|
|
152
|
+
|
|
153
|
+
`--dry-run` prints what would change without modifying local files.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
### `status`
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
ado-sync status
|
|
161
|
+
ado-sync status --tags "@smoke"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Compares local specs against Azure DevOps and prints a diff — no changes made. Equivalent to `push --dry-run`.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Config file reference
|
|
169
|
+
|
|
170
|
+
Config files can be JSON (`.json`) or YAML (`.yml` / `.yaml`).
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"orgUrl": "https://dev.azure.com/YOUR_ORG",
|
|
175
|
+
"project": "YOUR_PROJECT",
|
|
176
|
+
|
|
177
|
+
"auth": {
|
|
178
|
+
"type": "pat",
|
|
179
|
+
"token": "$AZURE_DEVOPS_TOKEN",
|
|
180
|
+
"applicationIdURI": ""
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
"testPlan": {
|
|
184
|
+
"id": 1234,
|
|
185
|
+
"suiteId": 5678
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
"local": {
|
|
189
|
+
"type": "gherkin",
|
|
190
|
+
"include": "specs/**/*.feature",
|
|
191
|
+
"exclude": []
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
"sync": {
|
|
195
|
+
"tagPrefix": "tc",
|
|
196
|
+
"areaPath": "MyProject\\Team A",
|
|
197
|
+
"iterationPath": "MyProject\\Sprint 1"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### `orgUrl`
|
|
203
|
+
Azure DevOps organisation URL. Format: `https://dev.azure.com/YOUR_ORG`.
|
|
204
|
+
|
|
205
|
+
### `project`
|
|
206
|
+
Azure DevOps project name.
|
|
207
|
+
|
|
208
|
+
### `auth`
|
|
209
|
+
|
|
210
|
+
| Field | Description |
|
|
211
|
+
|-------|-------------|
|
|
212
|
+
| `type` | `"pat"` · `"accessToken"` · `"managedIdentity"` |
|
|
213
|
+
| `token` | PAT or access token value. Prefix with `$` to read from an environment variable (e.g. `"$AZURE_DEVOPS_TOKEN"`). |
|
|
214
|
+
| `applicationIdURI` | Required only when `type` is `"managedIdentity"`. |
|
|
215
|
+
|
|
216
|
+
**PAT permissions required:** Test Management (Read & Write), Work Items (Read & Write).
|
|
217
|
+
|
|
218
|
+
### `testPlan`
|
|
219
|
+
|
|
220
|
+
| Field | Description |
|
|
221
|
+
|-------|-------------|
|
|
222
|
+
| `id` | ID of the Azure DevOps Test Plan new test cases are added to. |
|
|
223
|
+
| `suiteId` | *(Optional)* ID of the Test Suite within the plan. Defaults to the plan's root suite. |
|
|
224
|
+
|
|
225
|
+
### `local`
|
|
226
|
+
|
|
227
|
+
| Field | Description |
|
|
228
|
+
|-------|-------------|
|
|
229
|
+
| `type` | `"gherkin"` for `.feature` files · `"markdown"` for prose `.md` specs. |
|
|
230
|
+
| `include` | Glob pattern(s) relative to the config file. String or array. |
|
|
231
|
+
| `exclude` | *(Optional)* Glob pattern(s) to exclude. String or array. |
|
|
232
|
+
|
|
233
|
+
### `sync`
|
|
234
|
+
|
|
235
|
+
| Field | Default | Description |
|
|
236
|
+
|-------|---------|-------------|
|
|
237
|
+
| `tagPrefix` | `"tc"` | Prefix used in ID tags. See [ID tags](#id-tags) below. |
|
|
238
|
+
| `areaPath` | *(none)* | Area path for newly created Test Cases. |
|
|
239
|
+
| `iterationPath` | *(none)* | Iteration path for newly created Test Cases. |
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## ID tags
|
|
244
|
+
|
|
245
|
+
After a scenario is pushed for the first time, ado-sync writes the Azure Test Case ID back into the local file. The format depends on the spec type.
|
|
246
|
+
|
|
247
|
+
### Gherkin
|
|
248
|
+
|
|
249
|
+
The ID tag is inserted on its own line immediately above the `Scenario:` keyword, using the `@{tagPrefix}:{id}` convention:
|
|
250
|
+
|
|
251
|
+
```gherkin
|
|
252
|
+
Feature: Login
|
|
253
|
+
|
|
254
|
+
@tc:1042
|
|
255
|
+
Scenario: Successful login with valid credentials
|
|
256
|
+
Given I am on the login page
|
|
257
|
+
When I enter username "standard_user" and password "secret_sauce"
|
|
258
|
+
Then I am redirected to the inventory page
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Tags from the feature or scenario are preserved. The ID tag is always kept as the first tag on the line or alongside existing tags.
|
|
262
|
+
|
|
263
|
+
### Markdown
|
|
264
|
+
|
|
265
|
+
The ID comment is inserted on the line immediately after the `### heading`, using `<!-- {tagPrefix}: {id} -->`:
|
|
266
|
+
|
|
267
|
+
```markdown
|
|
268
|
+
### 1. Login (happy path)
|
|
269
|
+
<!-- tc: 1042 -->
|
|
270
|
+
|
|
271
|
+
Assumption: Fresh browser session.
|
|
272
|
+
|
|
273
|
+
Steps:
|
|
274
|
+
1. Enter username `standard_user` in the Username field.
|
|
275
|
+
2. Enter password `secret_sauce` in the Password field.
|
|
276
|
+
3. Click `Login`.
|
|
277
|
+
|
|
278
|
+
Expected results:
|
|
279
|
+
- User is redirected to `/inventory.html`.
|
|
280
|
+
- Product list is visible.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
The comment is invisible when the Markdown is rendered.
|
|
286
|
+
|
|
287
|
+
### Custom prefix
|
|
288
|
+
|
|
289
|
+
Change the `sync.tagPrefix` in your config to use a different prefix:
|
|
290
|
+
|
|
291
|
+
```json
|
|
292
|
+
{ "sync": { "tagPrefix": "azure" } }
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Result:
|
|
296
|
+
- Gherkin: `@azure:1042`
|
|
297
|
+
- Markdown: `<!-- azure: 1042 -->`
|
|
298
|
+
|
|
299
|
+
> **Warning:** Changing the prefix on an existing project means all existing ID tags will no longer be recognised. Do a project-wide find-and-replace on the old prefix before changing it.
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## Tag filtering
|
|
304
|
+
|
|
305
|
+
All commands accept a `--tags` option to limit which scenarios are synced. The syntax is the standard Cucumber tag expression language.
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
# Only sync scenarios tagged @smoke
|
|
309
|
+
ado-sync push --tags "@smoke"
|
|
310
|
+
|
|
311
|
+
# Sync everything except @wip
|
|
312
|
+
ado-sync push --tags "not @wip"
|
|
313
|
+
|
|
314
|
+
# Combine tags with and / or
|
|
315
|
+
ado-sync push --tags "@smoke and not @slow"
|
|
316
|
+
ado-sync pull --tags "@regression or @critical"
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Tags are evaluated against all tags on a scenario, including:
|
|
320
|
+
- Tags inherited from the Feature block
|
|
321
|
+
- Tags on the Scenario / Scenario Outline block
|
|
322
|
+
- Tags on individual Examples tables
|
|
323
|
+
- **Path-based auto-tags** — directory segments prefixed with `@` are automatically applied as tags to all scenarios in that directory:
|
|
324
|
+
|
|
325
|
+
```
|
|
326
|
+
specs/
|
|
327
|
+
@smoke/
|
|
328
|
+
login.feature ← all scenarios get tag 'smoke'
|
|
329
|
+
@regression/
|
|
330
|
+
@slow/
|
|
331
|
+
checkout.feature ← all scenarios get tags 'regression' and 'slow'
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
This lets you filter by folder without adding tags to every scenario manually:
|
|
335
|
+
```bash
|
|
336
|
+
ado-sync push --tags "@smoke" # only push specs/@smoke/** scenarios
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
> Path-based auto-tagging applies to Gherkin files only. Markdown specs do not have a tag system, so `--tags` has no effect on them.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Spec file formats
|
|
344
|
+
|
|
345
|
+
### Gherkin `.feature`
|
|
346
|
+
|
|
347
|
+
Standard Gherkin syntax is supported, including:
|
|
348
|
+
- `Feature` / `Scenario` / `Scenario Outline`
|
|
349
|
+
- `Given` / `When` / `Then` / `And` / `But`
|
|
350
|
+
- Feature-level and scenario-level tags
|
|
351
|
+
- `Examples` tables — each row becomes a separate Azure Test Case
|
|
352
|
+
|
|
353
|
+
```gherkin
|
|
354
|
+
Feature: Checkout
|
|
355
|
+
|
|
356
|
+
@smoke
|
|
357
|
+
Scenario: Add item and complete checkout
|
|
358
|
+
Given I am logged in as "standard_user"
|
|
359
|
+
When I add "Sauce Labs Backpack" to the cart
|
|
360
|
+
And I proceed through checkout with name "Test User" and zip "12345"
|
|
361
|
+
Then I see the order confirmation page
|
|
362
|
+
|
|
363
|
+
Scenario Outline: Checkout with different users
|
|
364
|
+
Given I am logged in as "<user>"
|
|
365
|
+
When I complete a checkout
|
|
366
|
+
Then the result is "<result>"
|
|
367
|
+
|
|
368
|
+
Examples:
|
|
369
|
+
| user | result |
|
|
370
|
+
| standard_user | success |
|
|
371
|
+
| performance_glitch_user | success |
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Markdown `.md`
|
|
375
|
+
|
|
376
|
+
Each `### heading` is treated as one test case. The file can contain any number of scenarios separated by `---`.
|
|
377
|
+
|
|
378
|
+
```markdown
|
|
379
|
+
# My Feature Test Plan
|
|
380
|
+
|
|
381
|
+
## Test scenarios
|
|
382
|
+
|
|
383
|
+
### Login with valid credentials
|
|
384
|
+
|
|
385
|
+
Steps:
|
|
386
|
+
1. Navigate to https://example.com/login
|
|
387
|
+
2. Enter username "admin" and password "secret"
|
|
388
|
+
3. Click the Login button
|
|
389
|
+
|
|
390
|
+
Expected results:
|
|
391
|
+
- The dashboard page is shown
|
|
392
|
+
- The username appears in the top navigation
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
### Login with invalid credentials
|
|
397
|
+
|
|
398
|
+
Steps:
|
|
399
|
+
1. Navigate to https://example.com/login
|
|
400
|
+
2. Enter username "wrong" and password "wrong"
|
|
401
|
+
3. Click the Login button
|
|
402
|
+
|
|
403
|
+
Expected results:
|
|
404
|
+
- An error message "Invalid credentials" is displayed
|
|
405
|
+
- The user remains on the login page
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Headings can optionally have a number prefix (`### 1. Title` or `### Title` — both work).
|
|
411
|
+
Sections recognised: `Steps:`, `Expected results:` (case-insensitive). All other prose is captured as the test case description.
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## YAML config example
|
|
416
|
+
|
|
417
|
+
```yaml
|
|
418
|
+
orgUrl: https://dev.azure.com/my-org
|
|
419
|
+
project: MyProject
|
|
420
|
+
|
|
421
|
+
auth:
|
|
422
|
+
type: pat
|
|
423
|
+
token: $AZURE_DEVOPS_TOKEN
|
|
424
|
+
|
|
425
|
+
testPlan:
|
|
426
|
+
id: 1234
|
|
427
|
+
suiteId: 5678
|
|
428
|
+
|
|
429
|
+
local:
|
|
430
|
+
type: markdown
|
|
431
|
+
include:
|
|
432
|
+
- specs/**/*.md
|
|
433
|
+
exclude:
|
|
434
|
+
- specs/archive/**
|
|
435
|
+
|
|
436
|
+
sync:
|
|
437
|
+
tagPrefix: tc
|
|
438
|
+
areaPath: "MyProject\\QA Team"
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## Environment variables
|
|
444
|
+
|
|
445
|
+
| Variable | Description |
|
|
446
|
+
|----------|-------------|
|
|
447
|
+
| `AZURE_DEVOPS_TOKEN` | PAT or access token. Reference it in config with `"$AZURE_DEVOPS_TOKEN"`. |
|
|
448
|
+
| Any name | Any env var can be used — set `auth.token` to `"$MY_VAR_NAME"`. |
|
|
449
|
+
|
|
450
|
+
You can also use a `.env` file in the working directory. It is loaded automatically.
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## Output symbols
|
|
455
|
+
|
|
456
|
+
```
|
|
457
|
+
+ created — new Test Case created in Azure DevOps
|
|
458
|
+
~ updated — existing Test Case updated
|
|
459
|
+
↓ pulled — local file updated from Azure DevOps
|
|
460
|
+
= skipped — no changes detected
|
|
461
|
+
! conflict — both sides changed (manual resolution needed)
|
|
462
|
+
✗ error — something went wrong (see detail message)
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## Workflow examples
|
|
468
|
+
|
|
469
|
+
### First-time setup
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
# Generate config
|
|
473
|
+
ado-sync init
|
|
474
|
+
|
|
475
|
+
# Edit ado-sync.json with your org, project, plan ID, and token
|
|
476
|
+
export AZURE_DEVOPS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
477
|
+
|
|
478
|
+
# Preview what will be created
|
|
479
|
+
ado-sync push --dry-run
|
|
480
|
+
|
|
481
|
+
# Create test cases in Azure DevOps
|
|
482
|
+
ado-sync push
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Day-to-day: local changes first
|
|
486
|
+
|
|
487
|
+
```bash
|
|
488
|
+
# Edit your .feature or .md files locally
|
|
489
|
+
# Then push changes to Azure DevOps
|
|
490
|
+
ado-sync push
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Day-to-day: Azure changes first
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
# Someone edited a test case in Azure DevOps Test Plans UI
|
|
497
|
+
# Pull the changes into your local files
|
|
498
|
+
ado-sync pull
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### Check for drift before a PR
|
|
502
|
+
|
|
503
|
+
```bash
|
|
504
|
+
ado-sync status
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## Troubleshooting
|
|
510
|
+
|
|
511
|
+
**`No config file found`**
|
|
512
|
+
Run `ado-sync init` or pass `-c path/to/config.json`.
|
|
513
|
+
|
|
514
|
+
**`Environment variable 'X' is not set`**
|
|
515
|
+
Your config references `$X` in `auth.token` but the variable is not exported. Run `export X=...` or add it to a `.env` file.
|
|
516
|
+
|
|
517
|
+
**Test Case created but ID not written back**
|
|
518
|
+
Check that the local file is writable. On the next `push` it will try again.
|
|
519
|
+
|
|
520
|
+
**`Test case #N not found in Azure DevOps`**
|
|
521
|
+
The test case was deleted in Azure. Remove the ID tag from the local file to recreate it, or restore the test case in Azure.
|
|
522
|
+
|
|
523
|
+
**`Failed to parse <file>`**
|
|
524
|
+
Gherkin syntax error in a `.feature` file. Run `npx cucumber-js --dry-run` to identify the problem line.
|
|
525
|
+
|
|
526
|
+
**Changes not detected on push**
|
|
527
|
+
The comparison is title + step text. Changing only description or area path requires a manual update via `ado-sync push` after touching a step.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as CoreApi from 'azure-devops-node-api/CoreApi';
|
|
2
|
+
import * as TestPlanApi from 'azure-devops-node-api/TestPlanApi';
|
|
3
|
+
import * as WorkItemTrackingApi from 'azure-devops-node-api/WorkItemTrackingApi';
|
|
4
|
+
import { SyncConfig } from '../types';
|
|
5
|
+
export declare class AzureClient {
|
|
6
|
+
private config;
|
|
7
|
+
private connection;
|
|
8
|
+
private _witApi;
|
|
9
|
+
private _testPlanApi;
|
|
10
|
+
private _coreApi;
|
|
11
|
+
private constructor();
|
|
12
|
+
static create(config: SyncConfig): Promise<AzureClient>;
|
|
13
|
+
private connect;
|
|
14
|
+
getWitApi(): Promise<WorkItemTrackingApi.IWorkItemTrackingApi>;
|
|
15
|
+
getTestPlanApi(): Promise<TestPlanApi.ITestPlanApi>;
|
|
16
|
+
getCoreApi(): Promise<CoreApi.ICoreApi>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AzureClient = void 0;
|
|
37
|
+
const identity_1 = require("@azure/identity");
|
|
38
|
+
const azdev = __importStar(require("azure-devops-node-api"));
|
|
39
|
+
const azure_devops_node_api_1 = require("azure-devops-node-api");
|
|
40
|
+
class AzureClient {
|
|
41
|
+
config;
|
|
42
|
+
connection;
|
|
43
|
+
_witApi;
|
|
44
|
+
_testPlanApi;
|
|
45
|
+
_coreApi;
|
|
46
|
+
constructor(config) {
|
|
47
|
+
this.config = config;
|
|
48
|
+
}
|
|
49
|
+
static async create(config) {
|
|
50
|
+
const client = new AzureClient(config);
|
|
51
|
+
await client.connect();
|
|
52
|
+
return client;
|
|
53
|
+
}
|
|
54
|
+
async connect() {
|
|
55
|
+
const { orgUrl, auth } = this.config;
|
|
56
|
+
let authHandler;
|
|
57
|
+
if (auth.type === 'managedIdentity') {
|
|
58
|
+
const credential = new identity_1.DefaultAzureCredential();
|
|
59
|
+
const token = await credential.getToken(auth.applicationIdURI);
|
|
60
|
+
authHandler = azdev.getBearerHandler(token.token);
|
|
61
|
+
}
|
|
62
|
+
else if (auth.type === 'accessToken') {
|
|
63
|
+
authHandler = azdev.getBearerHandler(auth.token);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// PAT
|
|
67
|
+
authHandler = azdev.getPersonalAccessTokenHandler(auth.token);
|
|
68
|
+
}
|
|
69
|
+
this.connection = new azure_devops_node_api_1.WebApi(orgUrl, authHandler);
|
|
70
|
+
}
|
|
71
|
+
async getWitApi() {
|
|
72
|
+
if (!this._witApi)
|
|
73
|
+
this._witApi = await this.connection.getWorkItemTrackingApi();
|
|
74
|
+
return this._witApi;
|
|
75
|
+
}
|
|
76
|
+
async getTestPlanApi() {
|
|
77
|
+
if (!this._testPlanApi)
|
|
78
|
+
this._testPlanApi = await this.connection.getTestPlanApi();
|
|
79
|
+
return this._testPlanApi;
|
|
80
|
+
}
|
|
81
|
+
async getCoreApi() {
|
|
82
|
+
if (!this._coreApi)
|
|
83
|
+
this._coreApi = await this.connection.getCoreApi();
|
|
84
|
+
return this._coreApi;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.AzureClient = AzureClient;
|
|
88
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/azure/client.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,8CAAyD;AACzD,6DAA+C;AAC/C,iEAA+C;AAQ/C,MAAa,WAAW;IAMM;IALpB,UAAU,CAAU;IACpB,OAAO,CAA4C;IACnD,YAAY,CAA4B;IACxC,QAAQ,CAAoB;IAEpC,YAA4B,MAAkB;QAAlB,WAAM,GAAN,MAAM,CAAY;IAAG,CAAC;IAElD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAkB;QACpC,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;QACvB,OAAO,MAAM,CAAC;IAChB,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;QAErC,IAAI,WAA4B,CAAC;QAEjC,IAAI,IAAI,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;YACpC,MAAM,UAAU,GAAG,IAAI,iCAAsB,EAAE,CAAC;YAChD,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAiB,CAAC,CAAC;YAChE,WAAW,GAAG,KAAK,CAAC,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACpD,CAAC;aAAM,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YACvC,WAAW,GAAG,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAM,CAAC,CAAC;QACpD,CAAC;aAAM,CAAC;YACN,MAAM;YACN,WAAW,GAAG,KAAK,CAAC,6BAA6B,CAAC,IAAI,CAAC,KAAM,CAAC,CAAC;QACjE,CAAC;QAED,IAAI,CAAC,UAAU,GAAG,IAAI,8BAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpD,CAAC;IAED,KAAK,CAAC,SAAS;QACb,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,IAAI,CAAC,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC;QACjF,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,IAAI,CAAC,YAAY,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,cAAc,EAAE,CAAC;QACnF,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,UAAU;QACd,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;QACvE,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;CACF;AA/CD,kCA+CC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRUD operations for Azure DevOps Test Cases (Work Items of type "Test Case").
|
|
3
|
+
*
|
|
4
|
+
* Azure stores test steps as XML in the field:
|
|
5
|
+
* Microsoft.VSTS.TCM.Steps
|
|
6
|
+
*
|
|
7
|
+
* Step XML shape:
|
|
8
|
+
* <steps id="0" last="N">
|
|
9
|
+
* <step id="2" type="ValidateStep">
|
|
10
|
+
* <parameterizedString isformatted="true"><DIV>Action text</DIV></parameterizedString>
|
|
11
|
+
* <parameterizedString isformatted="true"><DIV>Expected text</DIV></parameterizedString>
|
|
12
|
+
* <description/>
|
|
13
|
+
* </step>
|
|
14
|
+
* </steps>
|
|
15
|
+
*/
|
|
16
|
+
import { AzureTestCase, ParsedTest, SyncConfig } from '../types';
|
|
17
|
+
import { AzureClient } from './client';
|
|
18
|
+
export declare function getTestCase(client: AzureClient, id: number): Promise<AzureTestCase | null>;
|
|
19
|
+
export declare function createTestCase(client: AzureClient, test: ParsedTest, config: SyncConfig): Promise<number>;
|
|
20
|
+
export declare function updateTestCase(client: AzureClient, id: number, test: ParsedTest, config: SyncConfig): Promise<void>;
|
|
21
|
+
export declare function updateLocalFromAzure(client: AzureClient, id: number): Promise<AzureTestCase | null>;
|
|
22
|
+
export declare function getTestCasesInSuite(client: AzureClient, config: SyncConfig, suiteId?: number): Promise<AzureTestCase[]>;
|