@@ -172,43 +172,52 @@ export async function getBlockElementById(browser, id) {
172172 * @return A Promise that resolves when the actions are completed.
173173 */
174174export async function clickBlock ( browser , blockId , clickOptions ) {
175- const findableId = 'clickTargetElement' ;
176175 // In the browser context, find the element that we want and give it a findable ID.
177- await browser . execute (
178- ( blockId , newElemId ) => {
179- const block = Blockly . getMainWorkspace ( ) . getBlockById ( blockId ) ;
180- // Ensure the block we want to click is within the viewport.
181- Blockly . getMainWorkspace ( ) . scrollBoundsIntoView (
182- block . getBoundingRectangleWithoutChildren ( ) ,
183- 10 ,
184- ) ;
176+ const elem = await getTargetableBlockElement ( browser , blockId , false ) ;
177+ await elem . click ( clickOptions ) ;
178+ }
179+
180+ /**
181+ * Find an element on the block that is suitable for a click or drag.
182+ *
183+ * We can't always use the block's SVG root because clicking will always happen
184+ * in the middle of the block's bounds (including children) by default, which
185+ * causes problems if it has holes (e.g. statement inputs). Instead, this tries
186+ * to get the first text field on the block. It falls back on the block's SVG root.
187+ * @param browser The active WebdriverIO Browser object.
188+ * @param blockId The id of the block to click, as an interactable element.
189+ * @param toolbox True if this block is in the toolbox (which must be open already).
190+ * @return A Promise that returns an appropriate element.
191+ */
192+ async function getTargetableBlockElement ( browser , blockId , toolbox ) {
193+ const id = await browser . execute (
194+ ( blockId , toolbox , newElemId ) => {
195+ const ws = toolbox
196+ ? Blockly . getMainWorkspace ( ) . getFlyout ( ) . getWorkspace ( )
197+ : Blockly . getMainWorkspace ( ) ;
198+ const block = ws . getBlockById ( blockId ) ;
199+ // Ensure the block we want to click/drag is within the viewport.
200+ ws . scrollBoundsIntoView ( block . getBoundingRectangleWithoutChildren ( ) , 10 ) ;
185201 if ( ! block . isCollapsed ( ) ) {
186202 for ( const input of block . inputList ) {
187203 for ( const field of input . fieldRow ) {
188204 if ( field instanceof Blockly . FieldLabel ) {
189- field . getSvgRoot ( ) . id = newElemId ;
190- return ;
205+ // Expose the id of the element we want to target
206+ field . getSvgRoot ( ) . setAttribute ( 'data-id' , field . id_ ) ;
207+ return field . getSvgRoot ( ) . id ;
191208 }
192209 }
193210 }
194211 }
195- // No label field found. Fall back to the block's SVG root.
196- block . getSvgRoot ( ) . id = newElemId ;
212+ // No label field found. Fall back to the block's SVG root, which should
213+ // already use the block id.
214+ return block . id ;
197215 } ,
198216 blockId ,
199- findableId ,
217+ toolbox ,
200218 ) ;
201219
202- // In the test context, get the Webdriverio Element that we've identified.
203- const elem = await browser . $ ( `#${ findableId } ` ) ;
204-
205- await elem . click ( clickOptions ) ;
206-
207- // In the browser context, remove the ID.
208- await browser . execute ( ( elemId ) => {
209- const clickElem = document . getElementById ( elemId ) ;
210- clickElem . removeAttribute ( 'id' ) ;
211- } , findableId ) ;
220+ return await getBlockElementById ( browser , id ) ;
212221}
213222
214223/**
@@ -255,27 +264,14 @@ export async function getCategory(browser, categoryName) {
255264}
256265
257266/**
258- * @param browser The active WebdriverIO Browser object.
259- * @param categoryName The name of the toolbox category to search.
260- * @param n Which block to select, 0-indexed from the top of the category.
261- * @return A Promise that resolves to the root element of the nth
262- * block in the given category.
263- */
264- export async function getNthBlockOfCategory ( browser , categoryName , n ) {
265- const category = await getCategory ( browser , categoryName ) ;
266- await category . click ( ) ;
267- const block = (
268- await browser . $$ ( `.blocklyFlyout .blocklyBlockCanvas > .blocklyDraggable` )
269- ) [ n ] ;
270- return block ;
271- }
272-
273- /**
267+ * Opens the specified category, finds the first block of the given type,
268+ * scrolls it into view, and returns a draggable element on that block.
269+ *
274270 * @param browser The active WebdriverIO Browser object.
275271 * @param categoryName The name of the toolbox category to search.
276272 * Null if the toolbox has no categories (simple).
277273 * @param blockType The type of the block to search for.
278- * @return A Promise that resolves to the root element of the first
274+ * @return A Promise that resolves to a draggable element of the first
279275 * block with the given type in the given category.
280276 */
281277export async function getBlockTypeFromCategory (
@@ -290,61 +286,12 @@ export async function getBlockTypeFromCategory(
290286
291287 await browser . pause ( PAUSE_TIME ) ;
292288 const id = await browser . execute ( ( blockType ) => {
293- return Blockly . getMainWorkspace ( )
294- . getFlyout ( )
295- . getWorkspace ( )
296- . getBlocksByType ( blockType ) [ 0 ] . id ;
289+ const ws = Blockly . getMainWorkspace ( ) . getFlyout ( ) . getWorkspace ( ) ;
290+ const block = ws . getBlocksByType ( blockType ) [ 0 ] ;
291+ ws . scrollBoundsIntoView ( block . getBoundingRectangleWithoutChildren ( ) ) ;
292+ return block . id ;
297293 } , blockType ) ;
298- return getBlockElementById ( browser , id ) ;
299- }
300-
301- /**
302- * @param browser The active WebdriverIO Browser object.
303- * @param categoryName The name of the toolbox category to search.
304- * Null if the toolbox has no categories (simple).
305- * @param blockType The type of the block to search for.
306- * @return A Promise that resolves to a reasonable drag target element of the
307- * first block with the given type in the given category.
308- */
309- export async function getDraggableBlockElementByType (
310- browser ,
311- categoryName ,
312- blockType ,
313- ) {
314- if ( categoryName ) {
315- const category = await getCategory ( browser , categoryName ) ;
316- await category . click ( ) ;
317- }
318-
319- const findableId = 'dragTargetElement' ;
320- // In the browser context, find the element that we want and give it a findable ID.
321- await browser . execute (
322- ( blockType , newElemId ) => {
323- const block = Blockly . getMainWorkspace ( )
324- . getFlyout ( )
325- . getWorkspace ( )
326- . getBlocksByType ( blockType ) [ 0 ] ;
327- if ( ! block . isCollapsed ( ) ) {
328- for ( const input of block . inputList ) {
329- for ( const field of input . fieldRow ) {
330- if ( field instanceof Blockly . FieldLabel ) {
331- const svgRoot = field . getSvgRoot ( ) ;
332- if ( svgRoot ) {
333- svgRoot . id = newElemId ;
334- return ;
335- }
336- }
337- }
338- }
339- }
340- // No label field found. Fall back to the block's SVG root.
341- block . getSvgRoot ( ) . id = newElemId ;
342- } ,
343- blockType ,
344- findableId ,
345- ) ;
346- // In the test context, get the Webdriverio Element that we've identified.
347- return await browser . $ ( `#${ findableId } ` ) ;
294+ return getTargetableBlockElement ( browser , id , true ) ;
348295}
349296
350297/**
@@ -499,10 +446,16 @@ export async function switchRTL(browser) {
499446 * created block.
500447 */
501448export async function dragNthBlockFromFlyout ( browser , categoryName , n , x , y ) {
502- const flyoutBlock = await getNthBlockOfCategory ( browser , categoryName , n ) ;
503- while ( ! ( await elementInBounds ( browser , flyoutBlock ) ) ) {
504- await scrollFlyout ( browser , 0 , 50 ) ;
505- }
449+ const category = await getCategory ( browser , categoryName ) ;
450+ await category . click ( ) ;
451+
452+ await browser . pause ( PAUSE_TIME ) ;
453+ const id = await browser . execute ( ( n ) => {
454+ const ws = Blockly . getMainWorkspace ( ) . getFlyout ( ) . getWorkspace ( ) ;
455+ const block = ws . getTopBlocks ( true ) [ n ] ;
456+ return block . id ;
457+ } , n ) ;
458+ const flyoutBlock = await getTargetableBlockElement ( browser , id , true ) ;
506459 await flyoutBlock . dragAndDrop ( { x : x , y : y } ) ;
507460 return await getSelectedBlockElement ( browser ) ;
508461}
@@ -529,44 +482,16 @@ export async function dragBlockTypeFromFlyout(
529482 x ,
530483 y ,
531484) {
532- const flyoutBlock = await getDraggableBlockElementByType (
485+ const flyoutBlock = await getBlockTypeFromCategory (
533486 browser ,
534487 categoryName ,
535488 type ,
536489 ) ;
537- while ( ! ( await elementInBounds ( browser , flyoutBlock ) ) ) {
538- await scrollFlyout ( browser , 0 , 50 ) ;
539- }
540490 await flyoutBlock . dragAndDrop ( { x : x , y : y } ) ;
541491 await browser . pause ( PAUSE_TIME ) ;
542492 return await getSelectedBlockElement ( browser ) ;
543493}
544494
545- /**
546- * Check whether an element is fully inside the bounds of the Blockly div. You can use this
547- * to determine whether a block on the workspace or flyout is inside the Blockly div.
548- * This does not check whether there are other Blockly elements (such as a toolbox or
549- * flyout) on top of the element. A partially visible block is considered out of bounds.
550- * @param browser The active WebdriverIO Browser object.
551- * @param element The element to look for.
552- * @returns A Promise resolving to true if the element is in bounds and false otherwise.
553- */
554- async function elementInBounds ( browser , element ) {
555- return await browser . execute ( ( elem ) => {
556- const rect = elem . getBoundingClientRect ( ) ;
557-
558- const blocklyDiv = document . getElementsByClassName ( 'blocklySvg' ) [ 0 ] ;
559- const blocklyRect = blocklyDiv . getBoundingClientRect ( ) ;
560-
561- const vertInView =
562- rect . top >= blocklyRect . top && rect . bottom <= blocklyRect . bottom ;
563- const horInView =
564- rect . left >= blocklyRect . left && rect . right <= blocklyRect . right ;
565-
566- return vertInView && horInView ;
567- } , element ) ;
568- }
569-
570495/**
571496 * Drags the specified block type from the mutator flyout of the given block
572497 * and returns the root element of the block.
@@ -667,27 +592,4 @@ export async function getAllBlocks(browser) {
667592 id : block . id ,
668593 } ) ) ;
669594 } ) ;
670- }
671-
672- /**
673- * Find the flyout's scrollbar and scroll by the specified amount.
674- * This makes several assumptions:
675- * - A flyout with a valid scrollbar exists, is open, and is in view.
676- * - The workspace has a trash can, which means it has a second (hidden) flyout.
677- * @param browser The active WebdriverIO Browser object.
678- * @param xDelta How far to drag the flyout in the x direction. Positive is right.
679- * @param yDelta How far to drag the flyout in the y direction. Positive is down.
680- * @return A Promise that resolves when the actions are completed.
681- */
682- export async function scrollFlyout ( browser , xDelta , yDelta ) {
683- // There are two flyouts on the playground workspace: one for the trash can
684- // and one for the toolbox. We want the second one.
685- // This assumes there is only one scrollbar handle in the flyout, but it could
686- // be either horizontal or vertical.
687- await browser . pause ( PAUSE_TIME ) ;
688- const scrollbarHandle = await browser
689- . $$ ( `.blocklyFlyoutScrollbar` ) [ 1 ]
690- . $ ( `rect.blocklyScrollbarHandle` ) ;
691- await scrollbarHandle . dragAndDrop ( { x : xDelta , y : yDelta } ) ;
692- await browser . pause ( PAUSE_TIME ) ;
693- }
595+ }
0 commit comments