Flex Latches

Sometimes waiting for an element or text on the page is insufficient for synchronization purposes. At that point, you need to look at a latch. There are examples floating around in various languages, but I had not yet seen one using Flex so I wrote one. Its just a simple little slideshow (that started as this but ended up something else) app, but with the added twist of when you push the next button it waits a random period of time (up to 8 seconds). And since its inside the black box of Flex we can’t just wait for the HTML to change. And do we have to modify our application to provide the necessary synchronization hooks.

Latches in Flex revolve around the ExternalInterface and its ability to expose things to JS.

Let’s rip apart my application for purposes of illustration.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
  layout="vertical"
  verticalAlign="middle"
  backgroundGradientAlphas="[1.0, 1.0]"
  backgroundGradientColors="[#17043B, #000000]"
  applicationComplete="init()">
 
<mx:Script>
  <![CDATA[
    // http://www.riacodes.com/flex/build-an-automatic-slideshow-with-flex/
    import mx.effects.Iris;
    import org.flex_pilot.FPBootstrap;
 
    private var pictures:Array = ["1.png", "2.png", "3.png", "4.png"];
    private var index:int = 0;
    private var latches:Object = new Object();
    private var duration:int = 0;

Only thing of interest here is the latches Object. Seem Flex doesn’t have associated arrays as a separate type, so Object it is.

16
17
18
19
20
21
22
23
24
25
26
27
28
29
    private function init():void {
      // flexpilot
      FPBootstrap.flex_pilotLibPath = '/FlexPilot.swf';
      message.text = String(stage);
      FPBootstrap.init(stage);
 
      // latch stuff
      ExternalInterface.addCallback("WebDriverLatch", getLatchStatus);
 
      img.setStyle("completeEffect", Iris);
 
      img.load("images/" + pictures[0]);
      index ++;
    }

A couple things of note, first is the loading of the FlextPilot library. You likely don’t want to have this leak into production (though, Zynga or Wooga, feel free to so I can farm while I sleep).

Also you need the ExternalInterface line to actually expose the things to the outside world.

28
29
30
31
32
33
34
35
    // what's called via the external interface 
    public function getLatchStatus(s:String):String {
       return latches[s];
    }
 
    public function setLatchStatus(l:String, v:String):void {
       latches[l] = v;
    }

Here are the actual latch methods. I suspect these could even be generic enough to copy-and-paste into all your Flex apps. The thing to note is that the latch name is hard coded. This is to prevent the creation of overly broad latches. You want them to be finite and small. Yes, this could mean on a complicated action you are checking the status of a handful of latches, but that’s ok. (You might want to write a method that does all the checking for you — a meta-latch?)

35
36
37
38
39
40
41
42
43
44
    private function timedImageChange():void {
      duration = Math.ceil(Math.random() * 8) * 1000;
 
      var timer:Timer = new Timer(duration, 1);
      timer.addEventListener(TimerEvent.TIMER, changeImage);
      timer.start();
 
      setLatchStatus("slideshow", "paused");
      message.text = "Latch status: " + "paused";
    }

This function is that gets called when the button is pushed. Notice the random duration of the pause.

44
45
46
47
48
49
50
51
52
53
54
55
56
    private function changeImage(e:TimerEvent):void {
      setLatchStatus("slideshow", "changing");
      message.text = "Latch status: " + "changing";
 
      img.load("images/" + pictures[index]);
      if(index < pictures.length -1)
        index++;
      else
        index=0;
 
      setLatchStatus("slideshow", "changed");
      message.text = "Latch status: " + "changed";
    }

Is the actual image change. Pay close attention to the latch status setting before anything happens and it being changed as the final action.

55
56
57
58
59
60
61
62
63
64
65
66
  ]]>
</mx:Script>
 
  <mx:Image id="img"/>
  <mx:Button id="button"
             label="Next"
             click="timedImageChange()"/>
  <mx:Text id="message">
      <mx:text>Latch status: </mx:text>
  </mx:Text>
 
</mx:Application>

Phew.

Now how do you deal with this from WebDriver? Well, the JS Executor. Learn the JS Executor. It is going to become the most important part of WebDriver with the rise of Canvas and JS-centric applications.

/**
* @group latch
*/
public function testPhotoChanging() {
    $chain = "id:button";
    self::$fp->click($chain);
    $w = new PHPWebDriver_WebDriverWait(self::$session, 10, 0.5, array("movie" => self::$fp->movie));
    $w->until(
        function($session, $extra_arguments) {
            $status = $session->execute(array(
                                          "script" => 'return arguments[0].WebDriverLatch("slideshow");',
                                          "args" => array(array("ELEMENT" => $extra_arguments["movie"]->getID()))
                                          )
                                      );
            if ($status == "changed") {
                return true;
            }
            return false;
        }
    );
}

See how our PHPWebDriver_WebDriverWait now checks not for an element or text on the page, but it pokes the browser for a JS value, and checks it. If it is what we want, then we proceed.

And that, ladies and gentlemen, is what latches are all about.

Oh. And here is the flexpilot-latches repo.

(And yes, I know there is something funky with the wp-syntax. It has something to do with displaying the line numbers and I’m not going to waste time tracking it down)

Post a Comment

Your email is never published nor shared. Required fields are marked *