FlexMonkey 5.1.1 – Automation Spark DataGrid with Custom GridItemRenderers

1 03 2012

In my previous blog posts I have discussed how to actually get flexmonkey to automate a spark DataGrid, but the problem was, if you had any custom GridItemRenderers inside of it, suddenly it didn’t know how to properly automate them. I made a simple spark data grid and filled it with data, and made a custom item renderer that renders a red box for ‘false’ and a green box for ‘true’, pretty simple:

So what happens if you click on one of the rows while recording in FlexMonkey (the AIR app)?

Notice how it essentially copies down the entire row of data, and puts asterisks around the actual row you clicked? In the first one, we clicked the 3rd row, in the “price” column, so it put asterisks around the the $350  to let the automation framework know that that was the one that was clicked. But, in the second one, there is nothing there for the “popular” column, its just an empty string! if you try to replay this now, it won’t work, since it doesn’t know how to represent the custom GridItemRenderer we are using. The other columns work fine since they don’t use a custom renderer, so they use a built in class DefaultGridItemRenderer.

So how do we get our code for our custom GridItemRenderer to identify itself correctly to the automation framework? I did a lot of tracing in what goes on behind the scenes since, as per usual, there is almost no documentation on the FlexMonkey website. It turns out, there is a property that is inherited from UIComponent called automationValue, that basically represents the ‘value’ that can be used to identify this GridItemRenderer in the automation framework. In our case, since our custom GridItemRenderer’s data is only a boolean, it makes sense to just have it return the boolean state of the row’s “popular” field.

So we just override the getter for the automationValue property:

override public function get automationValue():Array {

return [theState];

}

and when we run the app again, and try recording the value with the FlexMonkey AIR app:

Now it recognizes the GridItemRenderer, and if you play the events back, its able to correctly find what row/column you selected and play it back.

I have posted the example code for this here, if you are interested in taking a look at it. It contains the FlexMonkey source code, the libraries that are needed to build said source code, and the automation libraries that adobe provides the source for. HOWEVER, you still need to link to one more swc file (which adobe doesn’t give the source for), you do this by right clicking the project -> properties -> flex build path -> add swc, and this swc is located in your FlexSDK folder. For me, its located in:

/…/flexsdk/flex_sdk_4.5.1.21328/frameworks/libs/automation/automation_agent.swc

once you link that, you should be good. Let me know if you have any questions.





How to automate spark DataGrid with FlexMonkey 5.1.1

16 02 2012

After googling and searching, it turns out that the flexmonkey developers are currently leaving everyone hanging high and dry who want to automate a spark data grid. They say here: http://www.gorillalogic.com/forumpost/1991 that it would be available in the next release …in the first quarter of 2012. Flex 4.5 has been out since may of 2011. It really is taking them 7+ months to add support for the spark data grid?

Whatever, this isn’t about my beef with the flexmonkey people (which could be a novel), but instead, i spent the past couple weeks dealing with their lack of documentation and lack of support for the spark data grid, however, i do believe I have fixed it.

To automate your spark data grids, try putting this inside your FlexMonkeyEnv.xml file:

 http://pastebin.com/5TtQNGCp

This will work fine if you have no Item Renderers in your data grid, however, experimenting with it using a data grid that has custom item renderers, it doesn’t quite work, since I believe there are no entries in the xml file to tell the automation framework / flexmonkey to get the “automation name” of your item renderer, and conversely no way to get the automation name and turn it into something that flexmonkey can replay. More on this as I figure it out.





FlexMonkeyEnv.xml and Flex’s URLUtil.getFullUrl() woes

6 02 2012

So, I have been using FlexMonkey (an UI automation framework) , and one of the parts of it is editing a file called FlexMonkeyEnv.xml, which defines what events to listen for on each class, so you can play them back later (such as recording the “click” event on a button, or a certain key press). You are supposed to take the default FlexMonkeyEnv.xml file, edit it, and put it at the same folder location as your main mxml file, so when it compiles its at the same level as your .swf . You can see that it both embeds the file  and sends a HTTPService call  for the same file (why it does both, i have no idea),  just FlexMonkeyEnv.xml which means the same directory the swf is in.

[Embed(source="FlexMonkeyEnv.xml", mimeType="application/octet-stream")]
protected const FlexMonkeyEnv:Class;
// misc code here....
var httpService:HTTPService = new HTTPService();
httpService.url = "FlexMonkeyEnv.xml";
httpService.resultFormat = "e4x";
httpService.addEventListener(ResultEvent.RESULT, envResultHandler, false, 0, true);
httpService.addEventListener(FaultEvent.FAULT, envFaultHandler, false, 0, true);
httpService.send();

However, the project I’m working on is doing things a bit differently. It is interacting with a Groovy on Grails server thats running on localhost. So the server is serving content out of a certain folder (web-app), so the swf needs to be in this directory rather then the bin-debug directory that it gets placed when it compiles. So we just set up a symlink in the web-app folder so that it links to the most recent compiled swf in bin-debug

dhcp-10-134-232-191:web-app markgrandi$ ls -la
<other files>
lrwxr-xr-x 1 markgrandi staff 45 Oct 6 13:30 tos.swf -> ../../workspace/to-frontend/bin-debug/tos.swf

We also have a custom url set up so when flashbuilder compiles the app, we set a URL in the debug configurations so it launches this url rather then invoking a web browser pointing to the swf in the bin-debug folder (or just launch the flash player debugger program manually), the url looks like this:

http://localhost:8080/to/tos.swf?mode=login&dbm=t&lpw=someone@example.com/showthis&rf=0

So when you run the swf, you use the FlexMonkey AIR app to ‘connect’ to your code, and from the app, you can view the Environment file, and in the window that it opens, it shows the contents of the file, and whether you were using the default file or not. No matter how many changes I made, it said I was using the default file still.

This baffled me for a wrong time. What was I doing wrong? Flexmonkey sadly doesn’t log anything so I had no idea what was going on, and the FlexMonkeyEnv.xml was in the correct place, at the same folder level as my main mxml file.

Turns out that problem 1 was, I wasn’t actually “running” the swf from the bin-debug folder (which is where the the FlexMonkeyEnv.xml file was getting copied when the application was compiled). I was running it from the web-app directory that gets called by the Groovy on Grails server when flash builder invokes the url that i set it to in the debug/launch configurations. So the solution was very simple, simply symlink the FlexMonkeyEnv.xml file in the web-app folder like I did with the swf, and everything should work, right?

dhcp-10-134-232-191:web-app markgrandi$ ls -la
lrwxr-xr-x 1 markgrandi staff 55 Jan 27 15:08 FlexMonkeyEnv.xml -> ../../workspace/to-frontend/bin-debug/FlexMonkeyEnv.xml
lrwxr-xr-x 1 markgrandi staff 45 Oct 6 13:30 tos.swf -> ../../workspace/to-frontend/bin-debug/tos.swf

Wrong. I tried running the app again, and FlexMonkey STILL was saying that it was using the default xml file. I decided at this point to actually go inside the source code (you need the source code first, not just the swc obviously). I searched for “FlexMonkeyEnv.xml” and found the file AQAdapter.as (which is where the above code snippets came from as well), which handled loading the FlexMonkeyEnv.xml file. The setTestingEnvironment method is where it makes the HTTPService call to just “FlexMonkeyEnv.xml”, and two event listeners for success and failure. I added a trace statement to both, printing out the events, and sure enough, when I ran it, it called the failure event listener and printed out this:

envfile: USING DEFAULT ENV, REASON: [FaultEvent fault=[RPC Fault faultString="Error #1090: XML parser failure: element is malformed." faultCode="Client.CouldNotDecode" faultDetail="null"] messageId="389F72DA-1D24-AE78-23C4-55105174BFC6" type="fault" bubbles=false cancelable=true eventPhase=2]

What? the xml file is malformed? That can’t be right. I ran the xml file through lxml, a python api over gnome’s libxml, and see if it complained that the xml was malformed:

dhcp-10-134-232-191:~ markgrandi$ python3
Python 3.2.2 (v3.2.2:137e45f15c0b, Sep 3 2011, 17:28:59)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import lxml
>>> import lxml.etree as etree
>>> x = etree.parse("/Users/markgrandi/Desktop/tmp.xml")
>>> x
<lxml.etree._ElementTree object at 0x101461950>

Well obviously if lxml parsed it fine, it couldn’t be malformed. I decided to set a breakpoint on the httpService.send() call, to see what Flex was doing and see what it was failing on, maybe get a line number of the xml or something.

I eventually came to the class AbstractOperation.as, and it had this code in it:

if (urlToUse && urlToUse != '') {

urlToUse = URLUtil.getFullURL(rootURL, urlToUse);

}

Pretty straight forward. Its trying to take the relative URL FlexMonkey gave it (just FlexMonkeyEnv.xml) and turn it into a full url. When this completed and i saw the value urlToUse, I immediately saw where my bug was. The url that URLUtil.getFullUrl() gave back was:

http://localhost:8080/to/tos.swf?mode=login&dbm=t&lpw=whm@cs.arizona.edu/FlexMonkeyEnv.xml

Whoa there, that doesn’t seem right! why did it stick the “/FlexMonkeyEnv.xml” AFTER the url query parameters? Its obviously getting the first part from the url of the swf, which is retrieved from LoaderInfo.url, another Adobe class. That returns the url that I’m having FlashBuilder launch when I debug the program, so that seems right, and the second part is just “FlexMonkeyEnv.xml”, also correct. But just the ‘joining’ of these two urls just seems wrong.

I wanted to be sure so i copy/pasted this into a web browser, and sure enough it just loaded the swf, and not the xml file. So thats why it was giving out the “xml is malformed’ error, because a swf is obviously not an xml document.  To be sure that what FlexMonkey (and I ) was asking it to do was actually reasonable, I again opened python and used the urljoin() method of urllib.parse to see what it would give me:

>>> import urllib.parse
>>> x = urllib.parse.urljoin("http://localhost:8080/to/tos.swf?mode=login&dbm=t&lpw=something@example.com", "FlexMonkeyEnv.xml")
>>> x

http://localhost:8080/to/FlexMonkeyEnv.xml

Now THAT seems right. To verify, I paste this into a web browser and sure enough it displays the xml file as it should.  It seems that URLUtil.getFullUrl() doesn’t understand the concept of query parameters and just tries to add things to the end. Looking at the documentation:

Converts a potentially relative URL to a fully-qualified URL. If the URL is not relative, it is returned as is. 
If the URL starts with a slash, the host and port from the root URL are prepended. 
Otherwise, the host, port, and path are prepended.
@param rootURL URL used to resolve the URL specified by the url parameter, if url is relative.
@param url URL to convert.

I tried adding a ‘/’ to the front of the url that FlexMonkey is trying to load, so it becomes   httpService.url = “/FlexMonkeyEnv.xml” . I run it again, and see that getFullUrl now returns:

http://localhost:8080/FlexMonkeyEnv.xml

This is still wrong, now its completely removing the entire path, including the /to/ directory which is what the Groovy on Grails server is serving content out of. To finally fix this, I have to make FlexMonkey load “/to/FlexMonkeyEnv.xml”, so now the line is: httpService.url = “/to/FlexMonkeyEnv.xml”, with a result of:

http://localhost:8080/to/FlexMonkeyEnv.xml

Hooray. Now that it works, and to verify, I run the app, and the FlexMonkey  AIR app now reports that we are NOT using the default FlexMonkeyEnv.xml file. Pretty frustrating when stupid things like this prevent you from working, especially when they are pretty silly bugs in Adobe’s framework code, but I guess its satisfying when you do figure it out and fix it. Hopefully this will save someone an embarrassingly long amount of time trying to figure out why that xml file isn’t being loaded.





FlexMonkey 5.1.1: how to actually compile flexmonkey

26 01 2012

So, I recently started learning how to use FlexMonkey in order to bring some sort of UI Automation to the project I am working on for work, but debugging problems in it can be a hassle when you only have a magical “swc” file, which means you can’t view the source code when you are debugging. Highly annoying! So this post talks about how to actually compile flexmonkey into your application.

For this, you will need a couple of things. Flash builder for one, I am using 4.6,  and subversion to actually download the flex monkey source code

First off, for whatever reason, they require you to have a gorillamonkey.com account in order to download the source code, so go here: http://www.gorillalogic.com/user/register and register for an account.

Then, fire up a terminal and cd to whatever directory you want to put the code into (temporarily) , and type

svn export https://svn.gorillalogic.com/svn/flexmonkey/trunk

This will check out the latest revision and NOT leave the stupid .svn folders laying around everywhere. So open up the trunk folder, and inside you should see the MonkeyLink folder. Inside this folder, there are 3 folders, libs, js and src. You should merge the contents of this folder with your own project code, so now you have some stuff in /src, /js and /libs .

Second part, you need to right click on your project in the navigation tree on the left, go to properties -> flex compiler, and copy/paste this line after anything else that is there:

-include-libraries "${flexlib}/libs/automation/automation_spark.swc" "${flexlib}/libs/automation/automation.swc" "${flexlib}/libs/automation/automation_agent.swc" "${flexlib}/libs/automation/automation_dmv.swc"

Then, you need some libraries that Flexmonkey compiles against. These are the ones that worked for me, you might need specific versions if you are using flex 3 or flex 4 (mostly the Flexunit libraries differs between flex 3/4). You should be able to drop these in the ‘libs’ folder of your flex project and Flash Builder will automatically link them.

  • as3commons-bytecode-0.8.5.swc
  • as3commons-lang-0.3.1.swc
  • as3commons-reflect-1.3.1.swc
  • as3commons-logging-1.1.1.swc
  • flexunit-4.1.0_RC2-4-flex_4.1.0.16076.swc
  • flexunit_aircilistener-4.1.0_RC2-4.4.1.0.16076.swc
  • flexunit-cilistener-4.1.0_RC2-4-4.1.0.16076.swc
  • hamcrest-as3-1.1.0.swc

Now, to avoid Gotcha #1,  you need to actually REFERENCE the code before it will compile it. This one is annoying because you don’t actually have to reference anything in the monkeylink/flexmonkey code to make it work, since it initializes it self using the [mixin] metadata tag, but flex in it’s infinite wisdom chooses not to compile things unless they are referenced. So go to your <fx:script> tag in your main mxml file or somewhere, and just auto complete a variable (start by typing var something:Monkey… and then hit ctrl + space so flex will automatically import it for you) and then recompile. Note, you need to actually create a monkeylink object, just having the import and a undefined variable (var link:monkeylink;) is not enough, you need to do ‘new MonkeyLink()”. We should be good now right?

Wrong! I have _no_ clue what they are doing, but they what appears to be preprocessing instructions in their actionscript code, except for the fact that as3 doesn’t support them, so it gets interpreted as valid AS3 and you get wonderful errors like “Types are ambiguous, both mx.core.application and spark.components.application exist” and duplicate function definitions. So, in order to fix this, we need to get rid of all these preprocessor statements and as3 code that is meant for Flex 3 (since I am using flex 4). These are the rough locations (as of revision 652) of what i had to remove:

  • removed duplicate functions ( keep code in the else block of the if else, and then delete the if..else ) in MonkeyMagicAutomator.as line 200
  • kept code in if block in ApplicationWrapper.as, deleted if..else line 19
  • kept code in if block in MonkeyAutomationManager.as, deleted if..else line 33
  • got rid of if flex4 block in MonkeyMagicAutomator.as line 18 (kept code inside)
  • kept code in if block, deleted if..else in MonkeyLink.as line 131
  • got rid of if block (kept code inside) in MonkeyLink.as line 40
  • got rid of flex4 block (but kept code inside) in MagicMonkeyAutomationDelegate.as like 413
  • kept code in if block , got rid of if.else in MonkeyAutomationManager.as line 54

Now, if you got all of them, the code SHOULD compile. and that is it! when you start up your application, SystemManager will call the flexmonkey code (because of the [mixin] metadata tag) to do the necessary startup that it does, and you should be able to connect to it via the FlexMonkey air app. I hope this helped!





How to uninstall XCode4 from Mac OS X Lion

21 07 2011

So i had an old version of XCode on my computer, and i tried upgrading it with the new mac app store, and it didn’t work. I wanted to uninstall it and then reinstall it using the app store, but they of course don’t have an option to UNINSTALL apps using the app store

Google tells me to just delete the app and then you can click ‘install’ again on the purchases tab in the app store, but since this is xcode, you can’t just drag an application to the trash. You have to run a command using Terminal. So, open terminal and type:

sudo /Developer/Library/uninstall-devtools –mode=all

The directory MIGHT be different, but for me it installed it to /Developer. Anyway, it should list all the packages and then say it deleted them. I restarted my mac just to be safe.

THEN, if it STILL says that xcode is installed, you need to delete the installer from /Applications (different from the Applications folder in your home folder). Go Macintosh HD/Applications and there should be a file called “Install Xcode” and then you can delete that folder, and the ‘install’ button will again be enabled, allowing you to install it from the mac app store and get on with your life





Image File Location on iPod Touch

20 06 2011

I wanted to get the photos off my iPod that i had downloaded using the “save image” option in Opera Mini. They appeared if i opened the Photos app and were under Saved Photos. Since there doesn’t seem to be a way to sync photos FROM the ipod TO the pc, i used a program called iPhone Explorer (mac) to explore the filey system on my ipod

The images are not located in a folder called “Photos”, which i find silly. They are located in: /Media/DCIM/100APPLE/

then all the image files are in there.





Compiling PyQt4 on Mac OS X 10.6 (Snow Leopard)

17 06 2011

So i recently started messing around with pyqt4 and python, and i wanted it to work on my mac, but to my dismay i had to compile it from source.

So, here is how i did it. You will need some things

You will probably need XCode so you get access to gcc and other things. You can get it from here: http://developer.apple.com/xcode/ (you either need to pay 5 dollars for xcode 4 or look for a link on that page saying “looking for xcode 3?” which is free.)

First, download and install the QT SDK here: http://qt.nokia.com/downloads I would put it somewhere in your home folder, I put it in ~/Programs/QtSDK

Now, you need to download the source tarballs (the Mac OSX / Linux ones) for PyQT4 and SIP (which is used to generate the python bindings for the c++ code in qt). Get them here:  http://www.riverbankcomputing.com/software/sip/download and http://www.riverbankcomputing.com/software/pyqt/download . I created a folder pyqtstuff to put the source tarballs in. Extract them both and you should have two folders for each of the tarballs.

You need to compile SIP first. Now Mac OSX has python 2.6 already installed, if you want to use python 3 you need to install that. Whatever version of python you want to use pyqt4 with, you NEED to run the following scripts with the version of python you want to use. So if you want to use 2.6, you just use ‘python configure.py’, if python 3, then ‘python3 configure.py .

So, change directories to the sp folder and then run these commands.

python configure.py

make

sudo make install

If all goes well, then it wont report any errors.

Now you change directories to the pyqt folder, and do the same thing, making sure to run the configure.py script as the version of python you are going to use with pyqt. BUT , i had a problem where it was saying that ‘qmake’ was not in my path. Never fear, thats why you downloaded the QT SDK. You need to locate the folder where you installed it, and deep inside it is the qmake binary. I installed the sdk to the QtSDK folder, and my qmake was in :

QtSDK/Desktop/Qt/473/gcc/bin/qmake

The version number will probably differ, but other then that the path is the same. Now, make sure you copy the FULL PATH (from root, so for me it would be  /Users/<username>/Programs/QtSDK/Desktop/Qt/473/gcc/bin/qmake , but substitute this with your own) and you need to pass it as an argument to the configure.py call. Again run this with the version of python you intend to use!

python configure.py -g -q <PATH TO QMAKE HERE>

That will configure pyqt. We use the -g because that ‘consolidates’ everything, which i’m not sure what that means, but the other option it told me to use did not work.

Now you just compile it and install it. This will probably take a bit of time since its kinda big. Be patient

make

sudo make install

Done! now you should be able to use PyQt4 on your mac. Hopefully this helps someone since I did not find a guide for it online yet. Post here if you have any questions!








Follow

Get every new post delivered to your Inbox.