@cspeach/cli 1.1.18 → 1.1.20
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 +3 -4
- package/dist/agent/anthropic-provider.js +30 -10
- package/dist/agent/cache-keepalive.js +162 -0
- package/dist/agent/cold-prune.js +116 -0
- package/dist/agent/loop.js +804 -159
- package/dist/agent/provider-shape.js +263 -0
- package/dist/agent/providers/ai-hub-provider.js +17 -2
- package/dist/agent/providers/byok-provider.js +33 -3
- package/dist/agent/providers/local-provider.js +12 -2
- package/dist/agent/repair-partial.js +125 -7
- package/dist/agent/summarise-via-provider.js +6 -1
- package/dist/agent/system-prompt.js +38 -0
- package/dist/agent/tool-dispatch.js +66 -21
- package/dist/agent/tool-loading-pin.js +100 -0
- package/dist/agent/turn-error-ux.js +77 -26
- package/dist/agent/turn-stream.js +25 -3
- package/dist/approvals/adt-type.js +27 -0
- package/dist/approvals/advisory-prompt.js +48 -0
- package/dist/approvals/advisory-render.js +25 -11
- package/dist/approvals/approval-prompt.js +74 -24
- package/dist/approvals/jwt.js +2 -1
- package/dist/approvals/render.js +64 -36
- package/dist/approvals/risk-floor.js +53 -2
- package/dist/auth/api-key.js +30 -16
- package/dist/auth/keychain.js +0 -0
- package/dist/auth/me.js +25 -7
- package/dist/cli.js +36 -6
- package/dist/commands/auto-compact.js +33 -16
- package/dist/commands/compact.js +49 -11
- package/dist/commands/config-set.js +16 -3
- package/dist/commands/config-show.js +18 -5
- package/dist/commands/cost.js +54 -35
- package/dist/commands/help.js +88 -50
- package/dist/commands/login.js +28 -19
- package/dist/commands/logout.js +7 -3
- package/dist/commands/plan-audit.js +1 -0
- package/dist/commands/plan-chain.js +71 -77
- package/dist/commands/plan-continue.js +1 -0
- package/dist/commands/plan-resume.js +20 -12
- package/dist/commands/whoami.js +23 -9
- package/dist/config/loader.js +70 -5
- package/dist/cost/cost-log.js +62 -2
- package/dist/cost/pricing.js +11 -6
- package/dist/doctor/checks/forge-rules.js +13 -3
- package/dist/doctor/checks/keychain.js +6 -4
- package/dist/doctor/run.js +101 -20
- package/dist/index.js +5 -2
- package/dist/lib/graceful-exit.js +56 -0
- package/dist/lib/piped-prompt.js +65 -0
- package/dist/lib/spill-labels.js +13 -0
- package/dist/lock-contention.js +2 -2
- package/dist/models/resolve.js +93 -2
- package/dist/models/server-config.js +158 -3
- package/dist/one-shot.js +150 -45
- package/dist/projects/answer-blockers.js +6 -1
- package/dist/projects/handover-md.js +15 -14
- package/dist/projects/image-attachments.js +15 -2
- package/dist/projects/plan-run.js +97 -54
- package/dist/projects/promote-command.js +11 -2
- package/dist/projects/save-command.js +3 -2
- package/dist/projects/status.js +2 -1
- package/dist/projects/yes-no.js +12 -0
- package/dist/renderer/abap-inline.js +18 -14
- package/dist/renderer/answer-trim.js +69 -0
- package/dist/renderer/banners.js +8 -8
- package/dist/renderer/brand-settled.js +19 -0
- package/dist/renderer/change-card.js +156 -0
- package/dist/renderer/checklist-format.js +130 -0
- package/dist/renderer/color-mode.js +45 -0
- package/dist/renderer/error-detail.js +104 -0
- package/dist/renderer/fence-state.js +46 -0
- package/dist/renderer/footer-line.js +100 -0
- package/dist/renderer/glyphs.js +25 -0
- package/dist/renderer/goodbye.js +38 -0
- package/dist/renderer/legacy-palette.js +41 -0
- package/dist/renderer/look.js +30 -0
- package/dist/renderer/markdown.js +299 -92
- package/dist/renderer/notice-log.js +72 -0
- package/dist/renderer/notice-shape.js +48 -0
- package/dist/renderer/notices.js +40 -8
- package/dist/renderer/panel-rows.js +16 -0
- package/dist/renderer/pipeline.js +66 -0
- package/dist/renderer/progress-chatter.js +23 -17
- package/dist/renderer/rendering-mode.js +34 -0
- package/dist/renderer/routing-line.js +14 -0
- package/dist/renderer/sanitize.js +12 -0
- package/dist/renderer/severity.js +2 -7
- package/dist/renderer/startup-lines.js +154 -0
- package/dist/renderer/status-footer.js +52 -34
- package/dist/renderer/steering-echo.js +41 -0
- package/dist/renderer/syntax.js +31 -12
- package/dist/renderer/tables.js +5 -1
- package/dist/renderer/theme.js +44 -0
- package/dist/renderer/thinking-heartbeat.js +33 -2
- package/dist/renderer/todo-block.js +10 -49
- package/dist/renderer/tool-labels.js +269 -0
- package/dist/renderer/tool-widget.js +245 -51
- package/dist/renderer/trace.js +42 -0
- package/dist/renderer/transcript-flow.js +132 -0
- package/dist/renderer/tty.js +34 -1
- package/dist/renderer/ui-width.js +55 -0
- package/dist/renderer/verify-chain.js +4 -2
- package/dist/renderer/widget-fallback.js +58 -65
- package/dist/repl/bracketed-paste.js +7 -1
- package/dist/repl/builtin-commands.js +19 -8
- package/dist/repl/credential-handover-gate.js +28 -0
- package/dist/repl/current-transport.js +13 -0
- package/dist/repl/early-line-buffer.js +5 -2
- package/dist/repl/file-picker.js +54 -12
- package/dist/repl/ink-stdin-guard.js +66 -0
- package/dist/repl/inquirer-guard.js +59 -16
- package/dist/repl/inquirer-theme.js +27 -33
- package/dist/repl/paste-marker.js +19 -0
- package/dist/repl/plan-turn-end.js +20 -0
- package/dist/repl/post-turn-status.js +43 -11
- package/dist/repl/reroute-turn.js +21 -0
- package/dist/repl/reset-tty-stdin.js +44 -0
- package/dist/repl/restore-guard.js +22 -0
- package/dist/repl/resume-standalone.js +63 -0
- package/dist/repl/rule8-detector.js +11 -3
- package/dist/repl/safety-confirm.js +161 -93
- package/dist/repl/session-spend-line.js +8 -4
- package/dist/repl/themed-prompts.js +19 -0
- package/dist/repl/ui-look-command.js +75 -0
- package/dist/repl/update-method-preview-hook.js +48 -5
- package/dist/repl.js +863 -339
- package/dist/rewind/cli.js +3 -2
- package/dist/rewind/format.js +14 -9
- package/dist/rewind/restore.js +11 -0
- package/dist/router/classifier.js +47 -4
- package/dist/router/intent-extractor.js +24 -8
- package/dist/router/piped-routing.js +23 -0
- package/dist/router/resume-routing.js +16 -0
- package/dist/router/routing-failure.js +57 -0
- package/dist/sap/first-run-choice.js +1 -1
- package/dist/sap/onboarding.js +3 -2
- package/dist/sap/standalone-onboarding.js +1 -1
- package/dist/sap/system-info.js +4 -2
- package/dist/sap/unreachable.js +28 -0
- package/dist/sap-errors/clean-error-text.js +182 -0
- package/dist/session/interrupt-reason.js +39 -0
- package/dist/session/recap.js +36 -24
- package/dist/session/repin-model.js +18 -0
- package/dist/session/resume.js +104 -36
- package/dist/session/store.js +84 -28
- package/dist/session/user-prompt.js +21 -0
- package/dist/skill-catalog.js +7 -0
- package/dist/skills/bundled-skills.js +76 -69
- package/dist/skills/preamble.js +75 -0
- package/dist/skills/source-manifest.js +11 -1
- package/dist/standards/standards-init.js +1 -1
- package/dist/test-helpers/answer-any.js +46 -0
- package/dist/tools/approval.js +27 -11
- package/dist/tools/ask-question.js +47 -5
- package/dist/tools/filesystem/file-read.js +11 -1
- package/dist/tools/filesystem/file-write.js +19 -2
- package/dist/tools/fiori/fe-extend.js +16 -1
- package/dist/tools/fiori/fe-scaffold.js +105 -3
- package/dist/tools/fiori/html-escapes.js +28 -0
- package/dist/tools/fiori/metadata/audit.js +605 -0
- package/dist/tools/fiori/metadata/references.js +104 -0
- package/dist/tools/fiori/metadata/types.js +8 -0
- package/dist/tools/fiori/preview/app-guard.js +215 -0
- package/dist/tools/fiori/preview/env.js +193 -0
- package/dist/tools/fiori/preview/readiness.js +127 -0
- package/dist/tools/fiori/preview/registry.js +412 -0
- package/dist/tools/fiori/preview/start.js +469 -0
- package/dist/tools/fiori/samples/loader.js +20 -5
- package/dist/tools/fiori/scaffold.js +18 -6
- package/dist/tools/fiori/smoke/app-driver-page.js +372 -0
- package/dist/tools/fiori/smoke/app-driver.js +100 -0
- package/dist/tools/fiori/smoke/assertions.js +161 -24
- package/dist/tools/fiori/smoke/audit-columns.js +21 -0
- package/dist/tools/fiori/smoke/batch.js +273 -0
- package/dist/tools/fiori/smoke/draft-safety.js +241 -0
- package/dist/tools/fiori/smoke/draft-smoke.js +473 -0
- package/dist/tools/fiori/smoke/driver.js +171 -4
- package/dist/tools/fiori/smoke/evidence.js +35 -0
- package/dist/tools/fiori/smoke/labels-codes.js +207 -0
- package/dist/tools/fiori/smoke/nav-error.js +26 -0
- package/dist/tools/fiori/smoke/run-smoke.js +155 -12
- package/dist/tools/fiori/tools.js +651 -7
- package/dist/tools/fiori/ui5-version.js +16 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/local-build.js +6 -0
- package/dist/tools/result-spill.js +238 -0
- package/dist/tools/sap-read.js +74 -15
- package/dist/tools/sap-write.js +122 -10
- package/dist/tools/shell/shell_exec.js +39 -12
- package/dist/tools/subagent/adt-serial.js +33 -0
- package/dist/tools/subagent/agent_run.js +2 -0
- package/dist/tools/subagent/read_agent.js +178 -0
- package/dist/tools/subagent/reader-prompt.js +48 -0
- package/dist/tools/syntax-state.js +67 -0
- package/dist/tools/todo.js +26 -19
- package/dist/tools/tool-loading.js +255 -0
- package/dist/tools/tool-output-read.js +117 -0
- package/dist/tools/transport-fit.js +462 -0
- package/dist/tools/transport-resolution.js +3 -1
- package/dist/tools/transport.js +39 -8
- package/dist/tools/verify.js +184 -8
- package/dist/ui/app.js +284 -146
- package/dist/ui/approval-modal.js +41 -15
- package/dist/ui/ask-answer-text.js +17 -0
- package/dist/ui/body.js +25 -6
- package/dist/ui/card-slot.js +98 -0
- package/dist/ui/checklist.js +9 -0
- package/dist/ui/coaching-picker-classic.js +8 -5
- package/dist/ui/command-palette.js +6 -4
- package/dist/ui/confirm-request.js +18 -0
- package/dist/ui/context-grid.js +2 -1
- package/dist/ui/file-palette.js +6 -4
- package/dist/ui/files-card-emitter.js +19 -0
- package/dist/ui/footer-line.js +16 -0
- package/dist/ui/footer.js +105 -104
- package/dist/ui/input-wrap.js +122 -0
- package/dist/ui/interrupt.js +103 -0
- package/dist/ui/key-burst.js +117 -0
- package/dist/ui/line-resolution.js +4 -3
- package/dist/ui/live-area.js +71 -0
- package/dist/ui/login-banner.js +54 -43
- package/dist/ui/rewind-panel.js +76 -24
- package/dist/ui/role-props.js +47 -0
- package/dist/ui/sap-state-store.js +2 -1
- package/dist/ui/session-timeline.js +49 -3
- package/dist/ui/skill-picker.js +5 -47
- package/dist/ui/text-input.js +128 -24
- package/dist/ui/todo-emitter.js +19 -0
- package/dist/ui/turn-status-emitter.js +65 -2
- package/dist/ui/turn-status.js +16 -19
- package/dist/ui/use-card-request.js +44 -0
- package/dist/ui/widgets/ask-card.js +521 -0
- package/dist/ui/widgets/bar-chart.js +11 -5
- package/dist/ui/widgets/coaching-picker.js +50 -55
- package/dist/ui/widgets/confirm-card.js +193 -0
- package/dist/ui/widgets/dep-graph.js +3 -2
- package/dist/ui/widgets/diff-viewer.js +6 -5
- package/dist/ui/widgets/files-card.js +97 -0
- package/dist/ui/widgets/question-card.js +2 -1
- package/dist/ui/widgets/reclaim-raw-stdin.js +24 -0
- package/dist/ui/widgets/shortcuts-sheet.js +25 -0
- package/dist/ui/widgets/stack-frames.js +2 -1
- package/dist/upgrade-check.js +39 -8
- package/package.json +12 -2
- package/test-harness/fe-draft/app.ts +188 -0
- package/test-harness/fe-draft/fe-draft.e2e.test.ts +461 -0
- package/test-harness/fe-draft/manifest-enhancer.unit.test.ts +51 -0
- package/test-harness/fe-draft/routes.ts +224 -0
- package/test-harness/fe-draft/routes.unit.test.ts +45 -0
- package/test-harness/fe-draft/server.ts +225 -0
- package/test-harness/fe-draft/variants.ts +196 -0
- package/vitest.smoke-e2e.config.ts +20 -0
|
@@ -24,6 +24,34 @@ import * as path from 'node:path';
|
|
|
24
24
|
import { resolveBrowserExecutable } from './browser.js';
|
|
25
25
|
import { openApp as realOpenApp } from './driver.js';
|
|
26
26
|
import { checkBoot, checkOData, checkColumns, checkRows, checkExercise, } from './assertions.js';
|
|
27
|
+
import { deriveExpectedColumns } from './audit-columns.js';
|
|
28
|
+
import { checkLabels, judgeRawCodes, readRowTruths } from './labels-codes.js';
|
|
29
|
+
import { pairBatch } from './batch.js';
|
|
30
|
+
import { writeEvidence, auditSummary, SMOKE_VIEWPORT } from './evidence.js';
|
|
31
|
+
import { classifyNavError, NAV_TIMEOUT_MS, REFUSED_ATTEMPTS, REFUSED_BACKOFF_MS } from './nav-error.js';
|
|
32
|
+
/**
|
|
33
|
+
* L1-5a: the inner requests of every recorded `$batch`. A body that cannot be
|
|
34
|
+
* read is itself a failure (status 0), never dropped.
|
|
35
|
+
*/
|
|
36
|
+
export async function innerBatchParts(page) {
|
|
37
|
+
if (!page.batchExchanges)
|
|
38
|
+
return [];
|
|
39
|
+
const out = [];
|
|
40
|
+
for (const ex of await page.batchExchanges()) {
|
|
41
|
+
try {
|
|
42
|
+
out.push(...pairBatch(ex));
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
out.push({
|
|
46
|
+
method: '$batch',
|
|
47
|
+
url: '',
|
|
48
|
+
status: 0,
|
|
49
|
+
errorMessage: `could not read a $batch body (${err instanceof Error ? err.message : String(err)})`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
27
55
|
/**
|
|
28
56
|
* UI5 selectors used by the driver-side collection. ONE exported const so Track
|
|
29
57
|
* 3 can extend it (add chart/form control selectors) without hunting through
|
|
@@ -32,8 +60,16 @@ import { checkBoot, checkOData, checkColumns, checkRows, checkExercise, } from '
|
|
|
32
60
|
export const SMOKE_SELECTORS = {
|
|
33
61
|
/** Responsive-table + grid-table column headers. */
|
|
34
62
|
tableHeaders: '.sapMColumnHeader, [role="columnheader"]',
|
|
35
|
-
/**
|
|
36
|
-
|
|
63
|
+
/**
|
|
64
|
+
* DATA rows only (L1-2b F-3): a responsive-table list item that is neither
|
|
65
|
+
* the header row nor the "no data" line, or a grid-table content row that is
|
|
66
|
+
* not a hidden filler row. The old selector counted the header and the
|
|
67
|
+
* "To start, set the relevant filters and choose Go" line as 2 rows.
|
|
68
|
+
*/
|
|
69
|
+
tableRows: '.sapMListTbl tr.sapMListTblRow:not(.sapMListTblHeader):not([id$="-nodata"]), ' +
|
|
70
|
+
'table.sapUiTableCtrlScroll tr.sapUiTableContentRow:not(.sapUiTableRowHidden)',
|
|
71
|
+
/** The table has finished loading and is empty (responsive no-data line / empty grid). */
|
|
72
|
+
emptyList: '.sapMListTbl tr[id$="-nodata"], .sapUiTableEmpty',
|
|
37
73
|
/** Overview-page cards. */
|
|
38
74
|
ovpCards: '.sapOvpCard',
|
|
39
75
|
/** Freestyle chart control (sap.viz VizFrame) — Track 3 chart controlKind. */
|
|
@@ -89,8 +125,10 @@ function isRowsControl(kind) {
|
|
|
89
125
|
*/
|
|
90
126
|
async function runExercises(page, exercises) {
|
|
91
127
|
const results = [];
|
|
128
|
+
const pageErrors = () => (page.pageErrors ? page.pageErrors() : []);
|
|
92
129
|
for (const ex of exercises) {
|
|
93
130
|
const before = page.consoleErrors().length;
|
|
131
|
+
const beforePage = pageErrors().length;
|
|
94
132
|
const label = ex.kind === 'press' ? ex.selectorHint : ex.name;
|
|
95
133
|
let threw;
|
|
96
134
|
try {
|
|
@@ -109,7 +147,8 @@ async function runExercises(page, exercises) {
|
|
|
109
147
|
threw = err instanceof Error ? err.message : String(err);
|
|
110
148
|
}
|
|
111
149
|
const newConsoleErrors = page.consoleErrors().slice(before);
|
|
112
|
-
|
|
150
|
+
const newPageErrors = pageErrors().slice(beforePage);
|
|
151
|
+
results.push({ kind: ex.kind, label, threw, newConsoleErrors, newPageErrors });
|
|
113
152
|
}
|
|
114
153
|
return results;
|
|
115
154
|
}
|
|
@@ -125,18 +164,49 @@ export async function runSmoke(spec, deps) {
|
|
|
125
164
|
const resolveBrowser = deps.resolveBrowser ?? (() => resolveBrowserExecutable());
|
|
126
165
|
const open = deps.openApp ?? realOpenApp;
|
|
127
166
|
const timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
167
|
+
const startedAt = new Date();
|
|
168
|
+
const t0 = Date.now();
|
|
128
169
|
// Skip path 2 — no browser.
|
|
129
170
|
const browser = resolveBrowser();
|
|
130
171
|
if (!browser) {
|
|
131
172
|
return skipped('no Chromium-based browser found (set CSPEACH_BROWSER to an Edge/Chrome executable); no app was rendered');
|
|
132
173
|
}
|
|
133
|
-
// Skip path 3 —
|
|
174
|
+
// Skip path 3 — the first navigation failed. L1-7: classified, never
|
|
175
|
+
// swallowed. Refused → the preview may still be starting: retry, then skip.
|
|
176
|
+
// Timeout → the app did not settle: a real `boot` FAILURE, never retried.
|
|
177
|
+
// Certificate → skip with the trust hint. Anything else → skip, named.
|
|
178
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
134
179
|
let page;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
180
|
+
for (let attempt = 1; !page; attempt++) {
|
|
181
|
+
try {
|
|
182
|
+
page = spec.capUser
|
|
183
|
+
? await open(browser.path, spec.appUrl, timeoutMs, NAV_TIMEOUT_MS, { credentials: { username: spec.capUser, password: '' } })
|
|
184
|
+
: await open(browser.path, spec.appUrl, timeoutMs, NAV_TIMEOUT_MS);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
const nav = classifyNavError(err);
|
|
188
|
+
if (nav.kind === 'refused') {
|
|
189
|
+
if (attempt < REFUSED_ATTEMPTS) {
|
|
190
|
+
await sleep(REFUSED_BACKOFF_MS);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
return skipped(`preview server not reachable at ${spec.appUrl}; run npm run start first`);
|
|
194
|
+
}
|
|
195
|
+
if (nav.kind === 'timeout') {
|
|
196
|
+
const shot = err.screenshotPath;
|
|
197
|
+
return {
|
|
198
|
+
verification: 'smoke',
|
|
199
|
+
passed: false,
|
|
200
|
+
checks: [{ name: 'boot', ok: false, detail: `page did not settle in ${NAV_TIMEOUT_MS / 1000} s` }],
|
|
201
|
+
...(typeof shot === 'string' ? { screenshots: { failure: shot } } : {}),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (nav.kind === 'cert') {
|
|
205
|
+
return skipped(`the browser does not trust the certificate at ${spec.appUrl} (${nav.message}); ` +
|
|
206
|
+
'trust the SAP system certificate — run cspeach doctor for the steps');
|
|
207
|
+
}
|
|
208
|
+
return skipped(`could not open ${spec.appUrl}: ${nav.message}`);
|
|
209
|
+
}
|
|
140
210
|
}
|
|
141
211
|
// F-L2-10A evidence capture. The dir is created LAZILY on the first successful
|
|
142
212
|
// capture, so a skip path (or a driver with no screenshot capability) writes
|
|
@@ -163,6 +233,10 @@ export async function runSmoke(spec, deps) {
|
|
|
163
233
|
};
|
|
164
234
|
// Hoisted so the finally block can decide whether a failure shot is owed.
|
|
165
235
|
const checks = [];
|
|
236
|
+
// L1-8b: set once a real verdict exists — the finally block writes its evidence.
|
|
237
|
+
let result;
|
|
238
|
+
let batch = [];
|
|
239
|
+
const navMs = Date.now() - t0;
|
|
166
240
|
try {
|
|
167
241
|
// Skip path 4 — SAP logon page (deployed BSP / unauthenticated preview).
|
|
168
242
|
// Detect via a logon-redirect URL OR the presence of a logon-form field, and
|
|
@@ -172,12 +246,37 @@ export async function runSmoke(spec, deps) {
|
|
|
172
246
|
if (LOGON_URL.test(url) || logonHits.length > 0) {
|
|
173
247
|
return skipped(`preview served a SAP logon page (not authenticated) at ${url}; run npm run start first`);
|
|
174
248
|
}
|
|
249
|
+
// L1-2b F-2/F-3: a table is judged only once it has finished loading —
|
|
250
|
+
// data rows, or the empty-list marker. Navigation "settling" is not enough:
|
|
251
|
+
// UI5 loads the list after networkidle. A timeout falls through; the rows
|
|
252
|
+
// and odata checks then fail on what is (not) there.
|
|
253
|
+
if (isRowsControl(spec.controlKind) || spec.metadataAudit) {
|
|
254
|
+
try {
|
|
255
|
+
await page.waitForSelector(`${SMOKE_SELECTORS.tableRows}, ${SMOKE_SELECTORS.emptyList}`);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
// judged below on what rendered
|
|
259
|
+
}
|
|
260
|
+
}
|
|
175
261
|
// The app is open past the logon check — this is the boot evidence shot.
|
|
176
262
|
await capture('boot', 'boot.png');
|
|
177
263
|
// Real render — assert content. boot + odata always; columns only when the
|
|
178
264
|
// spec asked for them; rows only for a rows-bearing (table) control.
|
|
179
|
-
|
|
180
|
-
|
|
265
|
+
// Dry run 2026-09-25: an "&" in the header passed every check — boot
|
|
266
|
+
// also reads the visible text. A page that can't answer is judged as before.
|
|
267
|
+
let visibleText;
|
|
268
|
+
if (page.evalInPage) {
|
|
269
|
+
try {
|
|
270
|
+
const text = await page.evalInPage('function () { return document.body ? document.body.innerText : ""; }', null);
|
|
271
|
+
visibleText = typeof text === 'string' ? text : undefined;
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
visibleText = undefined;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
checks.push(checkBoot(page.consoleErrors(), page.failedRequests(), visibleText));
|
|
278
|
+
batch = await innerBatchParts(page);
|
|
279
|
+
checks.push(checkOData(page.failedRequests(), page.observedRequests ? page.observedRequests() : [], batch, page.unansweredRequests ? page.unansweredRequests() : []));
|
|
181
280
|
if (spec.expectedColumns && spec.expectedColumns.length > 0) {
|
|
182
281
|
const headers = await page.queryTexts(SMOKE_SELECTORS.tableHeaders);
|
|
183
282
|
checks.push(checkColumns(headers, spec.expectedColumns));
|
|
@@ -186,6 +285,28 @@ export async function runSmoke(spec, deps) {
|
|
|
186
285
|
const rows = await page.queryTexts(SMOKE_SELECTORS.tableRows);
|
|
187
286
|
checks.push(checkRows(rows.length, spec.allowEmptyRows === true));
|
|
188
287
|
}
|
|
288
|
+
// L1-3: labels + no-raw-codes, judged against the audit (machine truth).
|
|
289
|
+
// Runs before the exercises, which may navigate away from the list.
|
|
290
|
+
if (spec.metadataAudit) {
|
|
291
|
+
const audit = spec.metadataAudit;
|
|
292
|
+
const expected = deriveExpectedColumns(audit);
|
|
293
|
+
let snapshot;
|
|
294
|
+
try {
|
|
295
|
+
snapshot = page.cellTextsByColumn ? await page.cellTextsByColumn(audit.entitySet) : undefined;
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
snapshot = undefined; // judged as "could not read the table" — a failure
|
|
299
|
+
}
|
|
300
|
+
checks.push(checkLabels(snapshot, expected, audit));
|
|
301
|
+
let truths;
|
|
302
|
+
try {
|
|
303
|
+
truths = snapshot ? await readRowTruths(page, audit, expected, snapshot) : undefined;
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
truths = undefined;
|
|
307
|
+
}
|
|
308
|
+
checks.push(judgeRawCodes(audit, expected, snapshot, truths));
|
|
309
|
+
}
|
|
189
310
|
// Track 3 (dynamic A4): run each declared exercise through the SAME page
|
|
190
311
|
// abstraction, diffing the driver's console-error collection around each
|
|
191
312
|
// interaction. A press that throws or an exercise that emits a new console
|
|
@@ -200,17 +321,39 @@ export async function runSmoke(spec, deps) {
|
|
|
200
321
|
// `screenshots` is the SAME object the finally block may still add the
|
|
201
322
|
// failure entry to, so a failing run carries it even though the value is
|
|
202
323
|
// built here. It rides along only when the driver can shoot at all.
|
|
203
|
-
|
|
324
|
+
result = {
|
|
204
325
|
verification: 'smoke',
|
|
205
326
|
passed: checks.every((c) => c.ok),
|
|
206
327
|
checks,
|
|
207
328
|
...(canShoot ? { screenshots } : {}),
|
|
208
329
|
};
|
|
330
|
+
return result;
|
|
209
331
|
}
|
|
210
332
|
finally {
|
|
211
333
|
// A failing check earns a failure shot — captured BEFORE the page is closed.
|
|
212
334
|
if (checks.some((c) => !c.ok))
|
|
213
335
|
await capture('failure', 'failure.png');
|
|
214
336
|
await page.close();
|
|
337
|
+
// L1-8b evidence pack — only for a real verdict (never on a skip path).
|
|
338
|
+
// `result` is the object the caller receives, so the path rides along.
|
|
339
|
+
if (result) {
|
|
340
|
+
const dir = shotDir ?? deps.artifactDir ?? path.join(os.homedir(), '.cspeach', 'smoke', `${appNameFromUrl(spec.appUrl)}-${runStamp()}`);
|
|
341
|
+
const file = writeEvidence(dir, {
|
|
342
|
+
tool: 'fiori_render_smoke',
|
|
343
|
+
appUrl: spec.appUrl,
|
|
344
|
+
startedAt: startedAt.toISOString(),
|
|
345
|
+
finishedAt: new Date().toISOString(),
|
|
346
|
+
viewport: SMOKE_VIEWPORT,
|
|
347
|
+
verification: result.verification,
|
|
348
|
+
passed: result.passed,
|
|
349
|
+
checks: result.checks,
|
|
350
|
+
audit: auditSummary(spec.metadataAudit),
|
|
351
|
+
batch,
|
|
352
|
+
screenshots: result.screenshots ?? {},
|
|
353
|
+
timings: { navigateMs: navMs, totalMs: Date.now() - t0 },
|
|
354
|
+
});
|
|
355
|
+
if (file)
|
|
356
|
+
result.evidence = file;
|
|
357
|
+
}
|
|
215
358
|
}
|
|
216
359
|
}
|