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.
@@ -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.30",
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",