Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
1771
extensions/qa-lab/web/src/app.ts
Normal file
1771
extensions/qa-lab/web/src/app.ts
Normal file
File diff suppressed because it is too large
Load Diff
2
extensions/qa-lab/web/src/assets.d.ts
vendored
Normal file
2
extensions/qa-lab/web/src/assets.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// Qa Lab type declarations define plugin contracts.
|
||||
declare module "*.css";
|
||||
106
extensions/qa-lab/web/src/capture-saved-view.test.ts
Normal file
106
extensions/qa-lab/web/src/capture-saved-view.test.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
// Qa Lab tests cover capture saved view plugin behavior.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeCaptureSavedView, normalizeCaptureSavedViews } from "./capture-saved-view.js";
|
||||
|
||||
describe("capture saved views", () => {
|
||||
it("drops non-object and nameless saved views", () => {
|
||||
expect(normalizeCaptureSavedView(null)).toBeNull();
|
||||
expect(normalizeCaptureSavedView({ id: "view-1" })).toBeNull();
|
||||
expect(normalizeCaptureSavedViews([{ id: "view-1" }, "bad", null])).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps valid saved view fields", () => {
|
||||
expect(
|
||||
normalizeCaptureSavedView({
|
||||
id: "view-1",
|
||||
name: "Errors",
|
||||
sessionIds: ["session-a"],
|
||||
kindFilter: ["request"],
|
||||
providerFilter: ["openai"],
|
||||
hostFilter: ["api.example.test"],
|
||||
searchText: "timeout",
|
||||
headerMode: "all",
|
||||
viewMode: "timeline",
|
||||
groupMode: "flow",
|
||||
timelineLaneMode: "provider",
|
||||
timelineLaneSort: "severity",
|
||||
timelineZoom: 200,
|
||||
timelineSparklineMode: "lane-relative",
|
||||
errorsOnly: true,
|
||||
detailPlacement: "bottom",
|
||||
payloadLayout: "raw",
|
||||
payloadExtent: "full",
|
||||
}),
|
||||
).toEqual({
|
||||
id: "view-1",
|
||||
name: "Errors",
|
||||
sessionIds: ["session-a"],
|
||||
kindFilter: ["request"],
|
||||
providerFilter: ["openai"],
|
||||
hostFilter: ["api.example.test"],
|
||||
searchText: "timeout",
|
||||
headerMode: "all",
|
||||
viewMode: "timeline",
|
||||
groupMode: "flow",
|
||||
timelineLaneMode: "provider",
|
||||
timelineLaneSort: "severity",
|
||||
timelineZoom: 200,
|
||||
timelineSparklineMode: "lane-relative",
|
||||
errorsOnly: true,
|
||||
detailPlacement: "bottom",
|
||||
payloadLayout: "raw",
|
||||
payloadExtent: "full",
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back invalid enum and scalar fields to safe defaults", () => {
|
||||
expect(
|
||||
normalizeCaptureSavedView({
|
||||
id: "view-1",
|
||||
name: "Corrupt",
|
||||
sessionIds: ["session-a", 42, " "],
|
||||
kindFilter: "request",
|
||||
providerFilter: ["openai"],
|
||||
hostFilter: null,
|
||||
searchText: 123,
|
||||
headerMode: "everything",
|
||||
viewMode: "grid",
|
||||
groupMode: "cards",
|
||||
timelineLaneMode: "host",
|
||||
timelineLaneSort: "random",
|
||||
timelineZoom: 999,
|
||||
timelineSparklineMode: "global",
|
||||
errorsOnly: "yes",
|
||||
detailPlacement: "left",
|
||||
payloadLayout: "html",
|
||||
payloadExtent: "all",
|
||||
}),
|
||||
).toMatchObject({
|
||||
sessionIds: ["session-a"],
|
||||
kindFilter: [],
|
||||
providerFilter: ["openai"],
|
||||
hostFilter: [],
|
||||
searchText: "",
|
||||
headerMode: "key",
|
||||
viewMode: "list",
|
||||
groupMode: "none",
|
||||
timelineLaneMode: "domain",
|
||||
timelineLaneSort: "most-events",
|
||||
timelineZoom: 100,
|
||||
timelineSparklineMode: "session-relative",
|
||||
errorsOnly: false,
|
||||
detailPlacement: "right",
|
||||
payloadLayout: null,
|
||||
payloadExtent: "preview",
|
||||
});
|
||||
});
|
||||
|
||||
it("caps loaded saved views to the UI limit", () => {
|
||||
const views = Array.from({ length: 20 }, (_, index) => ({
|
||||
id: `view-${index}`,
|
||||
name: `View ${index}`,
|
||||
}));
|
||||
|
||||
expect(normalizeCaptureSavedViews(views)).toHaveLength(12);
|
||||
});
|
||||
});
|
||||
124
extensions/qa-lab/web/src/capture-saved-view.ts
Normal file
124
extensions/qa-lab/web/src/capture-saved-view.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
// Qa Lab plugin module implements capture saved view behavior.
|
||||
import type { CaptureSavedView } from "./ui-render.js";
|
||||
|
||||
const MAX_SAVED_VIEWS = 12;
|
||||
const MAX_FILTER_ITEMS = 64;
|
||||
const MAX_FILTER_VALUE_LENGTH = 256;
|
||||
const MAX_NAME_LENGTH = 80;
|
||||
const MAX_SEARCH_TEXT_LENGTH = 500;
|
||||
|
||||
const headerModes = new Set<CaptureSavedView["headerMode"]>(["key", "all", "hidden"]);
|
||||
const viewModes = new Set<CaptureSavedView["viewMode"]>(["list", "timeline"]);
|
||||
const groupModes = new Set<CaptureSavedView["groupMode"]>(["none", "flow", "host-path", "burst"]);
|
||||
const timelineLaneModes = new Set<CaptureSavedView["timelineLaneMode"]>([
|
||||
"domain",
|
||||
"provider",
|
||||
"flow",
|
||||
]);
|
||||
const timelineLaneSorts = new Set<CaptureSavedView["timelineLaneSort"]>([
|
||||
"most-events",
|
||||
"most-errors",
|
||||
"severity",
|
||||
"alphabetical",
|
||||
]);
|
||||
const timelineZooms = new Set<CaptureSavedView["timelineZoom"]>([75, 100, 150, 200, 300]);
|
||||
const timelineSparklineModes = new Set<CaptureSavedView["timelineSparklineMode"]>([
|
||||
"session-relative",
|
||||
"lane-relative",
|
||||
]);
|
||||
const detailPlacements = new Set<CaptureSavedView["detailPlacement"]>(["right", "bottom"]);
|
||||
const payloadLayouts = new Set<NonNullable<CaptureSavedView["payloadLayout"]>>([
|
||||
"formatted",
|
||||
"raw",
|
||||
]);
|
||||
const payloadExtents = new Set<CaptureSavedView["payloadExtent"]>(["preview", "full"]);
|
||||
|
||||
function readString(value: unknown, maxLength: number): string | null {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed ? trimmed.slice(0, maxLength) : null;
|
||||
}
|
||||
|
||||
function readStringArray(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
return value
|
||||
.filter((item): item is string => typeof item === "string")
|
||||
.map((item) => item.trim().slice(0, MAX_FILTER_VALUE_LENGTH))
|
||||
.filter(Boolean)
|
||||
.slice(0, MAX_FILTER_ITEMS);
|
||||
}
|
||||
|
||||
function readEnum<T extends string>(value: unknown, allowed: Set<T>, fallback: T): T {
|
||||
return typeof value === "string" && allowed.has(value as T) ? (value as T) : fallback;
|
||||
}
|
||||
|
||||
function readNullableEnum<T extends string>(value: unknown, allowed: Set<T>): T | null {
|
||||
return typeof value === "string" && allowed.has(value as T) ? (value as T) : null;
|
||||
}
|
||||
|
||||
function readTimelineZoom(value: unknown): CaptureSavedView["timelineZoom"] {
|
||||
return typeof value === "number" && timelineZooms.has(value as CaptureSavedView["timelineZoom"])
|
||||
? (value as CaptureSavedView["timelineZoom"])
|
||||
: 100;
|
||||
}
|
||||
|
||||
export function normalizeCaptureSavedView(value: unknown): CaptureSavedView | null {
|
||||
const record =
|
||||
typeof value === "object" && value !== null ? (value as Record<string, unknown>) : null;
|
||||
if (!record) {
|
||||
return null;
|
||||
}
|
||||
const id = readString(record.id, MAX_FILTER_VALUE_LENGTH);
|
||||
const name = readString(record.name, MAX_NAME_LENGTH);
|
||||
if (!id || !name) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
sessionIds: readStringArray(record.sessionIds),
|
||||
kindFilter: readStringArray(record.kindFilter),
|
||||
providerFilter: readStringArray(record.providerFilter),
|
||||
hostFilter: readStringArray(record.hostFilter),
|
||||
searchText:
|
||||
typeof record.searchText === "string"
|
||||
? record.searchText.slice(0, MAX_SEARCH_TEXT_LENGTH)
|
||||
: "",
|
||||
headerMode: readEnum(record.headerMode, headerModes, "key"),
|
||||
viewMode: readEnum(record.viewMode, viewModes, "list"),
|
||||
groupMode: readEnum(record.groupMode, groupModes, "none"),
|
||||
timelineLaneMode: readEnum(record.timelineLaneMode, timelineLaneModes, "domain"),
|
||||
timelineLaneSort: readEnum(record.timelineLaneSort, timelineLaneSorts, "most-events"),
|
||||
timelineZoom: readTimelineZoom(record.timelineZoom),
|
||||
timelineSparklineMode: readEnum(
|
||||
record.timelineSparklineMode,
|
||||
timelineSparklineModes,
|
||||
"session-relative",
|
||||
),
|
||||
errorsOnly: record.errorsOnly === true,
|
||||
detailPlacement: readEnum(record.detailPlacement, detailPlacements, "right"),
|
||||
payloadLayout: readNullableEnum(record.payloadLayout, payloadLayouts),
|
||||
payloadExtent: readEnum(record.payloadExtent, payloadExtents, "preview"),
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeCaptureSavedViews(value: unknown): CaptureSavedView[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
const views: CaptureSavedView[] = [];
|
||||
for (const item of value) {
|
||||
const view = normalizeCaptureSavedView(item);
|
||||
if (view) {
|
||||
views.push(view);
|
||||
}
|
||||
if (views.length >= MAX_SAVED_VIEWS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return views;
|
||||
}
|
||||
34
extensions/qa-lab/web/src/errors.ts
Normal file
34
extensions/qa-lab/web/src/errors.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
// Qa Lab plugin module implements errors behavior.
|
||||
export function formatErrorMessage(err: unknown): string {
|
||||
if (err instanceof Error) {
|
||||
let formatted = err.message || err.name || "Error";
|
||||
let cause: unknown = err.cause;
|
||||
const seen = new Set<unknown>([err]);
|
||||
while (cause && !seen.has(cause)) {
|
||||
seen.add(cause);
|
||||
if (cause instanceof Error) {
|
||||
if (cause.message) {
|
||||
formatted += ` | ${cause.message}`;
|
||||
}
|
||||
cause = cause.cause;
|
||||
continue;
|
||||
}
|
||||
if (typeof cause === "string") {
|
||||
formatted += ` | ${cause}`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return formatted;
|
||||
}
|
||||
if (typeof err === "string") {
|
||||
return err;
|
||||
}
|
||||
if (typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") {
|
||||
return String(err);
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(err);
|
||||
} catch {
|
||||
return Object.prototype.toString.call(err);
|
||||
}
|
||||
}
|
||||
11
extensions/qa-lab/web/src/main.ts
Normal file
11
extensions/qa-lab/web/src/main.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// Qa Lab plugin module implements main behavior.
|
||||
import "./styles.css";
|
||||
import { createQaLabApp } from "./app.js";
|
||||
|
||||
const root = document.querySelector<HTMLDivElement>("#app");
|
||||
|
||||
if (!root) {
|
||||
throw new Error("QA Lab app root missing");
|
||||
}
|
||||
|
||||
void createQaLabApp(root);
|
||||
3766
extensions/qa-lab/web/src/styles.css
Normal file
3766
extensions/qa-lab/web/src/styles.css
Normal file
File diff suppressed because it is too large
Load Diff
413
extensions/qa-lab/web/src/ui-render.test.ts
Normal file
413
extensions/qa-lab/web/src/ui-render.test.ts
Normal file
@@ -0,0 +1,413 @@
|
||||
// Qa Lab UI render tests cover evidence gallery affordances.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { renderQaLabUi, type UiState } from "./ui-render.js";
|
||||
|
||||
function evidenceState(overrides: Partial<UiState> = {}): UiState {
|
||||
return {
|
||||
activeTab: "evidence",
|
||||
bootstrap: null,
|
||||
busy: false,
|
||||
captureCollapsedLaneIds: [],
|
||||
captureControlsExpanded: true,
|
||||
captureCoverage: null,
|
||||
captureDetailPlacement: "right",
|
||||
captureDetailSplitDragging: false,
|
||||
captureDetailSplitPct: 35,
|
||||
captureDetailView: "overview",
|
||||
captureErrorsOnly: false,
|
||||
captureEvents: [],
|
||||
captureFlowDetailLayout: null,
|
||||
captureGroupMode: "none",
|
||||
captureHeaderMode: "key",
|
||||
captureHostFilter: [],
|
||||
captureKindFilter: [],
|
||||
capturePayloadDetailLayout: null,
|
||||
capturePayloadEventFilter: "",
|
||||
capturePayloadEventSort: "stream",
|
||||
capturePayloadExtent: "preview",
|
||||
capturePinnedLaneIds: [],
|
||||
capturePreferredDetailView: null,
|
||||
captureProviderFilter: [],
|
||||
captureQueryPreset: "none",
|
||||
captureQueryRows: [],
|
||||
captureSavedViews: [],
|
||||
captureSearchText: "",
|
||||
captureSelectedSessionsExpanded: true,
|
||||
captureSessions: [],
|
||||
captureStartupStatus: null,
|
||||
captureSummaryExpanded: true,
|
||||
captureTimelineBrushAnchorPct: null,
|
||||
captureTimelineBrushCurrentPct: null,
|
||||
captureTimelineFocusSelectedFlow: false,
|
||||
captureTimelineFocusedLaneMode: "all",
|
||||
captureTimelineFocusedLaneThreshold: "any",
|
||||
captureTimelineLaneMode: "domain",
|
||||
captureTimelineLaneSearch: "",
|
||||
captureTimelineLaneSort: "most-events",
|
||||
captureTimelinePreviousLaneSort: null,
|
||||
captureTimelineSparklineMode: "session-relative",
|
||||
captureTimelineWindowEndPct: null,
|
||||
captureTimelineWindowStartPct: null,
|
||||
captureTimelineZoom: 100,
|
||||
captureViewMode: "list",
|
||||
composer: {
|
||||
conversationId: "",
|
||||
conversationKind: "direct",
|
||||
senderId: "",
|
||||
senderName: "",
|
||||
text: "",
|
||||
},
|
||||
error: null,
|
||||
evidence: null,
|
||||
evidenceArtifactFilter: "all",
|
||||
evidenceError: null,
|
||||
evidenceLoading: false,
|
||||
evidencePathDraft: "",
|
||||
evidenceSearchText: "",
|
||||
evidenceStatusFilter: "all",
|
||||
latestReport: null,
|
||||
runnerDraft: null,
|
||||
runnerDraftDirty: false,
|
||||
scenarioRun: null,
|
||||
selectedCaptureEventKey: null,
|
||||
selectedCaptureSessionIds: [],
|
||||
selectedConversationId: null,
|
||||
selectedEvidenceEntryId: null,
|
||||
selectedScenarioId: null,
|
||||
selectedThreadId: null,
|
||||
sidebarCollapsed: false,
|
||||
sidebarPanel: "scenarios",
|
||||
snapshot: null,
|
||||
theme: "light",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("QA Lab UI evidence render", () => {
|
||||
it("renders capture startup commands without personal home paths", () => {
|
||||
const html = renderQaLabUi(evidenceState({ activeTab: "capture" }));
|
||||
|
||||
expect(html).toContain("$HOME/.openclaw/debug-proxy/certs/root-ca.pem");
|
||||
expect(html).not.toContain("/Users/");
|
||||
});
|
||||
|
||||
it("maps blocked and skipped evidence statuses to styled tones", () => {
|
||||
const html = renderQaLabUi(
|
||||
evidenceState({
|
||||
evidence: {
|
||||
counts: { blocked: 1, fail: 0, pass: 0, skipped: 1 },
|
||||
entries: [
|
||||
{
|
||||
artifacts: [],
|
||||
coverage: [{ id: "qa.blocked", role: "primary" }],
|
||||
failureReason: "Environment unavailable",
|
||||
id: "qa-lab.blocked",
|
||||
kind: "script-test",
|
||||
sourcePath: "scripts/blocked.ts",
|
||||
status: "blocked",
|
||||
title: "Blocked evidence",
|
||||
},
|
||||
{
|
||||
artifacts: [],
|
||||
coverage: [{ id: "qa.skipped", role: "primary" }],
|
||||
failureReason: null,
|
||||
id: "qa-lab.skipped",
|
||||
kind: "vitest-test",
|
||||
sourcePath: "extensions/qa-lab/src/skipped.test.ts",
|
||||
status: "skipped",
|
||||
title: "Skipped evidence",
|
||||
},
|
||||
],
|
||||
evidenceMode: "full",
|
||||
evidencePath: ".artifacts/qa-e2e/suite/qa-evidence.json",
|
||||
generatedAt: "2026-06-17T12:00:00.000Z",
|
||||
producerContext: null,
|
||||
profile: null,
|
||||
schemaVersion: 2,
|
||||
},
|
||||
selectedEvidenceEntryId: "qa-lab.blocked",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(html).toContain("badge-pending");
|
||||
expect(html).toContain("badge-skip");
|
||||
expect(html).toContain("scenario-item-dot-pending");
|
||||
expect(html).toContain("scenario-item-dot-skip");
|
||||
expect(html).not.toContain("badge-blocked");
|
||||
expect(html).not.toContain("badge-skipped");
|
||||
expect(html).not.toContain("scenario-item-dot-blocked");
|
||||
});
|
||||
|
||||
it("links executed UX Matrix cells to evidence entries and leaves proof gaps unlinked", () => {
|
||||
const html = renderQaLabUi(
|
||||
evidenceState({
|
||||
evidence: {
|
||||
counts: { blocked: 0, fail: 0, pass: 1, skipped: 0 },
|
||||
entries: [
|
||||
{
|
||||
artifacts: [
|
||||
{
|
||||
error: null,
|
||||
exists: true,
|
||||
href: "/api/evidence/artifact?artifactPath=screenshot.png",
|
||||
kind: "screenshot",
|
||||
mediaKind: "image",
|
||||
path: "screenshot.png",
|
||||
preview: null,
|
||||
source: "ux-matrix:web-ui:first-run",
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
exists: true,
|
||||
href: "/api/evidence/artifact?artifactPath=recording.gif",
|
||||
kind: "motion-preview-gif",
|
||||
mediaKind: "image",
|
||||
path: "recording.gif",
|
||||
preview: null,
|
||||
source: "ux-matrix:web-ui:first-run",
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
exists: true,
|
||||
href: "/api/evidence/artifact?artifactPath=recording.webm",
|
||||
kind: "video",
|
||||
mediaKind: "video",
|
||||
path: "recording.webm",
|
||||
preview: null,
|
||||
source: "ux-matrix:web-ui:first-run",
|
||||
},
|
||||
],
|
||||
coverage: [{ id: "ui.control", role: "primary" }],
|
||||
failureReason: null,
|
||||
id: "ux-matrix.web-ui.first-run",
|
||||
kind: "ux-matrix-cell",
|
||||
sourcePath: "scripts/ux-matrix/dashboard.ts",
|
||||
status: "pass",
|
||||
title: "UX Matrix: web-ui / first-run",
|
||||
},
|
||||
],
|
||||
evidenceMode: "full",
|
||||
evidencePath: ".artifacts/qa-e2e/suite/qa-evidence.json",
|
||||
generatedAt: "2026-06-17T12:00:00.000Z",
|
||||
producerContext: {
|
||||
commands: null,
|
||||
kind: "ux-matrix",
|
||||
manifest: {
|
||||
href: "/api/evidence/artifact?artifactPath=manifest.json",
|
||||
path: "manifest.json",
|
||||
preview: null,
|
||||
runId: "run-1",
|
||||
runStatus: "pass",
|
||||
},
|
||||
matrix: {
|
||||
cells: [
|
||||
{
|
||||
artifactKinds: ["screenshot"],
|
||||
artifactPaths: ["screenshot.png"],
|
||||
coverageIds: ["ui.control"],
|
||||
runner: {
|
||||
availability: "local",
|
||||
command: "pnpm openclaw qa suite --scenario ux-matrix-evidence-dashboard",
|
||||
lane: "web-ui-playwright",
|
||||
workflow: ".github/workflows/ux-matrix-qa.yml#ux-matrix-local",
|
||||
},
|
||||
stage: "first-run",
|
||||
status: "pass",
|
||||
surface: "web-ui",
|
||||
testId: "ux-matrix.web-ui.first-run",
|
||||
title: "UX Matrix: web-ui / first-run",
|
||||
},
|
||||
{
|
||||
artifactKinds: [],
|
||||
artifactPaths: [],
|
||||
coverageIds: ["cli.entrypoint"],
|
||||
runner: {
|
||||
availability: "local",
|
||||
command: "pnpm openclaw qa suite --scenario ux-matrix-evidence-dashboard",
|
||||
lane: "cli-status",
|
||||
workflow: ".github/workflows/ux-matrix-qa.yml#ux-matrix-local",
|
||||
},
|
||||
stage: "first-run",
|
||||
status: "proof-gap",
|
||||
surface: "cli",
|
||||
testId: null,
|
||||
title: null,
|
||||
},
|
||||
],
|
||||
counts: { pass: 1, "proof-gap": 1 },
|
||||
path: "matrix.json",
|
||||
stages: ["first-run"],
|
||||
surfaces: ["cli", "web-ui"],
|
||||
},
|
||||
preflight: { adbDevices: null, memory: null },
|
||||
releaseLedger: null,
|
||||
rootPath: ".artifacts/qa-e2e/suite/script/ux-matrix-evidence-dashboard/run-1",
|
||||
scorecard: null,
|
||||
},
|
||||
profile: null,
|
||||
schemaVersion: 2,
|
||||
},
|
||||
selectedEvidenceEntryId: "ux-matrix.web-ui.first-run",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(html).toContain('data-evidence-entry-id="ux-matrix.web-ui.first-run"');
|
||||
expect(html).toContain("evidence-matrix-cell-proof-gap");
|
||||
expect(html).toContain("not executed in this run");
|
||||
expect(html).toContain("Coverage: cli.entrypoint");
|
||||
expect(html).toContain("Runner: cli-status");
|
||||
expect(html).toContain("Open media artifact");
|
||||
expect(html).toContain("Open video artifact");
|
||||
expect(html).not.toContain('src="/api/evidence/artifact?artifactPath=recording.gif"');
|
||||
expect(html).not.toContain("<video controls");
|
||||
expect(html).not.toContain('data-evidence-entry-id="null"');
|
||||
});
|
||||
|
||||
it("redacts secret-like capture payload fields in raw previews", () => {
|
||||
const payload =
|
||||
'{"message":"visible context","message":"duplicate context","completion_tokens":100,"cookies":["session=abc"],"apiToken":"secret-token","tokenValue":"token-value-secret","authTokens":["auth-token-secret"],"tokens":{"refresh":"refresh-token-secret"},"AWS_SECRET_ACCESS_KEY":"aws-secret","secretAccessKey":"access-secret","x-goog-api-key":"goog-secret","nested":{"password":"secret-password"}}';
|
||||
const html = renderQaLabUi(
|
||||
evidenceState({
|
||||
activeTab: "capture",
|
||||
captureDetailView: "payload",
|
||||
capturePayloadDetailLayout: "raw",
|
||||
captureEvents: [
|
||||
{
|
||||
contentType: "application/json",
|
||||
dataText: payload,
|
||||
direction: "outbound",
|
||||
flowId: "flow-1",
|
||||
host: "api.example.test",
|
||||
id: 1,
|
||||
kind: "request",
|
||||
method: "POST",
|
||||
path: "/v1/messages",
|
||||
payloadPreview: payload,
|
||||
protocol: "https",
|
||||
provider: "mock",
|
||||
ts: 1,
|
||||
},
|
||||
],
|
||||
selectedCaptureEventKey: "1:flow-1:1:request",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(html).toContain("visible context");
|
||||
expect(html).toContain("duplicate context");
|
||||
expect(html).toContain("completion_tokens");
|
||||
expect(html).toContain("100");
|
||||
expect(html).toContain("apiToken");
|
||||
expect(html).toContain("nested");
|
||||
expect(html).toContain("[redacted]");
|
||||
expect(html).not.toContain("session=abc");
|
||||
expect(html).not.toContain("secret-token");
|
||||
expect(html).not.toContain("token-value-secret");
|
||||
expect(html).not.toContain("auth-token-secret");
|
||||
expect(html).not.toContain("refresh-token-secret");
|
||||
expect(html).not.toContain("aws-secret");
|
||||
expect(html).not.toContain("access-secret");
|
||||
expect(html).not.toContain("goog-secret");
|
||||
expect(html).not.toContain("secret-password");
|
||||
});
|
||||
|
||||
it("redacts secret-like fields when captured JSON previews are truncated", () => {
|
||||
const payload =
|
||||
'{"apiToken":"secret-token","nested":{"password":"secret-password"},"message":"visible context"';
|
||||
for (const capturePayloadDetailLayout of ["raw", "formatted"] as const) {
|
||||
const html = renderQaLabUi(
|
||||
evidenceState({
|
||||
activeTab: "capture",
|
||||
captureDetailView: "payload",
|
||||
capturePayloadDetailLayout,
|
||||
captureEvents: [
|
||||
{
|
||||
contentType: "application/json",
|
||||
dataText: payload,
|
||||
direction: "outbound",
|
||||
flowId: "flow-1",
|
||||
host: "api.example.test",
|
||||
id: 1,
|
||||
kind: "request",
|
||||
method: "POST",
|
||||
path: "/v1/messages",
|
||||
payloadPreview: payload,
|
||||
protocol: "https",
|
||||
provider: "mock",
|
||||
ts: 1,
|
||||
},
|
||||
],
|
||||
selectedCaptureEventKey: "1:flow-1:1:request",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(html).toContain("visible context");
|
||||
expect(html).toContain("[redacted]");
|
||||
expect(html).not.toContain("secret-token");
|
||||
expect(html).not.toContain("secret-password");
|
||||
}
|
||||
});
|
||||
|
||||
it("redacts secret-like SSE data fields in formatted payloads", () => {
|
||||
const payload = 'event: message\ndata: {"apiToken":"secret-token","message":"visible"}';
|
||||
const html = renderQaLabUi(
|
||||
evidenceState({
|
||||
activeTab: "capture",
|
||||
captureDetailView: "payload",
|
||||
capturePayloadDetailLayout: "formatted",
|
||||
captureEvents: [
|
||||
{
|
||||
contentType: "text/event-stream",
|
||||
dataText: payload,
|
||||
direction: "inbound",
|
||||
flowId: "flow-1",
|
||||
host: "api.example.test",
|
||||
id: 1,
|
||||
kind: "response",
|
||||
path: "/v1/messages",
|
||||
payloadPreview: payload,
|
||||
protocol: "https",
|
||||
provider: "mock",
|
||||
ts: 1,
|
||||
},
|
||||
],
|
||||
selectedCaptureEventKey: "1:flow-1:1:response",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(html).toContain("visible");
|
||||
expect(html).toContain("[redacted]");
|
||||
expect(html).not.toContain("secret-token");
|
||||
});
|
||||
|
||||
it("redacts secret-like fields when capture cuts inside a JSON value", () => {
|
||||
const payload = '{"apiToken":"secret-token';
|
||||
const html = renderQaLabUi(
|
||||
evidenceState({
|
||||
activeTab: "capture",
|
||||
captureDetailView: "payload",
|
||||
capturePayloadDetailLayout: "raw",
|
||||
captureEvents: [
|
||||
{
|
||||
contentType: "application/json",
|
||||
dataText: payload,
|
||||
direction: "outbound",
|
||||
flowId: "flow-1",
|
||||
host: "api.example.test",
|
||||
id: 1,
|
||||
kind: "request",
|
||||
method: "POST",
|
||||
path: "/v1/messages",
|
||||
payloadPreview: payload,
|
||||
protocol: "https",
|
||||
provider: "mock",
|
||||
ts: 1,
|
||||
},
|
||||
],
|
||||
selectedCaptureEventKey: "1:flow-1:1:request",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(html).toContain("[redacted]");
|
||||
expect(html).not.toContain("secret-token");
|
||||
});
|
||||
});
|
||||
4207
extensions/qa-lab/web/src/ui-render.ts
Normal file
4207
extensions/qa-lab/web/src/ui-render.ts
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user