@sdods/cli 0.2.0

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 (124) hide show
  1. package/LICENSE +17 -0
  2. package/bin/sdods.js +19 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/bin.d.ts +2 -0
  5. package/dist/bin.js +11 -0
  6. package/dist/commands/agent.d.ts +3 -0
  7. package/dist/commands/agent.js +219 -0
  8. package/dist/commands/analyze.d.ts +20 -0
  9. package/dist/commands/analyze.js +216 -0
  10. package/dist/commands/auth.d.ts +7 -0
  11. package/dist/commands/auth.js +97 -0
  12. package/dist/commands/browsers.d.ts +17 -0
  13. package/dist/commands/browsers.js +103 -0
  14. package/dist/commands/completion.d.ts +17 -0
  15. package/dist/commands/completion.js +143 -0
  16. package/dist/commands/config.d.ts +3 -0
  17. package/dist/commands/config.js +79 -0
  18. package/dist/commands/coverage.d.ts +3 -0
  19. package/dist/commands/coverage.js +65 -0
  20. package/dist/commands/data.d.ts +7 -0
  21. package/dist/commands/data.js +188 -0
  22. package/dist/commands/db.d.ts +3 -0
  23. package/dist/commands/db.js +241 -0
  24. package/dist/commands/doctor.d.ts +3 -0
  25. package/dist/commands/doctor.js +323 -0
  26. package/dist/commands/env.d.ts +3 -0
  27. package/dist/commands/env.js +65 -0
  28. package/dist/commands/features.d.ts +3 -0
  29. package/dist/commands/features.js +57 -0
  30. package/dist/commands/feedback.d.ts +7 -0
  31. package/dist/commands/feedback.js +96 -0
  32. package/dist/commands/har.d.ts +3 -0
  33. package/dist/commands/har.js +124 -0
  34. package/dist/commands/heal.d.ts +4 -0
  35. package/dist/commands/heal.js +63 -0
  36. package/dist/commands/init.d.ts +38 -0
  37. package/dist/commands/init.js +363 -0
  38. package/dist/commands/insights.d.ts +7 -0
  39. package/dist/commands/insights.js +127 -0
  40. package/dist/commands/integrations.d.ts +14 -0
  41. package/dist/commands/integrations.js +306 -0
  42. package/dist/commands/lint.d.ts +3 -0
  43. package/dist/commands/lint.js +74 -0
  44. package/dist/commands/mcp.d.ts +3 -0
  45. package/dist/commands/mcp.js +190 -0
  46. package/dist/commands/project.d.ts +3 -0
  47. package/dist/commands/project.js +133 -0
  48. package/dist/commands/proposals.d.ts +3 -0
  49. package/dist/commands/proposals.js +106 -0
  50. package/dist/commands/record.d.ts +3 -0
  51. package/dist/commands/record.js +174 -0
  52. package/dist/commands/report.d.ts +3 -0
  53. package/dist/commands/report.js +255 -0
  54. package/dist/commands/run.d.ts +41 -0
  55. package/dist/commands/run.js +369 -0
  56. package/dist/commands/schedule.d.ts +3 -0
  57. package/dist/commands/schedule.js +337 -0
  58. package/dist/commands/serve.d.ts +3 -0
  59. package/dist/commands/serve.js +38 -0
  60. package/dist/commands/steps.d.ts +8 -0
  61. package/dist/commands/steps.js +67 -0
  62. package/dist/commands/tokens.d.ts +3 -0
  63. package/dist/commands/tokens.js +110 -0
  64. package/dist/commands/trace.d.ts +10 -0
  65. package/dist/commands/trace.js +104 -0
  66. package/dist/commands/upgrade.d.ts +14 -0
  67. package/dist/commands/upgrade.js +115 -0
  68. package/dist/commands/users.d.ts +3 -0
  69. package/dist/commands/users.js +183 -0
  70. package/dist/commands/watch.d.ts +7 -0
  71. package/dist/commands/watch.js +116 -0
  72. package/dist/commands/workspace.d.ts +8 -0
  73. package/dist/commands/workspace.js +83 -0
  74. package/dist/context.d.ts +17 -0
  75. package/dist/context.js +22 -0
  76. package/dist/index.d.ts +3 -0
  77. package/dist/index.js +3 -0
  78. package/dist/program.d.ts +7 -0
  79. package/dist/program.js +90 -0
  80. package/dist/templates/project.d.ts +10 -0
  81. package/dist/templates/project.js +115 -0
  82. package/dist/ui.d.ts +16 -0
  83. package/dist/ui.js +67 -0
  84. package/package.json +61 -0
  85. package/templates/.claude/skills/sdods-record/SKILL.md +75 -0
  86. package/templates/.claude/skills/sdods-run/SKILL.md +54 -0
  87. package/templates/.env.example +62 -0
  88. package/templates/docker-compose.yml +40 -0
  89. package/templates/projects/demo-shop/.env.example +4 -0
  90. package/templates/projects/demo-shop/data/common/posts.yaml +11 -0
  91. package/templates/projects/demo-shop/data/common/products.json +8 -0
  92. package/templates/projects/demo-shop/data/common/users.csv +7 -0
  93. package/templates/projects/demo-shop/data/factories.ts +16 -0
  94. package/templates/projects/demo-shop/data/staging/users.csv +6 -0
  95. package/templates/projects/demo-shop/envs/local.yaml +17 -0
  96. package/templates/projects/demo-shop/envs/staging.yaml +23 -0
  97. package/templates/projects/demo-shop/features/__screenshots__/demo-shop--ui--chromium/darwin/inventory.png +0 -0
  98. package/templates/projects/demo-shop/features/api/posts.feature +90 -0
  99. package/templates/projects/demo-shop/features/auth/data-driven-login.feature +17 -0
  100. package/templates/projects/demo-shop/features/auth/login.feature +32 -0
  101. package/templates/projects/demo-shop/features/auth/user-pool.feature +20 -0
  102. package/templates/projects/demo-shop/features/cart/cart.feature +33 -0
  103. package/templates/projects/demo-shop/features/hybrid/seed-then-verify.feature +28 -0
  104. package/templates/projects/demo-shop/features/inventory/inventory.feature +38 -0
  105. package/templates/projects/demo-shop/features/inventory/network-intercept.feature +29 -0
  106. package/templates/projects/demo-shop/features/inventory/visual.feature +10 -0
  107. package/templates/projects/demo-shop/har/staging/cart.har +1 -0
  108. package/templates/projects/demo-shop/har/staging/cart.har.json +11 -0
  109. package/templates/projects/demo-shop/har/staging/login.har +1 -0
  110. package/templates/projects/demo-shop/har/staging/login.har.json +11 -0
  111. package/templates/projects/demo-shop/har/staging/posts.api.har +2061 -0
  112. package/templates/projects/demo-shop/har/staging/posts.har.json +8 -0
  113. package/templates/projects/demo-shop/har/staging/products.har +1 -0
  114. package/templates/projects/demo-shop/har/staging/products.har.json +11 -0
  115. package/templates/projects/demo-shop/har/staging/site.api.har +148 -0
  116. package/templates/projects/demo-shop/har/staging/site.har +1 -0
  117. package/templates/projects/demo-shop/har/staging/site.har.json +11 -0
  118. package/templates/projects/demo-shop/pages/CartPage.ts +20 -0
  119. package/templates/projects/demo-shop/pages/InventoryPage.ts +103 -0
  120. package/templates/projects/demo-shop/pages/LoginPage.ts +42 -0
  121. package/templates/projects/demo-shop/schemas/post.schema.json +13 -0
  122. package/templates/projects/demo-shop/sdods.project.yaml +185 -0
  123. package/templates/projects/demo-shop/steps/auth.ts +7 -0
  124. package/templates/projects/demo-shop/steps/fixtures.ts +39 -0
@@ -0,0 +1,7 @@
1
+ id,username,password,role,displayName
2
+ 1,standard_user,${DEMO_SHOP_PASSWORD:-secret_sauce},standard,Standard user
3
+ 2,problem_user,${DEMO_SHOP_PASSWORD:-secret_sauce},problem,Problem user
4
+ 3,performance_glitch_user,${DEMO_SHOP_PASSWORD:-secret_sauce},performance,Performance glitch user
5
+ 4,locked_out_user,${DEMO_SHOP_PASSWORD:-secret_sauce},locked,Locked out user
6
+ 5,error_user,${DEMO_SHOP_PASSWORD:-secret_sauce},standard,Error user
7
+ 6,visual_user,${DEMO_SHOP_PASSWORD:-secret_sauce},standard,Visual user
@@ -0,0 +1,16 @@
1
+ import { defineFactories } from '@sdods/core/data';
2
+
3
+ /** Faker factories, seeded per scenario fingerprint so retries regenerate identical data. */
4
+ export default defineFactories({
5
+ user: (f) => ({
6
+ email: f.internet.email(),
7
+ firstName: f.person.firstName(),
8
+ lastName: f.person.lastName(),
9
+ zip: f.location.zipCode('#####'),
10
+ }),
11
+ post: (f) => ({
12
+ title: `SDODS ${f.lorem.words(3)}`,
13
+ body: f.lorem.sentence(),
14
+ userId: f.number.int({ min: 1, max: 10 }),
15
+ }),
16
+ });
@@ -0,0 +1,6 @@
1
+ id,username,password,role,displayName
2
+ 1,standard_user,${DEMO_SHOP_PASSWORD:-secret_sauce},standard,Standard user (staging)
3
+ 2,problem_user,${DEMO_SHOP_PASSWORD:-secret_sauce},problem,Problem user (staging)
4
+ 3,performance_glitch_user,${DEMO_SHOP_PASSWORD:-secret_sauce},performance,Performance glitch user (staging)
5
+ 4,locked_out_user,${DEMO_SHOP_PASSWORD:-secret_sauce},locked,Locked out user (staging)
6
+ 5,visual_user,${DEMO_SHOP_PASSWORD:-secret_sauce},standard,Visual user (staging)
@@ -0,0 +1,17 @@
1
+ name: local
2
+ description: Same public sandboxes, smaller pool, headed-friendly timeouts. Override URLs via SDODS_UI_BASE_URL / SDODS_API_BASE_URL.
3
+ ui:
4
+ baseUrl: https://www.saucedemo.com
5
+ api:
6
+ baseUrl: https://jsonplaceholder.typicode.com
7
+ headers: { Accept: application/json }
8
+ auth: { type: none }
9
+ users:
10
+ poolSize: 2
11
+ vars:
12
+ defaultProduct: Sauce Labs Bike Light
13
+ standardPassword: '${DEMO_SHOP_PASSWORD:-secret_sauce}'
14
+ timeouts:
15
+ test: 90000
16
+ screenshots:
17
+ onlyOnFailure: false
@@ -0,0 +1,23 @@
1
+ name: staging
2
+ description: Public sandboxes (SauceDemo + JSONPlaceholder). No secrets except the demo password.
3
+ ui:
4
+ baseUrl: https://www.saucedemo.com
5
+ api:
6
+ baseUrl: https://jsonplaceholder.typicode.com
7
+ headers: { Accept: application/json }
8
+ auth: { type: none }
9
+ # For a real API:
10
+ # auth: { type: bearer, token: '${API_TOKEN}' }
11
+ # openapi: ./openapi/staging.json
12
+ users:
13
+ poolSize: 4
14
+ vars:
15
+ defaultProduct: Sauce Labs Backpack
16
+ standardPassword: '${DEMO_SHOP_PASSWORD:-secret_sauce}'
17
+ use:
18
+ locale: en-US
19
+ timezoneId: America/New_York
20
+ screenshots:
21
+ onlyOnFailure: false
22
+ perf:
23
+ budgets: { pageLoadMs: 5000 }
@@ -0,0 +1,90 @@
1
+ @api @posts @har:posts
2
+ Feature: Posts API
3
+ JSONPlaceholder CRUD through the shared API step library: status codes, JSON-path assertions,
4
+ schema validation, variable chaining and response-time budgets.
5
+
6
+ @smoke
7
+ Scenario: List posts
8
+ When I send a GET request to "/posts"
9
+ Then the response status should be 200
10
+ And the response body should be an array
11
+ And the response JSON path "$.length" should equal "100"
12
+ And the response JSON path "[0].id" should equal "1"
13
+ And the response time should be under 3000 ms
14
+
15
+ @smoke @contract
16
+ Scenario: Get a single post and validate its shape
17
+ When I send a GET request to "/posts/1"
18
+ Then the response status should be 200
19
+ And the response JSON path "id" should equal "1"
20
+ And the response JSON path "userId" should equal "1"
21
+ And the response JSON path "title" should match ".+"
22
+ And the response should match the JSON schema "post"
23
+ And the response header "content-type" should contain "application/json"
24
+
25
+ @regression
26
+ Scenario: Nested resource - comments of a post
27
+ When I send a GET request to "/posts/1/comments"
28
+ Then the response status should be 200
29
+ And the response JSON path "$.length" should equal "5"
30
+ And the response JSON path "[0].postId" should equal "1"
31
+ And the response JSON path "[0].email" should match "@"
32
+
33
+ @regression
34
+ Scenario: Query parameters filter posts by user
35
+ Given I set the query parameter "userId" to "2"
36
+ When I send a GET request to "/posts"
37
+ Then the response status should be 200
38
+ And the response JSON path "$.length" should equal "10"
39
+ And the response JSON path "[0].userId" should equal "2"
40
+
41
+ @regression @contract
42
+ Scenario: Create a post and chain its id
43
+ Given I set the variable "title" to "SDODS created this"
44
+ When I send a POST request to "/posts" with body:
45
+ """json
46
+ { "title": "{{title}}", "body": "hello from SDODS", "userId": 1 }
47
+ """
48
+ Then the response status should be 201
49
+ And the response JSON path "title" should equal "{{title}}"
50
+ And the response should match the JSON schema "post"
51
+ When I save the response JSON path "id" as "postId"
52
+ And I send a GET request to "/posts/{{postId}}"
53
+ Then the response status should be one of "200, 404"
54
+
55
+ @regression
56
+ Scenario: Update a post
57
+ When I send a PUT request to "/posts/1" with body:
58
+ """json
59
+ { "id": 1, "title": "updated by SDODS", "body": "changed", "userId": 1 }
60
+ """
61
+ Then the response status should be 200
62
+ And the response JSON path "title" should equal "updated by SDODS"
63
+ When I send a PATCH request to "/posts/1" with body:
64
+ """json
65
+ { "title": "patched by SDODS" }
66
+ """
67
+ Then the response status should be 200
68
+ And the response JSON path "title" should equal "patched by SDODS"
69
+
70
+ @regression
71
+ Scenario: Delete a post
72
+ When I send a DELETE request to "/posts/1"
73
+ Then the response status should be 200
74
+
75
+ @regression
76
+ Scenario: Unknown post returns 404
77
+ When I send a GET request to "/posts/999999"
78
+ Then the response status should be 404
79
+
80
+ @regression
81
+ Scenario: Data-driven request from the posts dataset
82
+ Given I load dataset "posts" row 0
83
+ When I send a GET request to "/posts/{{id}}"
84
+ Then the response status should be 200
85
+ And the response JSON path "userId" should equal "{{userId}}"
86
+
87
+ @regression
88
+ Scenario: Polling until a condition holds
89
+ When I poll GET "/posts/2" until JSON path "id" equals "2" within 10 seconds
90
+ Then the response status should be 200
@@ -0,0 +1,17 @@
1
+ @ui @auth @data-driven
2
+ Feature: Data-driven login
3
+ Credentials come from the users dataset (data/<env>/users.csv with data/common fallback).
4
+
5
+ @regression
6
+ Scenario: Login with the first user of the dataset
7
+ Given I load dataset "users" row 0
8
+ And I am on the login page
9
+ When I login with "{{username}}" and "{{password}}"
10
+ Then I should be on the inventory page
11
+
12
+ @regression
13
+ Scenario: Login with the user whose role is problem
14
+ Given I load dataset "users" where "role" is "problem"
15
+ And I am on the login page
16
+ When I login with "{{username}}" and "{{password}}"
17
+ Then I should be on the inventory page
@@ -0,0 +1,32 @@
1
+ @ui @auth
2
+ Feature: Login
3
+ As a shopper I want to sign in so that I can see the inventory.
4
+
5
+ Background:
6
+ Given I am on the login page
7
+
8
+ @smoke @har:login
9
+ Scenario: Successful login shows the products
10
+ When I login with "standard_user" and "{{standardPassword}}"
11
+ Then I should be on the inventory page
12
+ And the inventory title should be "Products"
13
+
14
+ @regression
15
+ Scenario Outline: Failed login shows a clear error
16
+ When I login with "<username>" and "<password>"
17
+ Then I should see the login error "<error>"
18
+
19
+ # title-format: <username> → <error>
20
+ Examples:
21
+ | username | password | error |
22
+ | locked_out_user | secret_sauce | Epic sadface: Sorry, this user has been locked out. |
23
+ | standard_user | wrong | Epic sadface: Username and password do not match any user in this service |
24
+ | | secret_sauce | Epic sadface: Username is required |
25
+ | standard_user | | Epic sadface: Password is required |
26
+
27
+ @regression
28
+ Scenario: Generic UI steps work against the login form
29
+ When I fill the element with test id "username" with "standard_user"
30
+ And I fill the element with test id "password" with "{{standardPassword}}"
31
+ And I click the element with test id "login-button"
32
+ Then the page URL should contain "/inventory.html"
@@ -0,0 +1,20 @@
1
+ @ui @auth @pool
2
+ Feature: User pool
3
+ Accounts are leased per worker from the pool dataset; login state is cached as storageState.
4
+
5
+ @sanity @user:standard
6
+ Scenario: A leased standard user starts logged in through cached storage state
7
+ Given I navigate to the "inventory" page
8
+ Then I should be on the inventory page
9
+ And the inventory title should be "Products"
10
+
11
+ @sanity
12
+ Scenario: Leasing a user by role inside the scenario
13
+ Given I use a leased user with role "problem"
14
+ And I navigate to the "inventory" page
15
+ Then I should be on the inventory page
16
+
17
+ @sanity @user:performance
18
+ Scenario: A second role leases a different account
19
+ Given I navigate to the "inventory" page
20
+ Then I should be on the inventory page
@@ -0,0 +1,33 @@
1
+ @ui @cart
2
+ Feature: Cart
3
+ Adding products and reviewing them in the cart.
4
+
5
+ Background:
6
+ Given I am on the login page
7
+ And I login with "standard_user" and "{{standardPassword}}"
8
+
9
+ @smoke @har:cart
10
+ Scenario: A product added on the inventory page appears in the cart
11
+ When I add "Sauce Labs Backpack" to the cart
12
+ And I open the cart
13
+ Then the cart should list "Sauce Labs Backpack"
14
+
15
+ @regression
16
+ Scenario: Continue shopping returns to the inventory
17
+ When I open the cart
18
+ And I click the "Continue Shopping" button
19
+ Then I should be on the inventory page
20
+
21
+ @regression
22
+ Scenario: Checkout form is reachable from the cart
23
+ When I add "Sauce Labs Bolt T-Shirt" to the cart
24
+ And I open the cart
25
+ And I click the "Checkout" button
26
+ Then the page URL should contain "/checkout-step-one.html"
27
+ When I fill the form:
28
+ | field | value |
29
+ | First Name | Auto |
30
+ | Last Name | Max |
31
+ | Zip/Postal Code | 12345 |
32
+ And I click the "Continue" button
33
+ Then the page URL should contain "/checkout-step-two.html"
@@ -0,0 +1,28 @@
1
+ @hybrid @hybrid-demo
2
+ Feature: Seed through the API, verify in the browser
3
+ One scenario, both layers: the API client and the page share the same fixtures and variables.
4
+
5
+ @regression
6
+ Scenario: A post created through the API is rendered by the UI
7
+ Given I use a leased user with role "standard"
8
+ When I seed via POST "/posts" with body:
9
+ """json
10
+ { "title": "SDODS hybrid {{username}}", "body": "seeded", "userId": 1 }
11
+ """
12
+ Then the response status should be 201
13
+ When I save the response JSON path "title" as "title"
14
+ And I mock "**/inventory.html" with HTML:
15
+ """
16
+ <main><h1>{{title}}</h1><p>rendered from the seeded API response</p></main>
17
+ """
18
+ And I navigate to the "inventory" page
19
+ Then the UI should show the text from JSON path "title"
20
+
21
+ @regression
22
+ Scenario: API state and UI state in one flow
23
+ When I send a GET request to "/users/1"
24
+ Then the response status should be 200
25
+ When I save the response JSON path "username" as "apiUser"
26
+ Given I am on the login page
27
+ When I fill the element with test id "username" with "{{apiUser}}"
28
+ Then the element with test id "username" should be visible
@@ -0,0 +1,38 @@
1
+ @ui @inventory
2
+ Feature: Inventory
3
+ Products page: listing, cart badge and sorting.
4
+
5
+ Background:
6
+ Given I am on the login page
7
+ And I login with "standard_user" and "{{standardPassword}}"
8
+
9
+ @smoke @har:products
10
+ Scenario: Products are displayed
11
+ Then the inventory title should be "Products"
12
+ And there should be 6 products listed
13
+
14
+ @regression
15
+ Scenario: Add a product to the cart
16
+ When I add "{{defaultProduct}}" to the cart
17
+ Then the cart badge should show 1 item
18
+
19
+ @regression
20
+ Scenario: Remove a product from the cart
21
+ When I add "Sauce Labs Bike Light" to the cart
22
+ And I remove "Sauce Labs Bike Light" from the cart
23
+ Then the cart badge should be hidden
24
+
25
+ @regression
26
+ Scenario: Sort products by price ascending
27
+ When I sort products by "Price (low to high)"
28
+ Then the product prices should be sorted ascending
29
+
30
+ @regression
31
+ Scenario: Sort products by name descending
32
+ When I sort products by "Name (Z to A)"
33
+ Then the product names should be sorted descending
34
+
35
+ @regression @skip:webkit
36
+ Scenario: Open the menu (browser-specific exclusion example)
37
+ When I click the "Open Menu" button
38
+ Then the "Logout" link should be visible
@@ -0,0 +1,29 @@
1
+ @ui @inventory @mock
2
+ Feature: Network interception
3
+ Route mocking with the shared steps, and a direct API check from a UI scenario.
4
+
5
+ @regression
6
+ Scenario: A mocked page is rendered instead of the real one
7
+ Given I mock "**/mocked/**" with HTML:
8
+ """
9
+ <h1 data-test="banner">Mocked by SDODS</h1>
10
+ """
11
+ When I navigate to the "/mocked/banner.html" page
12
+ Then I should see the text "Mocked by SDODS"
13
+ And the element with test id "banner" should be visible
14
+
15
+ @regression
16
+ Scenario: A mocked JSON API response is observed through the API client
17
+ Given I am on the login page
18
+ And I mock "**/api/health" with JSON:
19
+ """json
20
+ { "status": "mocked" }
21
+ """
22
+ When I send a GET request to "https://www.saucedemo.com/"
23
+ Then the response status should be 200
24
+
25
+ @smoke @har:site
26
+ Scenario: The site answers over HTTP
27
+ When I send a GET request to "https://www.saucedemo.com/"
28
+ Then the response status should be 200
29
+ And the response time should be under 5000 ms
@@ -0,0 +1,10 @@
1
+ @ui @inventory @visual
2
+ Feature: Inventory visual baseline
3
+ Pixel comparison against a per-browser baseline (run once with --update-snapshots).
4
+
5
+ @regression
6
+ Scenario: Inventory page matches its baseline
7
+ Given I am on the login page
8
+ And I login with "standard_user" and "{{standardPassword}}"
9
+ Then I should be on the inventory page
10
+ And the page should match the visual baseline "inventory"