How to Test ZK Application with Selenium

From Documentation

Hi,

first, nice to hear that unit-tests are possible with ZK. But my question: What happens when i add a new component (i.e. button, label..). I think the testunit became out of sync? Because id17 is now id18?

Maybe i've missed something, but this means: that i have to refactor the testclass for every small gui-change? --Robertpic 12:53, 16 February 2009 (UTC)


Hi,


no , you don't have to refactor the testclass, following is a simple process of component's ID.

YES -> create desktop and id start from zk-comp-1
YES --> New Desktop ?
NO -> get next ID (ex: zk-comp-17 => zk-comp-18)
  • New component ?
NO
)

/Ryan Wu


Great article, doesn't seem to work for me though

I have been waiting for a way to test ZK with Selenium for a long time, unfortunately I am having some issues getting this to work.

I'm using ZK with the Grails plugin (0.5), ZK 3.0.7

I have created a zk.xml and put it in my WEB-INF folder, along with the following class and interface:

<zk>
<system-config>
		<id-generator-class>com.marks.s2.zk.ComponentIdGenerator</id-generator-class>
</system-config>
</zk>


package com.marks.s2.zk;

import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Page;


public interface IdGenerator {
         
	public String nextComponentUuid(Desktop desktop, Component comp);
	public String nextPageUuid(Page page);
	public String nextDesktopId(Desktop desktop);

}


package com.marks.s2.zk;

import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Page;

public class ComponentIdGenerator implements IdGenerator{

   public void ComponentIdGenerator(){
       
   }


   public String nextComponentUuid(Desktop desktop, Component comp) {
		int i = Integer.parseInt(desktop.getAttribute("Id_Num").toString());
		i++;// Start from 1
		desktop.setAttribute("Id_Num", String.valueOf(i));
		return "zk-comp-" + i;
	}

	public String nextDesktopId(Desktop desktop) {
		if (desktop.getAttribute("Id_Num") == null) {
			String number = "0";
			desktop.setAttribute("Id_Num", number);
		}
		return null;
	}

	public String nextPageUuid(Page page) {
		return null;
	}


}


When I start the app I get this exception:

SEVERE: Unable to load /WEB-INF/zk.xml org.zkoss.zk.ui.UiException: Unable to load [com.marks.s2.zk.ComponentIdGenerator], at [SYS file:/C:/workspaces/S2OrderEntryZK/web-app/WEB-INF/zk.xml line 3

       at org.zkoss.zk.ui.sys.ConfigParser.parseClass(ConfigParser.java:424)
       at org.zkoss.zk.ui.sys.ConfigParser.parse(ConfigParser.java:244)

Am I doing this right? According to the documentation "The class must have a default constructor (without any argument), and implement the org.zkoss.zk.ui.sys.IdGenerator interface."

Any help would be appreciated.

Rhett

Rhett, I think the problem could be that you're not implementing org.zkoss.zk.ui.sys.IdGenerator. You seem to be implementing com.marks.s2.zk.IdGenerator. Even though they have the same signature, you haven't told Java that ComponentIdGenerator implements the org.zkoss.zk.ui.sys one. I think you should delete the com.marks.s2.zk.IdGenerator interface from your code, and make sure you import org.zkoss.zk.ui.sys.IdGenerator.
John.yesberg 04:24, 9 March 2009 (UTC)

Nicer ids for testing

I wanted a nicer way to refer to my components in selenium tests. I decided to add a new "name" attribute to the .zul file like this:

 <button id="addNode" name="addNodeButton" label="Add Node" forward="onAddNode" />

Since Button doesn't have a name, I had to wrap Button in a new class called NamedButton, and change the .zul again to

 <button id="addNode" name="addNodeButton" label="Add Node" forward="onAddNode" use="myproject.NamedButton" />

My NamedButton.java simply adds a String "name", and implements a NamedItem interface, like this:

 package myproject;

 import org.zkoss.zul.Button;

 public class NamedButton extends Button implements NamedItem {
	String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

 }

The NamedItem interface looks like this:

package jpt.tree;

public interface NamedItem {

	public String getName();
}

Then I wrote an IdGenerator to use the name attribute to make the new Id.

package jpt.tree;

import org.apache.log4j.Logger;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.sys.IdGenerator;

public class NamedIdGenerator implements IdGenerator {
	Logger logger = Logger.getLogger(NamedIdGenerator.class);

	public String nextComponentUuid(Desktop arg0, Component comp) {
		if (comp instanceof NamedItem) {
			NamedItem namedItem = (NamedItem) comp;
			logger.info("Returning a name for " + comp + " of " + namedItem.getName());
			return namedItem.getName();
		} else
			return null;
	}

	public String nextDesktopId(Desktop arg0) {
		return null;
	}

	public String nextPageUuid(Page arg0) {
		return null;
	}

}

The only trouble was that it kept returning a null name, and so ZK would generate its standard (non-deterministic) names. It turns out that the code that creates the Button component was asking the IdGenerator to supply the id before the name attribute was set. I found where this was happening in the ZK source, and made a slight change. My project now includes a package org.zkoss.zk.ui.impl, and the AbstractUiFactory.java class in it. All other classes are just found from the zk.jar in the normal way. I changed these two methods, starting at line 91.

	public Component newComponent(Page page, Component parent, ComponentInfo compInfo) {
		final Component comp = compInfo.newInstance(page, parent);

		compInfo.applyProperties(comp); // Moved the line to here.
		if (parent != null)
			comp.setParent(parent);
		else
			comp.setPage(page);

		// This is where the line used to be
		// compInfo.applyProperties(comp); // include comp's definition
		return comp;
	}

	public Component newComponent(Page page, Component parent, ComponentDefinition compdef, String clsnm) {
		final Component comp = compdef.newInstance(page, clsnm);

		// Moved the line to here.
		comp.applyProperties(); // including custom-attributes

		if (parent != null)
			comp.setParent(parent);
		else
			comp.setPage(page);

		// This is where the line used to be
		// compInfo.applyProperties(comp);
		return comp;
	}

An alternative is to extend the standard implementation SimpleUIFactory and overload only these two methods above. Then you can setup your app to use this custom NamedUiFactory in zk.xml:

<system-config>
    <ui-factory-class>my.NamedUiFactory</ui-factory-class>    
</system-config>

It's possible that there's a good reason for doing the setParent before the applyProperties (maybe ZK folk can comment?), but it seems to work for me. My selenium test now just has the following line to click the addNodeButton:

  <tr>
    <td>click</td><td>addNodeButton!real</button></td><td></td>
  </tr>

If this is a viable strategy, I'd suggest that maybe the following changes could be made to ZK:

  1. Add a name attribute to AbstractComponent
  2. Modify the org.zkoss.zk.ui.impl.AbstractUiFactory as described above
  3. Make the id generation code use the name attribute this way by default if it exists, so that a separate IdGenerator class isn't required.

John.yesberg 04:55, 9 March 2009 (UTC)