ado-sync 0.1.30 → 0.1.31
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 +391 -22
- package/dist/ai/summarizer.d.ts +73 -0
- package/dist/ai/summarizer.js +789 -0
- package/dist/ai/summarizer.js.map +1 -0
- package/dist/azure/test-cases.js +17 -2
- package/dist/azure/test-cases.js.map +1 -1
- package/dist/cli.js +29 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/parsers/dart.d.ts +31 -0
- package/dist/parsers/dart.js +245 -0
- package/dist/parsers/dart.js.map +1 -0
- package/dist/parsers/javascript.d.ts +17 -7
- package/dist/parsers/javascript.js +20 -9
- package/dist/parsers/javascript.js.map +1 -1
- package/dist/parsers/swift.d.ts +34 -0
- package/dist/parsers/swift.js +250 -0
- package/dist/parsers/swift.js.map +1 -0
- package/dist/parsers/testcafe.d.ts +32 -0
- package/dist/parsers/testcafe.js +231 -0
- package/dist/parsers/testcafe.js.map +1 -0
- package/dist/sync/engine.d.ts +4 -1
- package/dist/sync/engine.js +43 -1
- package/dist/sync/engine.js.map +1 -1
- package/dist/sync/writeback.d.ts +6 -7
- package/dist/sync/writeback.js +22 -7
- package/dist/sync/writeback.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/docs/advanced.md +140 -0
- package/docs/configuration.md +1 -1
- package/docs/spec-formats.md +549 -0
- package/package.json +3 -2
package/docs/spec-formats.md
CHANGED
|
@@ -375,6 +375,7 @@ class TestCheckout:
|
|
|
375
375
|
|
|
376
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
377
|
|
|
378
|
+
> **Playwright Test** (`@playwright/test`) has its own type: use `local.type: "playwright"` instead — see below.
|
|
378
379
|
> **Cucumber `.feature` files** are handled by `local.type: "gherkin"` — not this type.
|
|
379
380
|
|
|
380
381
|
### Detected test functions
|
|
@@ -451,6 +452,553 @@ describe('Checkout', () => {
|
|
|
451
452
|
|
|
452
453
|
---
|
|
453
454
|
|
|
455
|
+
## Playwright Test `.js` / `.ts`
|
|
456
|
+
|
|
457
|
+
Set `local.type: "playwright"`. Supports **Playwright Test** (`@playwright/test`) for both JavaScript and TypeScript. Each `test()` call becomes one Test Case.
|
|
458
|
+
|
|
459
|
+
> **Jest/Jasmine/WebdriverIO** tests use `local.type: "javascript"` — not this type.
|
|
460
|
+
|
|
461
|
+
### Detected test functions
|
|
462
|
+
|
|
463
|
+
| Function | Description |
|
|
464
|
+
|----------|-------------|
|
|
465
|
+
| `test(title, fn)` | Standard test |
|
|
466
|
+
| `test.only` | Focused test |
|
|
467
|
+
| `test.skip` | Skipped test (still synced) |
|
|
468
|
+
| `test.fixme` | Test marked as broken (still synced, tagged `wip`) |
|
|
469
|
+
| `test.fail` | Test expected to fail (still synced) |
|
|
470
|
+
| `test.describe` | Nested describe block |
|
|
471
|
+
| `test.describe.parallel` | Parallel describe block |
|
|
472
|
+
| `test.describe.serial` | Serial describe block |
|
|
473
|
+
|
|
474
|
+
### Source mapping
|
|
475
|
+
|
|
476
|
+
| Playwright source | Azure TC field |
|
|
477
|
+
|-------------------|---------------|
|
|
478
|
+
| JSDoc `/** ... */` first non-numbered line | TC **Title** |
|
|
479
|
+
| Numbered lines `N. text` in JSDoc | TC **Steps** (action) |
|
|
480
|
+
| Numbered lines `N. Check: text` in JSDoc | TC **Steps** (expected result, when `useExpectedResult: true`) |
|
|
481
|
+
| `// @tags: smoke, regression` above `test()` | TC **Tags** |
|
|
482
|
+
| `// @tc:ID` above `test()` | TC ID (written back after first push) |
|
|
483
|
+
| `{basename} > {describe} > {test title}` | `AutomatedTestName` |
|
|
484
|
+
|
|
485
|
+
### TypeScript example
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
import { test, expect } from '@playwright/test';
|
|
489
|
+
|
|
490
|
+
test.describe('SauceDemo Checkout', () => {
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Complete checkout with valid details
|
|
494
|
+
* 1. Log in as standard_user
|
|
495
|
+
* 2. Add Sauce Labs Backpack to cart
|
|
496
|
+
* 3. Proceed through checkout and enter name and zip
|
|
497
|
+
* 4. Check: Order confirmation page is displayed
|
|
498
|
+
*/
|
|
499
|
+
// @tc:1041 // written back by ado-sync after first push
|
|
500
|
+
// @tags: smoke, checkout
|
|
501
|
+
test('completes checkout with valid details', async ({ page }) => {
|
|
502
|
+
// ...
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// @tc:1042
|
|
506
|
+
test.fixme('promo code applies discount', async ({ page }) => {
|
|
507
|
+
// known issue — still synced to Azure, tagged wip
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
test.describe.parallel('Cart assertions', () => {
|
|
512
|
+
|
|
513
|
+
// @tc:1043
|
|
514
|
+
test('cart badge shows correct count', async ({ page }) => {
|
|
515
|
+
// ...
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### JavaScript example (CommonJS)
|
|
521
|
+
|
|
522
|
+
```javascript
|
|
523
|
+
const { test, expect } = require('@playwright/test');
|
|
524
|
+
|
|
525
|
+
test.describe('SauceDemo Login', () => {
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Valid credentials redirect to inventory page
|
|
529
|
+
* 1. Navigate to https://www.saucedemo.com
|
|
530
|
+
* 2. Enter username "standard_user" and password "secret_sauce"
|
|
531
|
+
* 3. Click the login button
|
|
532
|
+
* 4. Check: URL contains "inventory.html"
|
|
533
|
+
*/
|
|
534
|
+
// @tc:1044
|
|
535
|
+
test('valid credentials redirect to inventory', async ({ page }) => {
|
|
536
|
+
// ...
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### Recommended config
|
|
542
|
+
|
|
543
|
+
```json
|
|
544
|
+
{
|
|
545
|
+
"local": {
|
|
546
|
+
"type": "playwright",
|
|
547
|
+
"include": ["tests/**/*.spec.ts", "tests/**/*.spec.js"],
|
|
548
|
+
"exclude": ["**/*.helper.ts", "**/*.fixture.ts"]
|
|
549
|
+
},
|
|
550
|
+
"sync": {
|
|
551
|
+
"markAutomated": true
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
### Notes
|
|
557
|
+
|
|
558
|
+
- **`pull` is not supported** for Playwright files. Only `push` applies.
|
|
559
|
+
- **`test.describe` nesting** — arbitrarily deep nesting is supported. All enclosing describe titles are included in the `automatedTestName`.
|
|
560
|
+
- **`test.fixme` / `test.fail`** — both are parsed and synced as normal test cases. Add `// @tags: wip` above `test.fixme` to mark them in Azure.
|
|
561
|
+
- **Publishing results** — set `testResultFormat: playwrightJson` when using `publish-test-results`.
|
|
562
|
+
|
|
563
|
+
---
|
|
564
|
+
|
|
565
|
+
## Puppeteer `.js` / `.ts`
|
|
566
|
+
|
|
567
|
+
Set `local.type: "puppeteer"`. Supports **Puppeteer** tests written with Jest or Mocha as the test runner. Puppeteer tests use the same `describe()` / `it()` / `test()` API as Jest, so this type is an alias for `javascript` — choose it for clarity when your project uses Puppeteer.
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
import puppeteer from 'puppeteer';
|
|
571
|
+
|
|
572
|
+
describe('SauceDemo Login', () => {
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Valid credentials redirect to inventory page
|
|
576
|
+
* 1. Navigate to https://www.saucedemo.com
|
|
577
|
+
* 2. Enter "standard_user" into the username field
|
|
578
|
+
* 3. Enter "secret_sauce" into the password field
|
|
579
|
+
* 4. Click the login button
|
|
580
|
+
* 5. Check: URL contains "inventory"
|
|
581
|
+
*/
|
|
582
|
+
// @tc:1060
|
|
583
|
+
it('redirects to inventory on valid login', async () => {
|
|
584
|
+
// ...
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### Recommended config
|
|
590
|
+
|
|
591
|
+
```json
|
|
592
|
+
{
|
|
593
|
+
"local": {
|
|
594
|
+
"type": "puppeteer",
|
|
595
|
+
"include": ["tests/**/*.test.ts"],
|
|
596
|
+
"exclude": ["**/*.helper.ts"]
|
|
597
|
+
},
|
|
598
|
+
"sync": { "markAutomated": true }
|
|
599
|
+
}
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
### Notes
|
|
603
|
+
|
|
604
|
+
- ID writeback format: `// @tc:12345` immediately above the `it()`/`test()` line.
|
|
605
|
+
- `pull` is not supported for Puppeteer files — only `push` applies.
|
|
606
|
+
- `describe()` nesting is fully supported.
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
## Cypress `.cy.js` / `.cy.ts`
|
|
611
|
+
|
|
612
|
+
Set `local.type: "cypress"`. Supports **Cypress** tests. Cypress uses `describe()` / `context()` / `it()` / `specify()` — all four are detected.
|
|
613
|
+
|
|
614
|
+
```typescript
|
|
615
|
+
describe('SauceDemo Login', () => {
|
|
616
|
+
|
|
617
|
+
context('valid credentials', () => {
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Successful login redirects to inventory page
|
|
621
|
+
* 1. Visit https://www.saucedemo.com
|
|
622
|
+
* 2. Enter "standard_user" into the username field
|
|
623
|
+
* 3. Enter "secret_sauce" into the password field
|
|
624
|
+
* 4. Click the login button
|
|
625
|
+
* 5. Check: URL contains "inventory"
|
|
626
|
+
*/
|
|
627
|
+
// @tc:1070
|
|
628
|
+
// @tags: smoke
|
|
629
|
+
it('redirects to inventory', () => {
|
|
630
|
+
// ...
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
// @tc:1071
|
|
634
|
+
specify('locked out user sees error', () => {
|
|
635
|
+
// ...
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### Recommended config
|
|
642
|
+
|
|
643
|
+
```json
|
|
644
|
+
{
|
|
645
|
+
"local": {
|
|
646
|
+
"type": "cypress",
|
|
647
|
+
"include": ["cypress/e2e/**/*.cy.ts", "cypress/e2e/**/*.cy.js"],
|
|
648
|
+
"exclude": ["cypress/support/**"]
|
|
649
|
+
},
|
|
650
|
+
"sync": { "markAutomated": true }
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Notes
|
|
655
|
+
|
|
656
|
+
- `context()` is treated as a `describe()` alias (Cypress convention).
|
|
657
|
+
- `specify()` is treated as an `it()` alias (Cypress convention).
|
|
658
|
+
- ID writeback format: `// @tc:12345` immediately above the `it()`/`specify()` line.
|
|
659
|
+
- `pull` is not supported for Cypress files — only `push` applies.
|
|
660
|
+
|
|
661
|
+
---
|
|
662
|
+
|
|
663
|
+
## TestCafe `.js` / `.ts`
|
|
664
|
+
|
|
665
|
+
Set `local.type: "testcafe"`. Supports **TestCafe** tests using the `fixture` / `test` API.
|
|
666
|
+
|
|
667
|
+
```typescript
|
|
668
|
+
fixture('SauceDemo Login')
|
|
669
|
+
.page('https://www.saucedemo.com');
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Valid credentials redirect to inventory page
|
|
673
|
+
* 1. Type "standard_user" into the username field
|
|
674
|
+
* 2. Type "secret_sauce" into the password field
|
|
675
|
+
* 3. Click the login button
|
|
676
|
+
* 4. Check: URL contains "inventory"
|
|
677
|
+
*/
|
|
678
|
+
// @tc:1080
|
|
679
|
+
// @tags: smoke
|
|
680
|
+
test('redirects to inventory on valid login', async t => {
|
|
681
|
+
// ...
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
// @tc:1081
|
|
685
|
+
test.skip('locked out user sees error', async t => {
|
|
686
|
+
// skipped — still synced to Azure
|
|
687
|
+
});
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### Recommended config
|
|
691
|
+
|
|
692
|
+
```json
|
|
693
|
+
{
|
|
694
|
+
"local": {
|
|
695
|
+
"type": "testcafe",
|
|
696
|
+
"include": ["tests/**/*.ts"],
|
|
697
|
+
"exclude": ["tests/helpers/**"]
|
|
698
|
+
},
|
|
699
|
+
"sync": { "markAutomated": true }
|
|
700
|
+
}
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### Notes
|
|
704
|
+
|
|
705
|
+
- The `fixture()` title is used as the group / suite name and included in `automatedTestName`.
|
|
706
|
+
- `test.skip()` and `test.only()` are both parsed and synced.
|
|
707
|
+
- ID writeback format: `// @tc:12345` immediately above the `test()` line.
|
|
708
|
+
- `pull` is not supported for TestCafe files — only `push` applies.
|
|
709
|
+
- JSDoc `/** ... */` above a `test()` call is parsed for TC title and numbered steps, same as Playwright and Jest.
|
|
710
|
+
|
|
711
|
+
---
|
|
712
|
+
|
|
713
|
+
## Appium
|
|
714
|
+
|
|
715
|
+
Appium tests are written in your language of choice. Use the `local.type` that matches the language:
|
|
716
|
+
|
|
717
|
+
| Appium language binding | Use `local.type` | Notes |
|
|
718
|
+
|-------------------------|-----------------|-------|
|
|
719
|
+
| JavaScript / TypeScript (WebdriverIO, Mocha, Jest) | `javascript` | Detects `describe()`/`it()`/`test()` |
|
|
720
|
+
| Java (JUnit 4, JUnit 5, TestNG) | `java` | Detects `@Test` annotations |
|
|
721
|
+
| Python (pytest) | `python` | Detects `def test_*()` functions |
|
|
722
|
+
| C# (NUnit, MSTest) | `csharp` | Detects `[TestMethod]`/`[Test]` attributes |
|
|
723
|
+
|
|
724
|
+
**JavaScript/TypeScript Appium example:**
|
|
725
|
+
|
|
726
|
+
```typescript
|
|
727
|
+
// @tc:12345
|
|
728
|
+
// @smoke
|
|
729
|
+
describe('Login', () => {
|
|
730
|
+
/**
|
|
731
|
+
* User can log in with valid credentials
|
|
732
|
+
*
|
|
733
|
+
* 1. Navigate to the login screen
|
|
734
|
+
* 2. Enter email into the email field
|
|
735
|
+
* 3. Enter password into the password field
|
|
736
|
+
* 4. Tap the login button
|
|
737
|
+
* 5. Check: Home screen is displayed
|
|
738
|
+
*/
|
|
739
|
+
it('can log in with valid credentials', async () => {
|
|
740
|
+
await driver.execute('mobile: launchApp', { bundleId: 'com.example.app' });
|
|
741
|
+
await $('~emailField').setValue('user@example.com');
|
|
742
|
+
await $('~passwordField').setValue('secret');
|
|
743
|
+
await $('~loginButton').click();
|
|
744
|
+
await expect($('~homeScreen')).toBeDisplayed();
|
|
745
|
+
});
|
|
746
|
+
});
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
Recommended `ado-sync.json`:
|
|
750
|
+
|
|
751
|
+
```json
|
|
752
|
+
{
|
|
753
|
+
"local": {
|
|
754
|
+
"type": "javascript",
|
|
755
|
+
"include": ["test/**/*.spec.ts"]
|
|
756
|
+
},
|
|
757
|
+
"sync": { "markAutomated": true }
|
|
758
|
+
}
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
---
|
|
762
|
+
|
|
763
|
+
## Detox `.js` / `.ts`
|
|
764
|
+
|
|
765
|
+
Set `local.type: "detox"` for React Native end-to-end tests using [Detox](https://wix.github.io/Detox/).
|
|
766
|
+
|
|
767
|
+
Detox uses the same Jest `describe()`/`it()` API as Jest — the existing JavaScript parser handles it.
|
|
768
|
+
|
|
769
|
+
```typescript
|
|
770
|
+
// @tc:12345
|
|
771
|
+
// @smoke
|
|
772
|
+
describe('Login flow', () => {
|
|
773
|
+
/**
|
|
774
|
+
* User can log in with valid credentials
|
|
775
|
+
*
|
|
776
|
+
* 1. Launch the app
|
|
777
|
+
* 2. Type email into the email field
|
|
778
|
+
* 3. Tap the login button
|
|
779
|
+
* 4. Check: Welcome screen is visible
|
|
780
|
+
*/
|
|
781
|
+
it('logs in with valid credentials', async () => {
|
|
782
|
+
await device.launchApp();
|
|
783
|
+
await element(by.id('email')).typeText('user@example.com');
|
|
784
|
+
await element(by.id('password')).typeText('secret');
|
|
785
|
+
await element(by.id('login-btn')).tap();
|
|
786
|
+
await expect(element(by.id('welcome-screen'))).toBeVisible();
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
Recommended `ado-sync.json`:
|
|
792
|
+
|
|
793
|
+
```json
|
|
794
|
+
{
|
|
795
|
+
"local": {
|
|
796
|
+
"type": "detox",
|
|
797
|
+
"include": ["e2e/**/*.test.ts"],
|
|
798
|
+
"exclude": ["e2e/setup/**"]
|
|
799
|
+
},
|
|
800
|
+
"sync": { "markAutomated": true }
|
|
801
|
+
}
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
**Notes:**
|
|
805
|
+
- `describe()` blocks define the test group (same as Jest).
|
|
806
|
+
- `// @tc:12345` is inserted immediately above the `it()`/`test()` line on writeback.
|
|
807
|
+
- `pull` is not supported for Detox files — only `push`.
|
|
808
|
+
|
|
809
|
+
---
|
|
810
|
+
|
|
811
|
+
## Espresso `.java` / `.kt`
|
|
812
|
+
|
|
813
|
+
Set `local.type: "espresso"` for Android UI tests using [Espresso](https://developer.android.com/training/testing/espresso).
|
|
814
|
+
|
|
815
|
+
Espresso tests use JUnit 4 `@Test` annotations — the same parser as `java` handles them.
|
|
816
|
+
|
|
817
|
+
```java
|
|
818
|
+
@RunWith(AndroidJUnit4.class)
|
|
819
|
+
public class LoginInstrumentedTest {
|
|
820
|
+
|
|
821
|
+
// @tc:12345
|
|
822
|
+
// @smoke
|
|
823
|
+
/**
|
|
824
|
+
* User can log in with valid credentials
|
|
825
|
+
*
|
|
826
|
+
* 1. Type email into the email field
|
|
827
|
+
* 2. Type password into the password field
|
|
828
|
+
* 3. Click the login button
|
|
829
|
+
* 4. Check: Welcome screen is displayed
|
|
830
|
+
*/
|
|
831
|
+
@Test
|
|
832
|
+
public void userCanLoginWithValidCredentials() {
|
|
833
|
+
onView(withId(R.id.email)).perform(typeText("user@example.com"), closeSoftKeyboard());
|
|
834
|
+
onView(withId(R.id.password)).perform(typeText("secret"), closeSoftKeyboard());
|
|
835
|
+
onView(withId(R.id.login_button)).perform(click());
|
|
836
|
+
onView(withId(R.id.welcome_screen)).check(matches(isDisplayed()));
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
**Kotlin** (same `@Test` detection):
|
|
842
|
+
|
|
843
|
+
```kotlin
|
|
844
|
+
@RunWith(AndroidJUnit4::class)
|
|
845
|
+
class LoginInstrumentedTest {
|
|
846
|
+
|
|
847
|
+
// @tc:12345
|
|
848
|
+
@Test
|
|
849
|
+
fun `user can login with valid credentials`() {
|
|
850
|
+
onView(withId(R.id.email)).perform(typeText("user@example.com"), closeSoftKeyboard())
|
|
851
|
+
onView(withId(R.id.login_button)).perform(click())
|
|
852
|
+
onView(withId(R.id.welcome_screen)).check(matches(isDisplayed()))
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
Recommended `ado-sync.json`:
|
|
858
|
+
|
|
859
|
+
```json
|
|
860
|
+
{
|
|
861
|
+
"local": {
|
|
862
|
+
"type": "espresso",
|
|
863
|
+
"include": ["app/src/androidTest/**/*.java", "app/src/androidTest/**/*.kt"],
|
|
864
|
+
"exclude": ["**/*BaseTest.java", "**/*Helper.java"]
|
|
865
|
+
},
|
|
866
|
+
"sync": { "markAutomated": true }
|
|
867
|
+
}
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
**Notes:**
|
|
871
|
+
- ID writeback format: `// @tc:12345` immediately above the `@Test` annotation line.
|
|
872
|
+
- Kotlin backtick method names (`` fun `my test name`() ``) are supported — the method name is used as the fallback title.
|
|
873
|
+
- `pull` is not supported — only `push`.
|
|
874
|
+
|
|
875
|
+
---
|
|
876
|
+
|
|
877
|
+
## XCUITest `.swift`
|
|
878
|
+
|
|
879
|
+
Set `local.type: "xcuitest"` for iOS and macOS UI automation tests using Apple's [XCTest](https://developer.apple.com/documentation/xctest) framework.
|
|
880
|
+
|
|
881
|
+
Tests are `func test*()` methods inside classes that extend `XCTestCase`.
|
|
882
|
+
|
|
883
|
+
```swift
|
|
884
|
+
import XCTest
|
|
885
|
+
|
|
886
|
+
class LoginTests: XCTestCase {
|
|
887
|
+
|
|
888
|
+
// @tc:12345
|
|
889
|
+
// @smoke
|
|
890
|
+
/// User can log in with valid credentials
|
|
891
|
+
///
|
|
892
|
+
/// 1. Launch the app
|
|
893
|
+
/// 2. Enter email into the email field
|
|
894
|
+
/// 3. Enter password into the password field
|
|
895
|
+
/// 4. Tap the login button
|
|
896
|
+
/// 5. Check: Welcome screen is visible
|
|
897
|
+
func testUserCanLoginWithValidCredentials() {
|
|
898
|
+
let app = XCUIApplication()
|
|
899
|
+
app.launch()
|
|
900
|
+
app.textFields["Email"].typeText("user@example.com")
|
|
901
|
+
app.secureTextFields["Password"].typeText("secret")
|
|
902
|
+
app.buttons["Log In"].tap()
|
|
903
|
+
XCTAssertTrue(app.staticTexts["Welcome"].exists)
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
func testLoginFailsWithInvalidPassword() {
|
|
907
|
+
let app = XCUIApplication()
|
|
908
|
+
app.launch()
|
|
909
|
+
app.textFields["Email"].typeText("user@example.com")
|
|
910
|
+
app.secureTextFields["Password"].typeText("wrong")
|
|
911
|
+
app.buttons["Log In"].tap()
|
|
912
|
+
XCTAssertTrue(app.staticTexts["Invalid credentials"].exists)
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
Recommended `ado-sync.json`:
|
|
918
|
+
|
|
919
|
+
```json
|
|
920
|
+
{
|
|
921
|
+
"local": {
|
|
922
|
+
"type": "xcuitest",
|
|
923
|
+
"include": ["UITests/**/*.swift"],
|
|
924
|
+
"exclude": ["UITests/**/*Helper.swift", "UITests/**/*Base.swift"]
|
|
925
|
+
},
|
|
926
|
+
"sync": { "markAutomated": true }
|
|
927
|
+
}
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
**Notes:**
|
|
931
|
+
- The enclosing class name is used as the test group (`automatedTestName = FileName > ClassName > methodName`).
|
|
932
|
+
- Both `///` triple-slash (Swift idiomatic) and `/** ... */` block doc comments are parsed for TC title and numbered steps.
|
|
933
|
+
- Method name → title fallback: `testUserCanLogin` → "User can login", `test_submit_form` → "Submit form".
|
|
934
|
+
- ID writeback format: `// @tc:12345` immediately above the `func test*()` line.
|
|
935
|
+
- `pull` is not supported — only `push`.
|
|
936
|
+
|
|
937
|
+
---
|
|
938
|
+
|
|
939
|
+
## Flutter `_test.dart`
|
|
940
|
+
|
|
941
|
+
Set `local.type: "flutter"` for Flutter widget tests and integration tests using the [`flutter_test`](https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html) package.
|
|
942
|
+
|
|
943
|
+
```dart
|
|
944
|
+
import 'package:flutter_test/flutter_test.dart';
|
|
945
|
+
|
|
946
|
+
void main() {
|
|
947
|
+
group('Login', () {
|
|
948
|
+
// @tc:12345
|
|
949
|
+
// @smoke
|
|
950
|
+
/// User can log in with valid credentials
|
|
951
|
+
///
|
|
952
|
+
/// 1. Tap the email field
|
|
953
|
+
/// 2. Enter the email address
|
|
954
|
+
/// 3. Tap the password field
|
|
955
|
+
/// 4. Enter the password
|
|
956
|
+
/// 5. Tap the Login button
|
|
957
|
+
/// 6. Check: Welcome screen is visible
|
|
958
|
+
testWidgets('can log in with valid credentials', (WidgetTester tester) async {
|
|
959
|
+
await tester.pumpWidget(MyApp());
|
|
960
|
+
await tester.enterText(find.byKey(Key('email')), 'user@example.com');
|
|
961
|
+
await tester.enterText(find.byKey(Key('password')), 'secret');
|
|
962
|
+
await tester.tap(find.byKey(Key('login-btn')));
|
|
963
|
+
await tester.pumpAndSettle();
|
|
964
|
+
expect(find.text('Welcome'), findsOneWidget);
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
// @tc:12346
|
|
968
|
+
testWidgets('shows error for invalid credentials', (WidgetTester tester) async {
|
|
969
|
+
await tester.pumpWidget(MyApp());
|
|
970
|
+
await tester.enterText(find.byKey(Key('email')), 'bad@example.com');
|
|
971
|
+
await tester.enterText(find.byKey(Key('password')), 'wrong');
|
|
972
|
+
await tester.tap(find.byKey(Key('login-btn')));
|
|
973
|
+
await tester.pumpAndSettle();
|
|
974
|
+
expect(find.text('Invalid credentials'), findsOneWidget);
|
|
975
|
+
});
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
Recommended `ado-sync.json`:
|
|
981
|
+
|
|
982
|
+
```json
|
|
983
|
+
{
|
|
984
|
+
"local": {
|
|
985
|
+
"type": "flutter",
|
|
986
|
+
"include": ["test/**/*_test.dart", "integration_test/**/*_test.dart"]
|
|
987
|
+
},
|
|
988
|
+
"sync": { "markAutomated": true }
|
|
989
|
+
}
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
**Notes:**
|
|
993
|
+
- `group('title', () { ... })` is the describe equivalent. Nested groups are supported.
|
|
994
|
+
- `testWidgets()`, `test()`, and `testUI()` (integration_test alias) are all detected.
|
|
995
|
+
- The file base name is stripped of `_test.dart` / `.dart` suffixes for `automatedTestName`.
|
|
996
|
+
- Both `///` triple-slash and `/** ... */` block doc comments are parsed.
|
|
997
|
+
- ID writeback format: `// @tc:12345` immediately above the `testWidgets()`/`test()` line.
|
|
998
|
+
- `pull` is not supported — only `push`.
|
|
999
|
+
|
|
1000
|
+
---
|
|
1001
|
+
|
|
454
1002
|
## CSV `.csv`
|
|
455
1003
|
|
|
456
1004
|
Set `local.type: "csv"` to parse Azure DevOps / SpecSync tabular CSV exports.
|
|
@@ -495,6 +1043,7 @@ After a first push, ado-sync writes the Azure TC ID back into the local file.
|
|
|
495
1043
|
| Java JUnit 4 / TestNG | `// @tc:12345` comment on the line above `@Test` |
|
|
496
1044
|
| Python pytest | `@pytest.mark.tc(12345)` decorator above `def test_*` |
|
|
497
1045
|
| JavaScript/TS | `// @tc:12345` comment on the line above `it()`/`test()` |
|
|
1046
|
+
| Playwright | `// @tc:12345` comment on the line above `test()` |
|
|
498
1047
|
| CSV | Numeric ID in column A of the matching title row |
|
|
499
1048
|
| Excel | Numeric ID in cell A of the matching title row |
|
|
500
1049
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ado-sync",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
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"
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"fast-xml-parser": "^4.4.1",
|
|
43
43
|
"glob": "^13.0.6",
|
|
44
44
|
"js-yaml": "^4.1.0",
|
|
45
|
-
"jszip": "^3.10.1"
|
|
45
|
+
"jszip": "^3.10.1",
|
|
46
|
+
"node-llama-cpp": "^3.0.0"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@types/js-yaml": "^4.0.9",
|