Skip to content

Commit 429b439

Browse files
committed
Namespace editor-open artifacts
1 parent 5aa92e9 commit 429b439

3 files changed

Lines changed: 60 additions & 14 deletions

File tree

packages/runtime-core/src/command-registry.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1267,8 +1267,9 @@ export const commandRegistry = [
12671267
{ name: "wait-selector", description: "Selector that marks the editor as ready; defaults to the block editor shell.", format: "CSS selector" },
12681268
{ name: "wait-timeout", description: "Timeout for navigation and editor-ready waits.", format: "duration, e.g. 15s or 500ms" },
12691269
{ name: "capture", description: "Comma-separated artifacts to capture after opening the editor.", format: "steps,console,errors,html,screenshot,editor-state,editor-validity" },
1270+
{ name: "artifact-prefix", description: "Optional artifact directory relative to the runtime artifact root for this invocation; defaults to files/browser. Use files/browser/editor-open/<name> to isolate per-fixture editor-open evidence in a batch.", format: "relative artifact directory" },
12701271
],
1271-
outputShape: "JSON summary plus files/browser/editor-steps.jsonl, editor-summary.json, editor-state.json, optional editor-validity.json, and optional console/errors/html/screenshot artifacts.",
1272+
outputShape: "JSON summary plus files/browser/editor-steps.jsonl, editor-summary.json, editor-state.json, optional editor-validity.json, and optional console/errors/html/screenshot artifacts. When artifact-prefix is supplied, every editor-open artifact is written under that directory instead of files/browser.",
12721273
policyRequirement: "Runtime policy commands must include wordpress.editor-open.",
12731274
recipe: true,
12741275
handler: { kind: "playground", method: "runEditorOpen" },

packages/runtime-playground/src/editor-command-runners.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { now, sha256 } from "@automattic/wp-codebox-core/internals"
33
import { durationStringMs } from "./browser-actions.js"
44
import { BrowserArtifactSession } from "./browser-artifact-session.js"
55
import { BrowserCommandArtifactError } from "./browser-command-artifact-error.js"
6-
import type { BrowserArtifact, BrowserArtifactSummary, BrowserEditorCanvasProbeDiagnostic, BrowserEditorCanvasProbeSummary, BrowserEditorCanvasSelectorGroupSummary, BrowserEditorCanvasSelectorSummary, BrowserEditorReadinessSummary, BrowserEditorSaveSummary, BrowserEditorValidateBlocksSummary, BrowserEditorValiditySummary, BrowserProbeAuthSummary, BrowserProbeErrorRecord, BrowserProbeViewport, BrowserStepRecord } from "./browser-artifacts.js"
6+
import type { BrowserArtifact, BrowserArtifactFiles, BrowserArtifactSummary, BrowserEditorCanvasProbeDiagnostic, BrowserEditorCanvasProbeSummary, BrowserEditorCanvasSelectorGroupSummary, BrowserEditorCanvasSelectorSummary, BrowserEditorReadinessSummary, BrowserEditorSaveSummary, BrowserEditorValidateBlocksSummary, BrowserEditorValiditySummary, BrowserProbeAuthSummary, BrowserProbeErrorRecord, BrowserProbeViewport, BrowserStepRecord } from "./browser-artifacts.js"
77
import { attachBrowserCaptureListeners, launchChromiumBrowser } from "./browser-capture-session.js"
88
import { browserStepRecord } from "./browser-interactions.js"
99
import { browserPreviewNetworkPolicyIsActive, browserPreviewNetworkPolicySummary, browserPreviewNeedsContextRouting, browserPreviewOrigins, browserPreviewReadinessError, browserPreviewRouting, browserPreviewSecureContextError, browserPreviewTopology, resolveBrowserPreviewUrl, routeBrowserPreviewContextNetwork } from "./browser-preview-routing.js"
@@ -522,7 +522,8 @@ export async function runEditorOpenCommand({
522522
const topology = browserPreviewTopology(args, runtimeSpec, server.serverUrl)
523523
const { preview, networkPolicy } = topology
524524
const targetUrl = topology.resolveUrl(target.url)
525-
const artifactSession = new BrowserArtifactSession(artifactRoot, "files/browser", { source: "wordpress.editor-open", operation: "editor-open" })
525+
const artifactPathPrefix = editorOpenArtifactPathPrefixFromArgs(args)
526+
const artifactSession = new BrowserArtifactSession(artifactRoot, artifactPathPrefix, { source: "wordpress.editor-open", operation: "editor-open" })
526527

527528
const stepRecords: BrowserStepRecord[] = []
528529
const consoleMessages: Record<string, unknown>[] = []
@@ -637,16 +638,7 @@ export async function runEditorOpenCommand({
637638
...(server.previewProxyDiagnostics ? { previewProxy: server.previewProxyDiagnostics } : {}),
638639
...(browserPreviewNetworkPolicyIsActive(networkPolicy) ? { networkPolicy: browserPreviewNetworkPolicySummary(networkPolicy) } : {}),
639640
...topology.origins,
640-
files: {
641-
...(capture.has("steps") ? { steps: "files/browser/editor-steps.jsonl" } : {}),
642-
...(capture.has("console") ? { console: "files/browser/editor-console.jsonl" } : {}),
643-
...(capture.has("editor-state") ? { editorState: "files/browser/editor-state.json" } : {}),
644-
...(capture.has("editor-validity") ? { editorValidity: "files/browser/editor-validity.json" } : {}),
645-
...(capture.has("errors") ? { errors: "files/browser/editor-errors.jsonl" } : {}),
646-
...(capture.has("html") ? { html: "files/browser/editor-snapshot.html" } : {}),
647-
...(capture.has("screenshot") ? { screenshot: "files/browser/editor-screenshot.png" } : {}),
648-
summary: "files/browser/editor-summary.json",
649-
},
641+
files: editorOpenArtifactFilesForCapture(capture, artifactPathPrefix),
650642
summary: {
651643
steps: stepRecords.length,
652644
consoleMessages: consoleMessages.length,
@@ -709,6 +701,32 @@ export async function runEditorOpenCommand({
709701
}
710702
}
711703

704+
export function editorOpenArtifactPathPrefixFromArgs(args: string[]): string {
705+
const rawPrefix = argValue(args, "artifact-prefix")?.trim()
706+
if (!rawPrefix) {
707+
return "files/browser"
708+
}
709+
710+
const prefix = rawPrefix.replace(/\\/g, "/").replace(/\/+/g, "/").replace(/\/$/, "")
711+
if (!prefix || prefix.startsWith("/") || prefix.split("/").includes("..")) {
712+
throw new Error(`wordpress.editor-open artifact-prefix must be a relative artifact directory without traversal: ${rawPrefix}`)
713+
}
714+
return prefix
715+
}
716+
717+
export function editorOpenArtifactFilesForCapture(capture: ReadonlySet<string>, artifactPathPrefix = "files/browser"): BrowserArtifactFiles & { summary: string } {
718+
return {
719+
...(capture.has("steps") ? { steps: `${artifactPathPrefix}/editor-steps.jsonl` } : {}),
720+
...(capture.has("console") ? { console: `${artifactPathPrefix}/editor-console.jsonl` } : {}),
721+
...(capture.has("editor-state") ? { editorState: `${artifactPathPrefix}/editor-state.json` } : {}),
722+
...(capture.has("editor-validity") ? { editorValidity: `${artifactPathPrefix}/editor-validity.json` } : {}),
723+
...(capture.has("errors") ? { errors: `${artifactPathPrefix}/editor-errors.jsonl` } : {}),
724+
...(capture.has("html") ? { html: `${artifactPathPrefix}/editor-snapshot.html` } : {}),
725+
...(capture.has("screenshot") ? { screenshot: `${artifactPathPrefix}/editor-screenshot.png` } : {}),
726+
summary: `${artifactPathPrefix}/editor-summary.json`,
727+
}
728+
}
729+
712730
async function waitForEditorOpenCanvasReadiness(page: import("playwright").Page, waitSelector: string, timeoutMs: number): Promise<BrowserEditorCanvasProbeSummary | undefined> {
713731
if (!waitSelector.includes("editor-canvas")) {
714732
return undefined

tests/editor-actions.test.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import assert from "node:assert/strict"
2-
import { captureEditorValidity } from "../packages/runtime-playground/src/editor-command-runners.js"
2+
import { captureEditorValidity, editorOpenArtifactFilesForCapture, editorOpenArtifactPathPrefixFromArgs } from "../packages/runtime-playground/src/editor-command-runners.js"
33
import { editorActionStepsFromArgs, editorOpenTargetFromArgs, resolveEditorOpenTarget } from "../packages/runtime-playground/src/editor-actions.js"
44

55
const steps = await editorActionStepsFromArgs([
@@ -87,4 +87,31 @@ const passthrough = await resolveEditorOpenTarget(postTarget, {
8787
})
8888
assert.equal(passthrough.url, "/wp-admin/post.php?post=12&action=edit")
8989

90+
// editor-open remains backward-compatible by default, but can namespace every
91+
// artifact path for per-fixture batch evidence.
92+
assert.equal(editorOpenArtifactPathPrefixFromArgs([]), "files/browser")
93+
assert.equal(
94+
editorOpenArtifactPathPrefixFromArgs(["artifact-prefix=files/browser/editor-open/coffee-shop"]),
95+
"files/browser/editor-open/coffee-shop",
96+
)
97+
assert.deepEqual(editorOpenArtifactFilesForCapture(new Set(["steps", "console", "errors", "html", "screenshot", "editor-state", "editor-validity"])), {
98+
steps: "files/browser/editor-steps.jsonl",
99+
console: "files/browser/editor-console.jsonl",
100+
errors: "files/browser/editor-errors.jsonl",
101+
html: "files/browser/editor-snapshot.html",
102+
screenshot: "files/browser/editor-screenshot.png",
103+
editorState: "files/browser/editor-state.json",
104+
editorValidity: "files/browser/editor-validity.json",
105+
summary: "files/browser/editor-summary.json",
106+
})
107+
assert.deepEqual(editorOpenArtifactFilesForCapture(new Set(["screenshot", "editor-state"]), "files/browser/editor-open/coffee-shop"), {
108+
screenshot: "files/browser/editor-open/coffee-shop/editor-screenshot.png",
109+
editorState: "files/browser/editor-open/coffee-shop/editor-state.json",
110+
summary: "files/browser/editor-open/coffee-shop/editor-summary.json",
111+
})
112+
assert.throws(
113+
() => editorOpenArtifactPathPrefixFromArgs(["artifact-prefix=files/browser/../escape"]),
114+
/relative artifact directory/,
115+
)
116+
90117
console.log("editor actions ok")

0 commit comments

Comments
 (0)