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.
<pre lang="actionscript3" line="1"><?xml version="1.0" encoding="utf-8"??>
<application applicationcomplete="init()" backgroundgradientalphas="[1.0, 1.0]" backgroundgradientcolors="[#17043B, #000000]" layout="vertical" verticalalign="middle" xmlns:mx="http://www.adobe.com/2006/mxml">
<script>
<p>Only thing of interest here is the latches Object. Seem Flex doesn't have associated arrays as a separate type, so Object it is.</p>
<pre lang="actionscript3" line="16"> 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 ++;
}
<p>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).</p>
<p>Also you need the ExternalInterface line to actually expose the things to the outside world.</p>
<pre lang="actionscript" line="28"> // 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;
}
<p>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?)</p>
<pre lang="actionscript" line="35"> 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";
}
<p>This function is that gets called when the button is pushed. Notice the random duration of the pause.</p>
<pre lang="actionscript" line="44"> private function changeImage(e:TimerEvent):void {
setLatchStatus("slideshow", "changing");
message.text = "Latch status: " + "changing";
img.load("images/" + pictures[index]);
if(index
<p>Is the actual image change. Pay close attention to the latch status setting before <i>anything</i> happens and it being changed as the final action.</p>
<pre lang="actionscript" line="55"> ]]>
</script>
<image id="img"></image>
<button click="timedImageChange()" id="button" label="Next"></button>
<text id="message">
<text>Latch status: </text>
</text>
</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.
<pre lang="php">/**
* @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)