diff --git a/packages/runtime-core/src/command-registry.ts b/packages/runtime-core/src/command-registry.ts index 166c27b3..f1fbc169 100644 --- a/packages/runtime-core/src/command-registry.ts +++ b/packages/runtime-core/src/command-registry.ts @@ -1186,9 +1186,13 @@ export const commandRegistry = [ }, { id: "wordpress.editor-canvas-probe", - description: "Open a WordPress editor URL and wait for the iframe-backed block canvas to become visible, non-loading, and populated before capturing selector diagnostics and optional canvas screenshot evidence.", + description: "Open a WordPress editor target and wait for the iframe-backed block canvas to become visible, non-loading, and populated before capturing selector diagnostics and optional canvas screenshot evidence.", acceptedArgs: [ - { name: "url", description: "Editor path or absolute URL to visit.", required: true, format: "path or URL" }, + { name: "target", description: "Editor target to open; defaults to post-new. Use front-page to open the site's configured static front page.", format: "post-new|site|front-page" }, + { name: "post-id", description: "Existing post ID to open in the post editor.", format: "positive integer" }, + { name: "post-slug", description: "Existing post slug or hierarchical path to resolve and open in the post editor.", format: "post slug or path" }, + { name: "post-type", description: "Post type for post-new, post-id, or post-slug targets; defaults to post.", format: "post type slug" }, + { name: "url", description: "Explicit editor path or absolute URL to open instead of resolving a target.", format: "path or URL" }, { name: "iframe-selector", description: "Editor canvas iframe selector; defaults to iframe[name=\"editor-canvas\"].", format: "CSS selector" }, { name: "layout-selector", description: "Canvas layout selector inside the editor iframe; defaults to .block-editor-block-list__layout.", format: "CSS selector" }, { name: "block-selector", description: "Block selector inside the editor canvas layout; defaults to .block-editor-block-list__block, [data-block].", format: "CSS selector" }, diff --git a/packages/runtime-playground/src/editor-command-runners.ts b/packages/runtime-playground/src/editor-command-runners.ts index 12ffff36..ee4da194 100644 --- a/packages/runtime-playground/src/editor-command-runners.ts +++ b/packages/runtime-playground/src/editor-command-runners.ts @@ -10,7 +10,7 @@ import { browserPreviewCleanupErrorIsFatal, browserPreviewNetworkPolicyIsActive, import { browserCommandResult } from "./browser-result-sanitization.js" import { browserProbeReplayability, browserProbeViewport } from "./browser-probe.js" import { argValue, commaListArg, durationArg, jsonArrayArg } from "./commands.js" -import { DEFAULT_EDITOR_WAIT_SELECTOR, editorActionStepsFromArgs, editorOpenTargetFromArgs, editorValidateContentFromArgs, editorValidateProviderFromArgs, resolveEditorOpenTarget, type EditorActionStep, type EditorBlockSpec, type EditorBlockTarget, type EditorOpenTarget } from "./editor-actions.js" +import { DEFAULT_EDITOR_WAIT_SELECTOR, editorActionStepsFromArgs, editorOpenTargetFromArgs, editorValidateContentFromArgs, editorValidateProviderFromArgs, resolveEditorOpenTarget, type EditorActionStep, type EditorBlockSpec, type EditorBlockTarget, type EditorOpenTarget, type RunPlaygroundCommand } from "./editor-actions.js" import { assertPlaygroundResponseOk, attachPlaygroundDiagnostics, type PlaygroundRunResponse } from "./playground-command-errors.js" import type { PlaygroundCliServer } from "./preview-server.js" import { serializeBrowserError } from "./browser-metrics.js" @@ -49,20 +49,24 @@ function editorCommandPreviewTopology(args: string[], runtimeSpec: RuntimeCreate export async function runEditorCanvasProbeCommand({ artifactRoot, + runPlaygroundCommand, runtimeSpec, server, spec, }: { artifactRoot: string + runPlaygroundCommand: RunPlaygroundCommand runtimeSpec: RuntimeCreateSpec server: PlaygroundCliServer spec: ExecutionSpec }): Promise<{ artifact: BrowserArtifact; output: string }> { const args = spec.args ?? [] - const urlArg = argValue(args, "url")?.trim() - if (!urlArg) { - throw new Error("wordpress.editor-canvas-probe requires url=") - } + const target = await resolveEditorOpenTarget(editorOpenTargetFromArgs(args), { + command: "wordpress.editor-canvas-probe", + runPlaygroundCommand, + runtimeSpec, + server, + }) const capture = new Set(commaListArg(args, "capture")) for (const item of capture) { @@ -79,7 +83,7 @@ export async function runEditorCanvasProbeCommand({ const topology = browserPreviewTopology(args, runtimeSpec, server.serverUrl, server.previewProxyDiagnostics?.targetOrigin) const { preview, networkPolicy } = topology const previewOrigins = topology.origins - const targetUrl = topology.resolveUrl(urlArg) + const targetUrl = topology.resolveUrl(target.url) const artifactSession = new BrowserArtifactSession(artifactRoot, "files/browser", { source: "wordpress.editor-canvas-probe", operation: "editor-canvas-probe" }) const screenshotPath = artifactSession.absolutePath("editor-canvas-screenshot.png") const startedAt = now() diff --git a/packages/runtime-playground/src/playground-runtime.ts b/packages/runtime-playground/src/playground-runtime.ts index c260f0c0..f7de110b 100644 --- a/packages/runtime-playground/src/playground-runtime.ts +++ b/packages/runtime-playground/src/playground-runtime.ts @@ -929,7 +929,7 @@ class PlaygroundRuntime implements Runtime { const server = await this.bootPlayground() let result: Awaited> try { - result = await runEditorCanvasProbeCommand({ artifactRoot: this.artifactRoot, runtimeSpec: this.spec, server, spec }) + result = await runEditorCanvasProbeCommand({ artifactRoot: this.artifactRoot, runPlaygroundCommand: (command, targetServer, options) => this.runPlaygroundCommand(command, targetServer, options), runtimeSpec: this.spec, server, spec }) } catch (error) { if (isBrowserCommandArtifactError(error)) { this.browserProbes.push(error.artifact) diff --git a/tests/browser-routed-command-security.test.ts b/tests/browser-routed-command-security.test.ts index b60834b7..76508b72 100644 --- a/tests/browser-routed-command-security.test.ts +++ b/tests/browser-routed-command-security.test.ts @@ -78,7 +78,9 @@ test("editor presentation contract isolates framed JSON from PHP diagnostics", ( test("real browser commands sanitize console, artifacts, stdout, and failure stderr", async () => { const httpServer = createServer((request, response) => { response.setHeader("content-type", "text/html") - response.end(request.url?.includes("post=4") + response.end(request.url?.includes("post=7") + ? editorHtml + : request.url?.includes("post=4") ? delayedPostEditorHtml : request.url?.includes("post=2") ? missingPresentationEditorHtml @@ -137,7 +139,9 @@ test("real browser commands sanitize console, artifacts, stdout, and failure std const runPlaygroundCommand = async (command: string) => ({ text: command === "wordpress.editor-open.capture-presentation-contract" ? editorPresentationContractOutput({ identities: GROWING_PRESENTATION_IDENTITIES, complete: true }) - : "[]", + : command === "wordpress.editor-canvas-probe.resolve-front-page" + ? "7" + : "[]", exitCode: 0, }) @@ -399,9 +403,10 @@ test("real browser commands sanitize console, artifacts, stdout, and failure std await withTempDir("wp-codebox-real-editor-canvas-security-", async (artifactRoot) => { const result = await runEditorCanvasProbeCommand({ artifactRoot, + runPlaygroundCommand, runtimeSpec, server, - spec: { command: "wordpress.editor-canvas-probe", args: [`url=${PUBLIC_URL}`, "route-host=routed.test", "timeout=5s"] }, + spec: { command: "wordpress.editor-canvas-probe", args: ["target=front-page", "route-host=routed.test", "timeout=5s"] }, }) await assertCommandSurfacesSafe(result, artifactRoot, ["files/browser/editor-canvas-summary.json"]) }) diff --git a/tests/editor-actions.test.ts b/tests/editor-actions.test.ts index 40f2d37e..e1fa55a9 100644 --- a/tests/editor-actions.test.ts +++ b/tests/editor-actions.test.ts @@ -1,4 +1,5 @@ import assert from "node:assert/strict" +import { getCommandDefinition } from "../packages/runtime-core/src/command-registry.js" import { assertEditorMutationPostcondition, captureEditorIdleCanvas, captureEditorState, captureEditorValidity, editorCommandWordPressUrl, editorOpenArtifactError, editorOpenArtifactFilesForCapture, editorOpenArtifactPathPrefixFromArgs, executeEditorActionStep, summarizeEditorPresentation, type EditorStateSnapshot, waitForEditorOpenReadiness } from "../packages/runtime-playground/src/editor-command-runners.js" import { isBrowserCommandArtifactError } from "../packages/runtime-playground/src/browser-command-artifact-error.js" import { editorActionStepsFromArgs, editorOpenTargetFromArgs, resolveEditorOpenTarget } from "../packages/runtime-playground/src/editor-actions.js" @@ -394,6 +395,11 @@ assert.equal(nestedValidity.warnings[0]?.clientId, "nested-invalid") const frontPageTarget = editorOpenTargetFromArgs(["target=front-page"]) assert.equal(frontPageTarget.kind, "front-page") assert.equal(frontPageTarget.url, "") +const canvasProbeArgs = getCommandDefinition("wordpress.editor-canvas-probe")?.acceptedArgs ?? [] +for (const name of ["target", "post-id", "post-slug", "post-type", "url"]) { + assert.equal(canvasProbeArgs.some((arg) => arg.name === name), true, `editor-canvas-probe accepts ${name}`) +} +assert.equal(canvasProbeArgs.find((arg) => arg.name === "url")?.required, undefined) const editorRuntimeSpec = { wp: "latest", environment: {} } as never assert.equal(editorCommandWordPressUrl({ serverUrl: "http://preview.test", wordpressUrl: "http://wordpress.test" } as never), "http://wordpress.test") assert.equal(editorCommandWordPressUrl({ serverUrl: "http://wordpress.test" } as never), "http://wordpress.test")