@sdods/cli 0.8.0 → 0.10.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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/commands/baselines.d.ts +3 -0
- package/dist/commands/baselines.js +111 -0
- package/dist/commands/browsers.js +1 -1
- package/dist/commands/doctor.js +44 -2
- package/dist/commands/init.js +4 -4
- package/dist/commands/report.js +161 -14
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +107 -6
- package/dist/commands/schedule.js +1 -1
- package/dist/commands/trace.js +2 -2
- package/dist/gates.d.ts +11 -0
- package/dist/gates.js +29 -0
- package/dist/program.js +2 -0
- package/package.json +10 -10
- package/skills/sdods-start-ui/SKILL.md +5 -4
- package/templates/projects/demo-shop/features/__screenshots__/demo-shop--ui--chromium/linux/inventory.png +0 -0
- package/templates/projects/demo-shop/features/__screenshots__/demo-shop--ui--firefox/linux/inventory.png +0 -0
- package/templates/projects/demo-shop/features/__screenshots__/demo-shop--ui--webkit/linux/inventory.png +0 -0
- package/templates/projects/demo-shop/features/auth/user-pool.feature +1 -1
- package/templates/projects/demo-shop/features/cart/performance.feature +14 -0
- package/templates/projects/demo-shop/features/inventory/accessibility.feature +12 -0
- package/templates/projects/demo-shop/features/inventory/visual.feature +2 -1
- package/templates/projects/demo-shop/sdods.project.yaml +3 -0
|
@@ -279,7 +279,7 @@ export function register(program) {
|
|
|
279
279
|
if (opts.target === 'github') {
|
|
280
280
|
const file = join(ctx.rootDir, '.github', 'workflows', `scheduled-${e.project}-${e.name}.yml`);
|
|
281
281
|
mkdirSync(join(ctx.rootDir, '.github', 'workflows'), { recursive: true });
|
|
282
|
-
writeFileSync(file, `name: scheduled ${e.project} ${e.name}\non:\n schedule:\n - cron: '${e.cron}'\n workflow_dispatch: {}\njobs:\n run:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@
|
|
282
|
+
writeFileSync(file, `name: scheduled ${e.project} ${e.name}\non:\n schedule:\n - cron: '${e.cron}'\n workflow_dispatch: {}\njobs:\n run:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v7\n - uses: oven-sh/setup-bun@v2\n - uses: actions/setup-node@v7\n with: { node-version: 22 }\n - run: bun install\n - run: bun run sdods browsers install --with-deps\n - run: bun run sdods run ${e.args.join(' ')} --trigger schedule --run-id sched-${e.name}-\${{ github.run_id }}\n env:\n SDODS_SERVER_URL: \${{ secrets.SDODS_SERVER_URL }}\n SDODS_TOKEN: \${{ secrets.SDODS_TOKEN }}\n - uses: actions/upload-artifact@v7\n if: always()\n with: { name: sdods-${e.project}-${e.name}, path: .sdods/runs }\n`);
|
|
283
283
|
written.push(file);
|
|
284
284
|
}
|
|
285
285
|
else if (opts.target === 'launchd') {
|
package/dist/commands/trace.js
CHANGED
|
@@ -56,8 +56,8 @@ export function register(program) {
|
|
|
56
56
|
.option('--list', 'list trace files instead of opening them')
|
|
57
57
|
.action(async (zip, opts, cmd) => {
|
|
58
58
|
const ctx = createContext(cmd);
|
|
59
|
-
let zips
|
|
60
|
-
let source
|
|
59
|
+
let zips;
|
|
60
|
+
let source;
|
|
61
61
|
if (zip) {
|
|
62
62
|
const p = resolvePath(ctx.opts.cwd ?? process.cwd(), zip);
|
|
63
63
|
if (!existsSync(p))
|
package/dist/gates.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { GateResult } from '@sdods/contracts';
|
|
2
|
+
import { SdodsError } from '@sdods/core';
|
|
3
|
+
/**
|
|
4
|
+
* How a process gate verdict reads on the terminal, and the error a breach fails with. Shared by
|
|
5
|
+
* `sdods run --process` (an unsharded run) and `sdods report merge --process` (the shards of one),
|
|
6
|
+
* so a gate looks the same wherever it was judged.
|
|
7
|
+
*/
|
|
8
|
+
export declare function printGates(gates: GateResult): void;
|
|
9
|
+
/** The `GATE_FAILED` error for a verdict that did not pass. Exit 1: a gate not met is a failure. */
|
|
10
|
+
export declare function gateFailedError(gates: GateResult, verdictFile: string): SdodsError;
|
|
11
|
+
//# sourceMappingURL=gates.d.ts.map
|
package/dist/gates.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { SdodsError } from '@sdods/core';
|
|
3
|
+
import { out, table } from './ui.js';
|
|
4
|
+
/**
|
|
5
|
+
* How a process gate verdict reads on the terminal, and the error a breach fails with. Shared by
|
|
6
|
+
* `sdods run --process` (an unsharded run) and `sdods report merge --process` (the shards of one),
|
|
7
|
+
* so a gate looks the same wherever it was judged.
|
|
8
|
+
*/
|
|
9
|
+
export function printGates(gates) {
|
|
10
|
+
out('');
|
|
11
|
+
out(pc.bold(`gates · process ${gates.process}`));
|
|
12
|
+
table(gates.rows.map((r) => ({
|
|
13
|
+
gate: r.gate,
|
|
14
|
+
threshold: r.threshold,
|
|
15
|
+
actual: r.actual,
|
|
16
|
+
result: r.passed ? 'pass' : 'FAIL',
|
|
17
|
+
detail: r.detail ?? '',
|
|
18
|
+
})));
|
|
19
|
+
}
|
|
20
|
+
/** The `GATE_FAILED` error for a verdict that did not pass. Exit 1: a gate not met is a failure. */
|
|
21
|
+
export function gateFailedError(gates, verdictFile) {
|
|
22
|
+
const failed = gates.rows.filter((r) => !r.passed);
|
|
23
|
+
return new SdodsError('GATE_FAILED', `Process "${gates.process}" did not meet ${failed.length} gate(s): ${failed.map((r) => `${r.gate} (${r.actual}, needs ${r.threshold})`).join('; ')}.`, {
|
|
24
|
+
hint: `The verdict is in ${verdictFile}. Gates are set under processes[].gates in sdods.project.yaml.`,
|
|
25
|
+
docsPath: '/docs/guides/processes-and-testing-types',
|
|
26
|
+
exitCode: 1,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=gates.js.map
|
package/dist/program.js
CHANGED
|
@@ -24,6 +24,7 @@ import { register as registerCoverage } from './commands/coverage.js';
|
|
|
24
24
|
import { register as registerBrowsers } from './commands/browsers.js';
|
|
25
25
|
import { register as registerWatch } from './commands/watch.js';
|
|
26
26
|
import { register as registerTrace } from './commands/trace.js';
|
|
27
|
+
import { register as registerBaselines } from './commands/baselines.js';
|
|
27
28
|
import { register as registerAgent } from './commands/agent.js';
|
|
28
29
|
import { register as registerProposals } from './commands/proposals.js';
|
|
29
30
|
import { register as registerMcp } from './commands/mcp.js';
|
|
@@ -80,6 +81,7 @@ export function buildProgram() {
|
|
|
80
81
|
registerBrowsers(program);
|
|
81
82
|
registerWatch(program);
|
|
82
83
|
registerTrace(program);
|
|
84
|
+
registerBaselines(program);
|
|
83
85
|
registerAgent(program);
|
|
84
86
|
registerProposals(program);
|
|
85
87
|
registerMcp(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdods/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "SDODS command line: run, record, analyze, agents, MCP server, scheduler and the web server.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "SDODS <admin@sdods.com>",
|
|
@@ -24,22 +24,22 @@
|
|
|
24
24
|
"node": ">=22"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@sdods/contracts": "0.
|
|
28
|
-
"@sdods/core": "0.
|
|
27
|
+
"@sdods/contracts": "0.10.0",
|
|
28
|
+
"@sdods/core": "0.10.0",
|
|
29
29
|
"commander": "^15.0.0",
|
|
30
30
|
"execa": "^10.0.1",
|
|
31
31
|
"picocolors": "^1.1.1",
|
|
32
32
|
"semver": "^7.8.5",
|
|
33
|
-
"yaml": "^2.9.
|
|
34
|
-
"zod": "^4.5
|
|
33
|
+
"yaml": "^2.9.1",
|
|
34
|
+
"zod": "^4.6.5",
|
|
35
35
|
"@playwright/test": "^1.63.0",
|
|
36
36
|
"playwright-core": "^1.63.0",
|
|
37
|
-
"@sdods/integrations": "0.
|
|
38
|
-
"@sdods/mcp": "0.
|
|
39
|
-
"@sdods/agents": "0.
|
|
40
|
-
"@sdods/db": "0.
|
|
37
|
+
"@sdods/integrations": "0.10.0",
|
|
38
|
+
"@sdods/mcp": "0.10.0",
|
|
39
|
+
"@sdods/agents": "0.10.0",
|
|
40
|
+
"@sdods/db": "0.10.0",
|
|
41
41
|
"csv-parse": "^7.0.2",
|
|
42
|
-
"@sdods/server": "0.
|
|
42
|
+
"@sdods/server": "0.10.0"
|
|
43
43
|
},
|
|
44
44
|
"homepage": "https://sdods.com",
|
|
45
45
|
"bugs": {
|
|
@@ -161,11 +161,12 @@ Defaults: **`http://127.0.0.1:4444`** for the CLI, **`http://localhost:8080`** f
|
|
|
161
161
|
image binds `0.0.0.0:8080`). `--open` launches the browser (`open` / `start` / `xdg-open` by
|
|
162
162
|
platform). `--host 0.0.0.0` exposes it on the LAN — only do that deliberately.
|
|
163
163
|
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
The image is published for `linux/amd64` and `linux/arm64`, so the same command runs natively on
|
|
165
|
+
Apple silicon and arm64 servers. Tags up to `0.9.0` carry amd64 only, and Docker refuses that pull
|
|
166
|
+
on arm64 rather than emulating; for those, add the platform flag:
|
|
166
167
|
|
|
167
168
|
```bash
|
|
168
|
-
docker run --rm --platform linux/amd64 -p 8080:8080 -v sdods-data:/data ghcr.io/siri1410/sdods-server
|
|
169
|
+
docker run --rm --platform linux/amd64 -p 8080:8080 -v sdods-data:/data ghcr.io/siri1410/sdods-server:0.9.0
|
|
169
170
|
```
|
|
170
171
|
|
|
171
172
|
If the run must come from a source checkout, `bun run web:build` is not optional — see
|
|
@@ -278,7 +279,7 @@ the API running **without** the UI bundle — that is the source-checkout case,
|
|
|
278
279
|
| Fresh install, but `/login` says "ask an admin" | the login page does not redirect to `/setup` | open the `/setup?token=…` URL from the `serve` output, or `sdods users create --admin` |
|
|
279
280
|
| Global CLI errors on a repo project (`CONFIG_INVALID`, unknown browser) | version skew between the global CLI and the checkout | use `bun run sdods` inside a checkout; upgrade the global one otherwise |
|
|
280
281
|
| `sdods: command not found` after installing | the bin dir is not on PATH — `~/.local/bin`, or `%LOCALAPPDATA%\SDODS\bin` on Windows | add it, or re-run the installer with `--modify-path` / `-ModifyPath` |
|
|
281
|
-
| Docker: `no matching manifest for linux/arm64/v8` | amd64-only
|
|
282
|
+
| Docker: `no matching manifest for linux/arm64/v8` | an amd64-only tag (`0.9.0` or earlier) on Apple silicon or arm64 | pull a newer tag, or add `--platform linux/amd64` |
|
|
282
283
|
| UI loads, runs never start | browser engines missing | `sdods browsers install -b chromium` (the positional `sdods browsers install chromium` also works on current versions, and failed with `too many arguments` before). Bare `sdods browsers install` installs all engines; on Linux `--with-deps` needs root |
|
|
283
284
|
| Node too old | SDODS needs Node 22+ | `nvm use 22` / `fnm use 22`. On macOS/Linux the installer also takes `--install-node`; **`install.ps1` has no such flag** — it exits and tells you to run `winget install OpenJS.NodeJS.LTS`, `scoop install nodejs-lts` or `nvm install 22` |
|
|
284
285
|
| Anything else | — | `sdods doctor` first; it reports what it found, not what it expected |
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
@ui @auth @pool
|
|
2
2
|
Feature: User pool
|
|
3
|
-
Accounts are leased per
|
|
3
|
+
Accounts are leased per scenario from the pool dataset; login state is cached as storageState.
|
|
4
4
|
|
|
5
5
|
@sanity @user:standard
|
|
6
6
|
Scenario: A leased standard user starts logged in through cached storage state
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
@ui @cart @perf
|
|
2
|
+
Feature: Cart performance
|
|
3
|
+
The @perf tag records the vitals of every document the scenario loads and judges them against
|
|
4
|
+
perf.budgets (pageLoadMs and lcpMs from the project, pageLoadMs overridden in envs/staging.yaml).
|
|
5
|
+
|
|
6
|
+
# WebKit does not implement largest-contentful-paint, and a vital a budget applies to that never
|
|
7
|
+
# fired fails the scenario rather than passing it, so this runs on Chromium and Firefox only.
|
|
8
|
+
@regression @skip:webkit
|
|
9
|
+
Scenario: The login and cart pages load within their budgets
|
|
10
|
+
Given I am on the login page
|
|
11
|
+
When I login with "standard_user" and "{{standardPassword}}"
|
|
12
|
+
And I add "{{defaultProduct}}" to the cart
|
|
13
|
+
And I navigate to the "cart" page
|
|
14
|
+
Then the cart should list "{{defaultProduct}}"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
@ui @inventory @a11y
|
|
2
|
+
Feature: Inventory accessibility
|
|
3
|
+
The @a11y tag audits the page each scenario ends on with axe (WCAG 2.x A and AA) and fails on
|
|
4
|
+
anything at or above a11y.failOn (serious by default). SauceDemo's login, inventory and cart
|
|
5
|
+
pages have no WCAG A/AA violation axe can detect, so nothing is scoped out with include/exclude.
|
|
6
|
+
|
|
7
|
+
@regression
|
|
8
|
+
Scenario: The inventory page has no serious accessibility violations
|
|
9
|
+
Given I am on the login page
|
|
10
|
+
When I login with "standard_user" and "{{standardPassword}}"
|
|
11
|
+
Then I should be on the inventory page
|
|
12
|
+
And there should be 6 products listed
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
@ui @inventory @visual
|
|
2
2
|
Feature: Inventory visual baseline
|
|
3
|
-
Pixel comparison against a per-browser baseline
|
|
3
|
+
Pixel comparison against a per-browser, per-platform baseline. Review a failure with
|
|
4
|
+
`sdods baselines diff` and accept it with `sdods baselines accept inventory --run <id>`.
|
|
4
5
|
|
|
5
6
|
@regression
|
|
6
7
|
Scenario: Inventory page matches its baseline
|
|
@@ -101,6 +101,9 @@ data:
|
|
|
101
101
|
roleColumn: role
|
|
102
102
|
leaseStore: file
|
|
103
103
|
leaseTtlMs: 600000
|
|
104
|
+
# One account per role after poolSize, and `release:check:live` runs three browsers in one
|
|
105
|
+
# invocation: release each account when its scenario ends so the browsers queue for it (#153).
|
|
106
|
+
leaseScope: scenario
|
|
104
107
|
|
|
105
108
|
auth:
|
|
106
109
|
strategy: form
|