Scripting an elaborate user interface scenario

We often show screenshots of Glamorous Toolkit in our communication. For example, consider this one:

This screenshot contains an elaborate scene with three panes. In the first pane, we have a Coder on the GtLudoGame Object subclass: #GtLudoGame instanceVariableNames: 'players squares startSquares goalSquares die announcer feedback winner needToRollDie lastDieRolled playerQueue routeCache' classVariableNames: '' package: 'GToolkit-Demo-Ludo-Model' class, in which we have expanded GtLudoGame>>#moveTokenNamed: moveTokenNamed: aTokenName ^ self moveToken: (self tokenNamed: aTokenName) method and in that method we expanded a message. Then we looked for references by pressing a shortcut and this produced a second pane with a filter tool. In that filter tool we scrolled down to GtLudoRecordingGameExamples>>#gameShowingAllMoves2 gameShowingAllMoves2 <gtExample> | game gameMoves | game := self gameShowingAllMoves1. gameMoves := game moves size. game roll: 6; moveTokenNamed: 'D'. self assert: game moves size - gameMoves = 1. self assert: game moves last numberOfTokensMoved = 1. ^ game , a method that happens to be an example (which is like a test that returns an object). We expanded the method and we executed the example by clicking on the button from the contextual toolbar of the method coder. This then produced the third pane containing an inspector with the result. And a little detail: as the mouse hovered over the button there is also a tooltip associated with that button.

These actions can be produced manually. But a proper graphical stack should allow us to simulate manual interactions programmatically, too. In Glamorous Toolkit we can.

For example, the script that produces a scene like in the picture from above is found in GtTour>>#scripterWithElaborateScenario scripterWithElaborateScenario <gtExample> | scripter pageWidth moveTokenMethod allMovesMethod | scripter := BlScripter new. scripter do block: [ :aSpace | aSpace extent: 1600 @ 870 ]; onSpace; play. moveTokenMethod := GtLudoGame >> #moveTokenNamed:. scripter element: (GtPager createWrappedOn: (GtCoder forMethod: moveTokenMethod)). pageWidth := scripter space width / 3. scripter do block: [ :aPage | aPage width: pageWidth ]; // (GtPagerPageElementId indexed: 1); play. scripter methodCoders forCompiledMethod: moveTokenMethod do: [ :aMethodCoderStep | aMethodCoderStep shortcut combination: BlKeyCombination primaryN; // GtSourceCoderEditorId; onChildAt: 1; onChildAt: 1. aMethodCoderStep clickOnMethodCoderExpander: 1 insideDo: [ :aStep1 | ] ]; // (GtPagerPageElementId indexed: 1); // GtPharoStreamingMethodsCoderElement; play. scripter do block: [ :aPage | aPage width: pageWidth ]; // (GtPagerPageElementId indexed: 2); play. allMovesMethod := GtLudoRecordingGameExamples >> #gameShowingAllMoves2. scripter methodCoders expandAndFocusCompiledMethod: allMovesMethod; scrollToCompiledMethod: allMovesMethod; forCompiledMethod: allMovesMethod do: [ :aMethodCoderStep | aMethodCoderStep clickOnPlayAndInspectExampleButton ]; // (GtPagerPageElementId indexed: 2); // GtPharoStreamingMethodsCoderElement; play. scripter do block: [ :aPage | aPage width: pageWidth ]; // (GtPagerPageElementId indexed: 3); play. scripter methodCoders forCompiledMethod: allMovesMethod do: [ :aMethodCoderStep | aMethodCoderStep clickOnMethodCoderExpander: 1 insideDo: [ :aStep1 | ] ]; scrollToCompiledMethod: allMovesMethod; forCompiledMethod: allMovesMethod do: [ :aMethodCoderStep | aMethodCoderStep mouseMoveOver // GtMethodCoderPlayAndInspectExampleActionId; onTopMost ]; // (GtPagerPageElementId indexed: 2); // GtPharoStreamingMethodsCoderElement; play. ^ scripter . The script makes use of BlScripter Object subclass: #BlScripter uses: TBlDevScripterActionStep + TBlDevScripterCheckStepCreation instanceVariableNames: 'element space events rootStep eventHandler maxPulseElapsedTime' classVariableNames: '' package: 'Bloc-Scripter-Scripter' , a scripting engine for the graphical stack. Scripter offers an an API that provides default abilities for searching for elements and for simulating actions (such as click or shortcut). The API is also extensible with higher level queries, such as methodCoder which gives us more concise abilities to work with a tool like Coder. For example, we can ask quite concisely a methodCoder to clickOnPlayAndInspectExampleButton.

The above script is quite elaborate. But the good news is that you can build it iteratively. Here is how it looks like in the editor that we used to write this very article. Executing the code from the snippet simply shows an inspector on the BlScripter Object subclass: #BlScripter uses: TBlDevScripterActionStep + TBlDevScripterCheckStepCreation instanceVariableNames: 'element space events rootStep eventHandler maxPulseElapsedTime' classVariableNames: '' package: 'Bloc-Scripter-Scripter' instance. And this inspector shows multiple dedicated views, one of which is the preview of the resulting scene.