How to set a unique debug id in the face of reusable components?

We’re just starting to rewrite the UI for our web app using Vaadin, and are pondering the following issue.

Each button (to take a specific control) in our app must have a static id assigned to it so that our UI testing tools can “click” it. For a single instance of a button this is easy enough using the “setDebugId()” method. But if a given button is embedded in a component, and that component is used in a number of places, the button will have multiple instances.This complicates things, since Vaadin requires the id to be unique in the application. Thus, one cannot simply assign some static string as the id when the component is created, since then the various instances of the button would have the same id. Also, we’d like the id to be somewhat stable in the face of small-scale UI changes (e.g. reordering uses of the component), which precludes maintaining some sort of id generator in the component.

I’m wondering if there’s prior art regarding assigning a unique id: code idiom, framework modification, neat trick, etc. (Or maybe I’m missing something, and it’s not really an issue.) Thanks in advance for any pointers!

Hi,

We’ve got the same issue, and have an approach that is in the early stages of development. I’m afraid I can’t share the complete code at the current time - still a fair bit of work to do pulling it together into a reusable package; it’s currently in a proof-of-concept project.

We’ve split the problem into separate two parts : creating a debugId for a component, and assigning it.


Creating a DebugId

We’ve come up with the idea of a Naming strategy, that generates a name for a given component :

public interface NamingStrategy {

  /**
   * Returns the name for a given component, or null if one cannot be derived.
   *
   * @param component
   * @return
   */
  public String getName(Component component);
}

We’ve currently got four main implementations :[list]

[]
CaptionNamingStrategy : Returns the components caption
[
]
HasNameNamingStrategy : If the component implements a HasName interface, use that. This mainly is so that we can support I18N, and return the resourceBundle key for the component as the name.
[]
CompositeNamingStrategy : Given a number of naming stragies, returns the result of the first strategy that returns a non-null result. This allows to combine HasName and Caption strategies into one single strategy.
[
]
CompoundNamingStrategy : Given a single strategy, generates names for all the components going up the component tree, joining them with “.” (and ignoring null results) : e.g. AddressWindow.AddressForm.addressLine1

[/list]

Assigning the DebugId

Instead of assigning the debugId at component creation time, we need to assign it when the component is attached to the application (i.e. added into the window), so that we can be sure that the component has a parent. As far as we are aware, components almost always get added to a ComponentContainer; we have a (badly named) TestBenchIdAssigner that implements ComponentContainer.ComponentAttached and ComponentContainer.ComponentDetached, and add it to the layout of the window. Every time a component is attached it [list=1]

[]
If the component does NOT have a DebugId, assigns the debug id, based on a naming strategy - typically a CompoundNamingStrategy(CompositeNameStategy(HasNameNamingStrategy, CaptionNamingStrategy))
[
]
If the component is a ComponentContainer, it [list]

[]
adds itself as a ComponentContainer.ComponentAttached, ComponentContainer.ComponentDetached lisetner, which allows it to get propogated down the component tree.
[
]
recursively iterates around the children of the component container, generating debugId’s and adding itself as listener to child ComponentContainers.
[/list]
[/list]
When the component is detached, if it is a ComponentContainer [list]

[]
it removes itself as an Attach/Detach listener.
[
]
recursively iterates around the children of the component container, removing itself as listener to child ComponentContainers.
[/list]
There are some edge cases here :
Form may need to be handled carefully : from memory, it does not implement ComponentContainer, so the component iteration in TestBenchIdAssigner may needs to be a bit smart and iterate the form’s layout. (or, you could use a subclass of Form and make that implement ComponentContainer)
If you remove a component, and add it elsewhere, the first debugId is currently retained - which could result in a naming clash. We’re not currently removing the DebugId in the detach method, because it might have been manually assigned. Our proposal would be to prefix the automatically-assigned debugId, so that we can identifiy as such at detach time, and removed.


Conclusion

This possibly over-engineered solution, assuming that all important components have a static caption/name and that you don’t have two components with the same name inside the same container, should give you unique debugIds within a window, allowing testing tools to uniquely identify components.

Apologies for the lengthy response. I hope it makes some sort of sense - any questions, please feel free to ask. Our intention is that - when it is in a more usable state, and when we have enough time - we will contribute the code to the Vaadin AddOns Directory; I currently have no timeframe for that. In the mean time, hopefully this post gives you an outline of one approach.

Cheers,

Charles.

Hi,

Prompted by this post, I have spent most of the day trying to marshal my existing code into some sort of order. I’ve not had time to
a) Create a Vaadin AddOn or
b) Create an ant-build or
c) Create an example

However, what I have done is created an open mercurial project on BitBucket, and published all my code and tests to it. The only dependency, other than JUnit, is Google’s
Guava Libraries
, which I am using for some Iterable goodness. Both binaries are included in the project.

The code is Apache2 licensed : use as is, or feel free to use it as a starting point of your own efforts.

The project is here


https://bitbucket.org/CharlesAnthony/vaadincomponentutilities

Excuse the rubbish name - I’ve been using it to store various generic utilities relating to Vaadin Components.

org.vaadin.addon.componentutilities.iterator.ComponentIterables is a facade for iterating around the immediate children of a component, or an entire component tree. e.g.

 for (Component child : ComponentIterables.immediateChildren(component)) {
   /* do something with child. the immediateChildrenIterable handles Forms and Panels, ensuring that their
      Content and Footer layouts  are returned as  children */
 }

for (Component component: ComponentIterables.componentTreeBreadthFirst(root)) {
   //do something with component
 }

org.vaadin.addon.componentutilities.naming contains all NamingStrategies as mentioned in my earlier post.

org.vaadin.addon.componentutilities.testing.DebugIdManager brings both the iterators and NamingStrategy together to assign DebugId’s to components.

See DebugIdManagerTest#testComponentEvents as a sort of example of usage. I would envisage a DebugIdManagingWindow class evolving at some point, so that any component added to or removed from the window’s component tree at any time could have a DebugID assigned to it (depending on the naming strategy)

I have tried to add relevant javadoc to the code, but I am sure it’s probably a bit rubbish/confusing.

I hope this is useful to other people - at least this has given me an impetus to tidy the code up and improve code-coverage of the tests (should be 100%, but whether they’re useful tests is open to question!)

Comments and feedback is more than welcome.

Cheers,

Charles.

I don’t know when I’ll be able to dig into the code, but I like the design ideas (which are the important thing to me), namely:

  • separating id creation from assignment
  • using a naming strategy to create the id, especially the strategy that uses the component tree to produce a dot-separated id
  • assigning the id at component attach time

I think we’ll do something along the same lines, and your code will be very helpful. Thanks for the detailed response!