@@ -555,3 +555,59 @@ export async function contextMenuExists(
555555 const item = await browser . $ ( `div=${ itemText } ` ) ;
556556 return await item . waitForExist ( { timeout : 200 , reverse : reverse } ) ;
557557}
558+
559+ /**
560+ * Find a clickable element on the block and click it.
561+ * We can't always use the block's SVG root because clicking will always happen
562+ * in the middle of the block's bounds (including children) by default, which
563+ * causes problems if it has holes (e.g. statement inputs). Instead, this tries
564+ * to get the first text field on the block. It falls back on the block's SVG root.
565+ *
566+ * @param browser The active WebdriverIO Browser object.
567+ * @param blockId The id of the block to click, as an interactable element.
568+ * @param clickOptions The options to pass to webdriverio's element.click function.
569+ * @return A Promise that resolves when the actions are completed.
570+ */
571+ export async function clickBlock (
572+ browser : WebdriverIO . Browser ,
573+ blockId : string ,
574+ clickOptions ?: Partial < webdriverio . ClickOptions > | undefined ,
575+ ) {
576+ const findableId = 'clickTargetElement' ;
577+ // In the browser context, find the element that we want and give it a findable ID.
578+ await browser . execute (
579+ ( blockId , newElemId ) => {
580+ const ws = Blockly . getMainWorkspace ( ) as Blockly . WorkspaceSvg ;
581+ const block = ws . getBlockById ( blockId ) as Blockly . BlockSvg ;
582+ // Ensure the block we want to click is within the viewport.
583+ ws . scrollBoundsIntoView ( block . getBoundingRectangleWithoutChildren ( ) , 10 ) ;
584+ if ( ! block . isCollapsed ( ) ) {
585+ for ( const input of block . inputList ) {
586+ for ( const field of input . fieldRow ) {
587+ if ( field instanceof Blockly . FieldLabel ) {
588+ const svgRoot = field . getSvgRoot ( ) ;
589+ if ( svgRoot ) {
590+ svgRoot . id = newElemId ;
591+ return ;
592+ }
593+ }
594+ }
595+ }
596+ }
597+ // No label field found. Fall back to the block's SVG root.
598+ block . getSvgRoot ( ) . id = newElemId ;
599+ } ,
600+ blockId ,
601+ findableId ,
602+ ) ;
603+
604+ // In the test context, get the Webdriverio Element that we've identified.
605+ const elem = await browser . $ ( `#${ findableId } ` ) ;
606+
607+ await elem . click ( clickOptions ) ;
608+
609+ // In the browser context, remove the ID.
610+ await browser . execute ( ( elemId ) => {
611+ document . getElementById ( elemId ) ?. removeAttribute ( 'id' ) ;
612+ } , findableId ) ;
613+ }
0 commit comments