Friday, February 22, 2008

FunFX and custom components


Hi, I have for a while now been supposed build a custom component with custom events and post how to to this here.

I am eventually finshed an example of how to create a custom component, which is not possible to act upon with the regular actions from FunFX.

(By the way, I followed this tutorial, Create custom component Flex, it is for QTP, but are in many ways the same way to create a custom component for FunFX.

Thanks alot to Jarek for pointing out the problem I had. I used the wrong variable in the call to the super class' constructor. I used a couple of hours yesterday banging my head in to the wall. But after the help from Jarek everything fell into place.

It is actually very easy to implement an custom component, and use it with FunFX. What you need to do is 4 steps:

1. Create the custom component

2. Create the custom event

3. Create the delegate that extends for instance UIComponentAutomationImpl

4. Add the component to the AutomationGenericEnv.xml file


(Does not need to do them in that order)


Create the custom component


I chose to create a simple component listing different buttons, which could easily be done with a repeater, but that is not the point. I wanted to find out and hopefully share my findings with you guys, that haven't tried it yet. What I wanted to do was to implement my own event, so that I could use the itemrenderer tag in the event to make the buttons be pushed.

Below is the code for the custom component. It just extends from a existing component and overrides the necessary methods like, createChildren etc. What is important to notice here is that you need to add you children, that you want to reach into the _renderers so that they can be used by the delagate as automationChildren. The top event sentence makes it possible for the delegate to replay that customevent, which I will create after this.


[Event(name="itemClick", type="event.CustomItemClickEvent")]
public class CustomComponent extends VBox
{
private var _dataProvider:Array;
private var _renderers:Array = [];
private var _itemRenderer:IFactory = new ClassFactory(Button);

public function CustomComponent()
{
super();
}

override protected function createChildren():void{
super.createChildren();
for(var i:int=0; i<_dataProvider.length; i++){
var inst:UIComponent = _itemRenderer.newInstance();
inst.addEventListener(MouseEvent.CLICK, itemClickHandler);
this.addChild(inst);
_renderers.push(inst);

Button(inst).label = _dataProvider[i];
}
invalidateSize();
invalidateDisplayList();
}
//You must also implement an itemClickHandler, for the buttons, but that is nothing special
}


Create the custom event


To be able to click on the button, I need to create a custom event that makes this happen. This event holds a button which is the type of item the custom component is holding. It is probably possible to do this a bit more generic, but this was just to understand how things works.


public class CustomItemClickEvent extends Event
{
public var itemRenderer:Button;
public static const ITEM_CLICK:String = "itemClick";

public function CustomItemClickEvent(type:String){
super(type, bubbles, cancelable);
}
}


Create the delegate


The delegate must be included to the application project that uses the custom component. I chose to build an swc of the delegate and add it as the FUnFXAdapter.swc file.

Below is the code from the delegate. It is important to use the [Mixin] attribute, as this forces the delegate's init methd to be run when your application starts. The numAutomationChild and getAutomationChild methods you will have to implement concerning how your custom component added items to the renders array. Here you can do what you want.


[Mixin]
public class CustomComponentDelegate extends UIComponentAutomationImpl
{
public static function init(root:DisplayObject):void
{
Automation.registerDelegateClass(CustomComponent, CustomComponentDelegate);
}

private var comp:CustomComponent;
public function CustomComponentDelegate(component:CustomComponent)
{
super(component);
comp = component;
}

override public function get numAutomationChildren():int
{
var renderers:Array = comp.getItemRenderers();
if(renderers != null)
return renderers.length;
else return 0;
}

override public function getAutomationChildAt(index:int):IAutomationObject
{
var renderers:Array = comp.getItemRenderers();
if(renderers == null) return null;
if(index >= 0 && index < renderers.length)
return renderers[index];
else
return null;
}

override public function replayAutomatableEvent(event:Event):Boolean
{
var help:IAutomationObjectHelper = Automation.automationObjectHelper;
if (event is CustomItemClickEvent)
{
var rEvent:CustomItemClickEvent = event as CustomItemClickEvent
help.replayClick(rEvent.itemRenderer);
(uiComponent as IInvalidating).validateNow();
return true;
}
else
return super.replayAutomatableEvent(event);
}


Add the component to the AutomationGenericEnv.xml file


When everything is created you can add the component to the AutomationGenericEnv.xml file. Below is what I added somewhere in the file below the Object (because it is based on inheritance and the file is parsed.)


<ClassInfo Name="CustomComponent" Extends="Object" SupportsTabularData="true">
<Implementation Class="CustomComponent"/>
<Events>
<Event Name="ItemClick">
<Implementation Class="customEvent::CustomItemClickEvent" Type="itemClick"/>
<Property Name="itemRenderer">
<PropertyType Type="String" />
</Property>
</Event>
</Events>
<Properties>
</Properties>
</ClassInfo>


Building a test application and writing a test


Now you are ready to write an application using the component and a test which interacts with the component.


<Application="" mx="http://www.adobe.com/2006/mxml" component="component.*">
<mx:VBox>
<component:CustomComponent="" id="cust" dataprovider="['Nr1','Nr2']"/>
</mx:VBox>
</mx:Application>



@ie.custom_component("cust").item_click(:item_renderer => "Nr2")



I hope this was helpfull, and possible to understand :-)

Do not forget to read the post about RSpec and Stories that I wrote just before this post. Thanks!

15 comments:

Imrahil said...

You have an error in CustomComponentDelegate constructor. There should be:
super(component) - not super(comp)

Jarek

ps. if you encounter any further problems I have good, working example :)

Peter Motzfeldt said...

You are absolutely right, cannot understand how I missed that. Thank you very much!

Imrahil said...

3 things:
- custom component which extends vbox isn't good example, because VBox component extends Container component which has good numAutomationChildren method. UIComponent has UIComponentAutomationImpl has numAutomationChildren which always returns zero... This is really challenge :)

- I don't see why one would use custom events. when I have all childrens in custom component, I can click on it, use it, etc...

- you have an error in flex.rb -
in method "getChildren" should be flex_object.checkComponent(@id, name) not flex_object.checkComponents(@id, name)

Imrahil said...

and 4, last thing :)

you lost getItemRenderers() function from CustomComponent class..

best regards,
Jarek

Peter Motzfeldt said...

You might be absolutely correct, but I just wanted to show how to add custom components to the xml file, and in that way interact with the button in another way than just @ie.button("name").click. It was an very simple example on how to do it if you have more complicated custom componets.

But I might be wrong. Thanks for the pointers on this theme.

An thanks for pointing out these errors, I will try to fix tehm tomorrow.

- Peter

Unknown said...

Congratulations peter and jarek for uncovering this functionality of FunFX. I have been trying to implement the code that u have posted but for some reason i have not been able to make it work (im a newbie at flex so pardon my ignorance :D ). i was thinking if u could post the project files itself so i can just download then and run them without having to write custom event listeners e.t.c.

-
Zesh

Peter Motzfeldt said...

Sure thing Zesh, I will do that. I have these files at work, so will do it tomorrow. I will add a link here when it is done.

Peter Motzfeldt said...

Now I have added the custom component project to SVN, but I have also added a zip file containing the project at CustomComponentFunFX.zip

stemlaur said...

Hello, my company seemed interrested by Fonctional Testing for our huge application, but I would like to have your advise about the fact to integrate a testing Framework for a huge application that is already in production.


The fact that the test that FunFX allows to write are non-trivial, and then it's impossible for the client to write them.
It means that it would be the developper (it's me) would have to write them by himself. And it means thousand and thousand line of ruby.

Don't you think that in my case, the manual test would be preferable ?

Thank you for listening, and sorry for the bad English Writting.

Laurent (the french guy)

Peter Motzfeldt said...

Hi Laurent

I have not to much experience testing huge production systems. But at the project I am working on now I am trying to use FunFX to make myself sure that when I change that piece, the other parts of the application does not break.

My advice with FunFX tests, is to build them as small as possible, do not assert every aspect of a page, but write maybe a few tests that walk trough the application. When you test manually, find out if there are some steps you are doing over and over again, and then implement them as functional tests.

It is difficult to say I you should do it maunally or with tests. Both are timeconsuming, but in different ends I think I you get to build good functional tests. With manual testing you uncover the initial bugs fast, but after correcting these bugs you will need to start over again, but with test scripts you can let them run over night, and easily discover if the fix introduced new bugs.

But I must also add that FunFX is still early in production, but it is by using and feedback from users we are able to build it better. So there might be things you would like FunFX to do when you are creating scripts, that it still does not support. But 90% of the basic flex operations are supported.

I hope this was of any help Laurent.

Asher said...

my company built an application front end of which is written in flex 2.0. I want to use your FunFX tool but you mention somewhere that funfx test need to be run inside the application.

Does your tool work like QTP? In QTP, I enter the test website. The tool then records my mouse clicks and navigations.

Doesn't your tool work like that? I have a feeling that I have to write Ruby code which I don't know.

Peter Motzfeldt said...

Hi Asher. I you want to use FunFX to test the application, you will need to write ruby scripts. But they are very simple ruby scripts.

You can use FunFX to test any Flex application, you will only need to build a new swf file that includes the FunFXAdapter.swc, automation.swc and automation_agent.swc files.

Let me know if you need any more pinters.

Unknown said...

hi Peter,

I am new to automation. I have installed funFx, Ruby and eclipse on my local machine. Please guide me how to configure eclipse to write ruby. And how should I start automating a flex application lying on another server using that configuration with FunFx.

Unknown said...

Hi Peter,
I have trouble closing the Title Window by invoking the close event. Following is the code:
@ie.title_window('Popup').close()
Please tell me how to solve this problem. I dont have an explicit close button on the title window.
Thanks,
Harsh

Peter Motzfeldt said...

Hmm, I am not sure how we can get this close thing to work without a button. FunFX is based on interactions like a mouse. I will look into it, I am also going to look into keyboard actions soon.