As all who have used it know Selenium is a powerful tool when testing front-end applications. I personally use it in combination with protractor. This is because most of the work I do is with Angular and AngularJS applications. When you are using Typescript extending classes is an easy thing. In light of this I’ve been experimenting with new approaches to creating page objects.

From what I knew

The experiments started out by creating a class and then passing the "container" to it’s constructor. This is a powerful mechanism which has served me well during my time working with non-Typescript AngularJS. But the downside for this approach is that you’d have to expose each and every API function Selenium gives you. Even if you’d only expose those functions you’d need; it would still feel like a hassle. The extensions would look something like this:

getText() {
  this.container.getText();
}

And though this works and makes it all very explicit there had to be a better way. So when looking into the API for Selenium it exposes two classes which are exactly what we need. These being: ElementFinder & ElementArrayFinder.

To what I found

So to make life easier on ourselves we can extend these classes; thus creating generic versions of them which handle the base construction. This because extending generic versions of these classes make the creations of page objects a breeze. Basically these classes look like this.

export class GenericElementFinder extends ElementFinder {
  constructor(protected elementFinder: ElementFinder) {
    super(elementFinder.browser_, elementFinder.elementArrayFinder_);
  }
}

export class GenericElementArrayFinder extends ElementArrayFinder {
  constructor(protected elementArrayFinder: ElementArrayFinder) {
    super(elementArrayFinder.browser_, elementArrayFinder.elementArrayFinder_);
  }
}

So finally

These classes allow me to create page objects quickly, as they extend on the readily available Selenium API. Thus creating specific objects without losing options. All this without having to write code to access the API of the wrapped object. Once created; a page object extending either of these classes can be used to expose an underlying element through with it’s own API. Like so:

get name() {
  return this.element(by.className('user-form__name'));
}

get address() {
  return new AddressPO(this.element(by.className('user-form__address')))
}

This allows us to use these page objects the same way we’d use selenium elements or element arrays. Whilst also providing element(-array) specific information retrieval and interactions.

Standard disclaimer
So as always i welcome an open dialog and look forward to your opinions. Please note that the opinions and practices as described in this post are meant as food for thought. If you decide that another approach better suits you and/or your organization I’d advise you follow that course of action.
shadow-left