@@ -28,6 +28,14 @@ const EDITOR_VALIDITY_WARNING_SELECTORS = [
2828 ".components-notice" ,
2929]
3030
31+ export function editorCommandWordPressUrl ( server : PlaygroundCliServer ) : string {
32+ return server . wordpressUrl ?? server . serverUrl
33+ }
34+
35+ function editorCommandPreviewTopology ( args : string [ ] , runtimeSpec : RuntimeCreateSpec , server : PlaygroundCliServer ) {
36+ return browserPreviewTopology ( [ "preview-mode=local" , ...args ] , runtimeSpec , editorCommandWordPressUrl ( server ) )
37+ }
38+
3139export async function runEditorCanvasProbeCommand ( {
3240 artifactRoot,
3341 runtimeSpec,
@@ -520,7 +528,7 @@ export async function runEditorOpenCommand({
520528 }
521529
522530 const waitTimeoutMs = durationArg ( args , "wait-timeout" , BROWSER_STEP_DEFAULT_TIMEOUT_MS )
523- const topology = browserPreviewTopology ( args , runtimeSpec , server . serverUrl )
531+ const topology = editorCommandPreviewTopology ( args , runtimeSpec , server )
524532 const { preview, networkPolicy } = topology
525533 const targetUrl = topology . resolveUrl ( target . url )
526534 const artifactPathPrefix = editorOpenArtifactPathPrefixFromArgs ( args )
@@ -803,7 +811,7 @@ export async function runEditorActionsCommand({
803811 const waitTimeoutMs = durationArg ( args , "wait-timeout" , BROWSER_STEP_DEFAULT_TIMEOUT_MS )
804812 const stepTimeoutMs = durationArg ( args , "step-timeout" , BROWSER_STEP_DEFAULT_TIMEOUT_MS )
805813 const totalTimeoutMs = durationArg ( args , "timeout" , BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS )
806- const topology = browserPreviewTopology ( args , runtimeSpec , server . serverUrl )
814+ const topology = editorCommandPreviewTopology ( args , runtimeSpec , server )
807815 const { preview, networkPolicy } = topology
808816 const targetUrl = topology . resolveUrl ( target . url )
809817 const artifactSession = new BrowserArtifactSession ( artifactRoot , "files/browser" , { source : "wordpress.editor-actions" , operation : "editor-actions" } )
@@ -1171,11 +1179,14 @@ async function executeEditorBlockMutation(page: import("playwright").Page, step:
11711179 let siblings = blocks
11721180 let block : Block | undefined
11731181 if ( typeof input . clientId === "string" ) {
1174- const locate = ( items : Block [ ] , parent ?: string ) : boolean => items . some ( ( item ) => {
1175- if ( item . clientId === input . clientId ) { block = item ; parentClientId = parent ; siblings = items ; return true }
1176- return locate ( item . innerBlocks ?? [ ] , item . clientId )
1177- } )
1178- locate ( blocks )
1182+ const pending : Array < { items : Block [ ] ; parent ?: string } > = [ { items : blocks } ]
1183+ while ( ! block && pending . length > 0 ) {
1184+ const current = pending . pop ( ) !
1185+ for ( const item of current . items ) {
1186+ if ( item . clientId === input . clientId ) { block = item ; parentClientId = current . parent ; siblings = current . items ; break }
1187+ if ( item . innerBlocks ?. length ) pending . push ( { items : item . innerBlocks , parent : item . clientId } )
1188+ }
1189+ }
11791190 } else {
11801191 const path = input . path ?? [ input . index ?? - 1 ]
11811192 for ( const position of path ) {
@@ -1192,22 +1203,33 @@ async function executeEditorBlockMutation(page: import("playwright").Page, step:
11921203 }
11931204 }
11941205 if ( ! block ?. clientId ) throw new Error ( "wp-codebox-editor-target-not-found: clientId, index, or path did not resolve a block" )
1195- const requireAction = ( name : string ) : ( ( ...args : unknown [ ] ) => unknown ) => {
1196- const action = actions [ name ]
1197- if ( typeof action !== "function" ) throw new Error ( `wp-codebox-editor-${ input . kind } -unsupported: core/block-editor.${ name } is unavailable` )
1198- return action as ( ...args : unknown [ ] ) => unknown
1199- }
1200- const create = ( spec : { name : string ; attributes ?: Record < string , unknown > ; innerBlocks ?: unknown [ ] } ) : unknown => {
1201- if ( typeof wp ?. blocks ?. createBlock !== "function" ) throw new Error ( "wp-codebox-editor-create-block-unsupported: wp.blocks.createBlock is unavailable" )
1202- return wp . blocks . createBlock ( spec . name , spec . attributes ?? { } , ( spec . innerBlocks ?? [ ] ) . map ( ( child ) => create ( child as typeof spec ) ) )
1203- }
1204- if ( input . kind === "selectBlock" ) return void requireAction ( "selectBlock" ) ( block . clientId )
1205- if ( input . kind === "updateBlockAttributes" ) return void requireAction ( "updateBlockAttributes" ) ( block . clientId , input . attributes )
1206- if ( input . kind === "removeBlock" ) return void requireAction ( "removeBlocks" ) ( [ block . clientId ] )
1207- if ( input . kind === "duplicateBlock" ) return void requireAction ( "duplicateBlocks" ) ( [ block . clientId ] )
1208- if ( input . kind === "moveBlock" ) return void requireAction ( "moveBlocksToPosition" ) ( [ block . clientId ] , parentClientId , parentClientId , input . position )
1209- if ( input . kind === "replaceBlock" ) return void requireAction ( "replaceBlock" ) ( block . clientId , create ( input . block ) )
1210- if ( input . kind === "replaceInnerBlocks" ) return void requireAction ( "replaceInnerBlocks" ) ( block . clientId , input . blocks . map ( create ) )
1206+ let actionName : string = input . kind
1207+ let actionArgs : unknown [ ] = [ block . clientId ]
1208+ if ( input . kind === "updateBlockAttributes" ) actionArgs . push ( input . attributes )
1209+ if ( input . kind === "removeBlock" ) { actionName = "removeBlocks" ; actionArgs = [ [ block . clientId ] ] }
1210+ if ( input . kind === "duplicateBlock" ) { actionName = "duplicateBlocks" ; actionArgs = [ [ block . clientId ] ] }
1211+ if ( input . kind === "moveBlock" ) { actionName = "moveBlocksToPosition" ; actionArgs = [ [ block . clientId ] , parentClientId , parentClientId , input . position ] }
1212+ if ( input . kind === "replaceBlock" || input . kind === "replaceInnerBlocks" ) {
1213+ const createBlock = wp ?. blocks ?. createBlock
1214+ if ( typeof createBlock !== "function" ) throw new Error ( "wp-codebox-editor-create-block-unsupported: wp.blocks.createBlock is unavailable" )
1215+ const specs = input . kind === "replaceBlock" ? [ input . block ] : input . blocks
1216+ const created = new Map < object , unknown > ( )
1217+ const pending = specs . map ( ( spec ) => ( { spec, visited : false } ) )
1218+ while ( pending . length > 0 ) {
1219+ const current = pending . pop ( ) !
1220+ if ( ! current . visited ) {
1221+ pending . push ( { spec : current . spec , visited : true } )
1222+ for ( const child of current . spec . innerBlocks ?? [ ] ) pending . push ( { spec : child , visited : false } )
1223+ continue
1224+ }
1225+ created . set ( current . spec , createBlock ( current . spec . name , current . spec . attributes ?? { } , ( current . spec . innerBlocks ?? [ ] ) . map ( ( child ) => created . get ( child ) ) ) )
1226+ }
1227+ actionName = input . kind
1228+ actionArgs = input . kind === "replaceBlock" ? [ block . clientId , created . get ( input . block ) ] : [ block . clientId , input . blocks . map ( ( spec ) => created . get ( spec ) ) ]
1229+ }
1230+ const action = actions [ actionName ]
1231+ if ( typeof action !== "function" ) throw new Error ( `wp-codebox-editor-${ input . kind } -unsupported: core/block-editor.${ actionName } is unavailable` )
1232+ action ( ...actionArgs )
12111233 } , step )
12121234}
12131235
@@ -1286,7 +1308,7 @@ async function waitForEditorSemanticReadiness(page: import("playwright").Page, t
12861308}
12871309
12881310async function saveEditorPost ( page : import ( "playwright" ) . Page , step : Extract < EditorActionStep , { kind : "savePost" } > , timeoutMs : number ) : Promise < BrowserEditorSaveSummary > {
1289- const save = await page . evaluate ( async ( input ) => {
1311+ await page . evaluate ( async ( input ) => {
12901312 const win = window as unknown as {
12911313 wp ?: {
12921314 blocks ?: { createBlock ?: ( name : string , attributes ?: Record < string , unknown > ) => unknown }
@@ -1316,31 +1338,19 @@ async function saveEditorPost(page: import("playwright").Page, step: Extract<Edi
13161338 blockEditor . insertBlocks ( [ createBlock ( "core/paragraph" , { content : input . content ?? input . marker } ) ] )
13171339 }
13181340 await Promise . resolve ( editorDispatch . savePost ( ) )
1319- const deadline = Date . now ( ) + input . timeoutMs
1320- await new Promise < void > ( ( resolve , reject ) => {
1321- const done = ( ) => {
1322- const isSaving = typeof editor . isSavingPost === "function" ? Boolean ( editor . isSavingPost ( ) ) : false
1323- const didSucceed = typeof editor . didPostSaveRequestSucceed === "function" ? Boolean ( editor . didPostSaveRequestSucceed ( ) ) : undefined
1324- const didFail = typeof editor . didPostSaveRequestFail === "function" ? Boolean ( editor . didPostSaveRequestFail ( ) ) : false
1325- if ( ! isSaving && didSucceed !== false ) {
1326- cleanup ( )
1327- resolve ( )
1328- } else if ( didFail ) {
1329- cleanup ( )
1330- reject ( new Error ( "wp-codebox-editor-save-failed: core/editor savePost reported a failed request" ) )
1331- } else if ( Date . now ( ) > deadline ) {
1332- cleanup ( )
1333- reject ( new Error ( "wp-codebox-editor-save-timeout: timed out waiting for core/editor savePost to settle" ) )
1334- }
1335- }
1336- const unsubscribe = typeof wpData ?. subscribe === "function" ? wpData . subscribe ( done ) : undefined
1337- const interval = window . setInterval ( done , 250 )
1338- const cleanup = ( ) => {
1339- window . clearInterval ( interval )
1340- if ( unsubscribe ) unsubscribe ( )
1341- }
1342- done ( )
1343- } )
1341+ } , { marker : step . marker , content : step . content } )
1342+ const settled = await page . waitForFunction ( ( ) => {
1343+ const editor = ( window as unknown as { wp ?: { data ?: { select ?: ( store : string ) => Record < string , unknown > } } } ) . wp ?. data ?. select ?.( "core/editor" )
1344+ if ( ! editor ) return false
1345+ const saving = typeof editor . isSavingPost === "function" ? Boolean ( editor . isSavingPost ( ) ) : false
1346+ const failed = typeof editor . didPostSaveRequestFail === "function" ? Boolean ( editor . didPostSaveRequestFail ( ) ) : false
1347+ const succeeded = typeof editor . didPostSaveRequestSucceed === "function" ? Boolean ( editor . didPostSaveRequestSucceed ( ) ) : undefined
1348+ return ! saving && ( failed || succeeded !== false ) ? { failed } : false
1349+ } , undefined , { timeout : timeoutMs } ) . then ( ( handle ) => handle . jsonValue ( ) as Promise < { failed : boolean } > )
1350+ if ( settled . failed ) throw new Error ( "wp-codebox-editor-save-failed: core/editor savePost reported a failed request" )
1351+ const save = await page . evaluate ( ( input ) => {
1352+ const editor = ( window as unknown as { wp ?: { data ?: { select ?: ( store : string ) => Record < string , unknown > } } } ) . wp ?. data ?. select ?.( "core/editor" )
1353+ if ( ! editor ) throw new Error ( "wp-codebox-editor-readiness-unavailable: core/editor store is unavailable" )
13441354 const editedContent = typeof editor . getEditedPostContent === "function" ? String ( editor . getEditedPostContent ( ) ?? "" ) : ""
13451355 return {
13461356 schema : "wp-codebox/editor-save/v1" ,
@@ -1351,7 +1361,7 @@ async function saveEditorPost(page: import("playwright").Page, step: Extract<Edi
13511361 markerPresent : input . marker ? editedContent . includes ( input . marker ) : undefined ,
13521362 content : editedContent ,
13531363 }
1354- } , { marker : step . marker , content : step . content , timeoutMs } )
1364+ } , { marker : step . marker } )
13551365
13561366 const { content, ...summary } = save as BrowserEditorSaveSummary & { content ?: string }
13571367 return {
@@ -1446,13 +1456,22 @@ export async function captureEditorState(page: import("playwright").Page, target
14461456 const currentPost = typeof editor . getCurrentPost === "function" ? editor . getCurrentPost ( ) as Record < string , unknown > | null : null
14471457 const blocks = typeof blockEditor . getBlocks === "function" ? blockEditor . getBlocks ( ) as Array < Record < string , unknown > > : [ ]
14481458 const serialize = ( window as unknown as { wp ?: { blocks ?: { serialize ?: ( items : unknown [ ] ) => string } } } ) . wp ?. blocks ?. serialize
1449- const toTree = ( items : Array < Record < string , unknown > > ) : NonNullable < EditorStateSnapshot [ "blocks" ] > => items . map ( ( block ) => ( {
1450- name : typeof block . name === "string" ? block . name : "" ,
1451- clientId : typeof block . clientId === "string" ? block . clientId : undefined ,
1452- attributes : typeof block . attributes === "object" && block . attributes ? block . attributes as Record < string , unknown > : undefined ,
1453- isValid : typeof block . isValid === "boolean" ? block . isValid : undefined ,
1454- innerBlocks : Array . isArray ( block . innerBlocks ) ? toTree ( block . innerBlocks as Array < Record < string , unknown > > ) : undefined ,
1455- } ) )
1459+ const tree : NonNullable < EditorStateSnapshot [ "blocks" ] > = [ ]
1460+ const pending : Array < { items : Array < Record < string , unknown > > ; output : NonNullable < EditorStateSnapshot [ "blocks" ] > } > = [ { items : blocks , output : tree } ]
1461+ while ( pending . length > 0 ) {
1462+ const current = pending . pop ( ) !
1463+ for ( const block of current . items ) {
1464+ const innerBlocks : NonNullable < EditorStateSnapshot [ "blocks" ] > = [ ]
1465+ current . output . push ( {
1466+ name : typeof block . name === "string" ? block . name : "" ,
1467+ clientId : typeof block . clientId === "string" ? block . clientId : undefined ,
1468+ attributes : typeof block . attributes === "object" && block . attributes ? block . attributes as Record < string , unknown > : undefined ,
1469+ isValid : typeof block . isValid === "boolean" ? block . isValid : undefined ,
1470+ innerBlocks,
1471+ } )
1472+ if ( Array . isArray ( block . innerBlocks ) ) pending . push ( { items : block . innerBlocks as Array < Record < string , unknown > > , output : innerBlocks } )
1473+ }
1474+ }
14561475 const savedContent = typeof currentPost ?. content === "object" && currentPost . content
14571476 ? stringValue ( ( currentPost . content as Record < string , unknown > ) . raw ?? ( currentPost . content as Record < string , unknown > ) . rendered )
14581477 : undefined
@@ -1464,7 +1483,7 @@ export async function captureEditorState(page: import("playwright").Page, target
14641483 status : typeof currentPost ?. status === "string" ? currentPost . status : undefined ,
14651484 title : typeof currentPost ?. title === "object" && currentPost . title ? ( currentPost . title as Record < string , unknown > ) . raw ?? ( currentPost . title as Record < string , unknown > ) . rendered : undefined ,
14661485 } ,
1467- blocks : toTree ( blocks ) ,
1486+ blocks : tree ,
14681487 serializedContent : typeof serialize === "function" ? serialize ( blocks ) : typeof editor . getEditedPostContent === "function" ? String ( editor . getEditedPostContent ( ) ?? "" ) : undefined ,
14691488 dirty : typeof editor . isEditedPostDirty === "function" ? Boolean ( editor . isEditedPostDirty ( ) ) : undefined ,
14701489 saving : typeof editor . isSavingPost === "function" ? Boolean ( editor . isSavingPost ( ) ) : undefined ,
@@ -1910,7 +1929,7 @@ export async function runEditorValidateBlocksCommand({
19101929 const content = await editorValidateContentFromArgs ( args )
19111930 const provider = editorValidateProviderFromArgs ( args )
19121931 const waitTimeoutMs = durationArg ( args , "wait-timeout" , EDITOR_VALIDATE_BLOCKS_READY_TIMEOUT_MS )
1913- const topology = browserPreviewTopology ( args , runtimeSpec , server . serverUrl )
1932+ const topology = editorCommandPreviewTopology ( args , runtimeSpec , server )
19141933 const { preview, networkPolicy } = topology
19151934 const targetUrl = topology . resolveUrl ( target . url )
19161935 const artifactSession = new BrowserArtifactSession ( artifactRoot , "files/browser" , { source : "wordpress.editor-validate-blocks" , operation : "editor-validate-blocks" } )
0 commit comments