Part 6: Create base pages and use simple page factory

Now, we would like to run the same test on Android. Before we do that, let's look at some concepts of inheritance.

Some concepts of Inheritance

Consider that we have an abstract base class called HomePage which declares a method called searchBooks().

Now, we can create an object of the WebHomePage and say that it is of type WebHomePage.

const page: WebHomePage = new WebHomePage();
page.searchBooks();

When we call the searchBooks() method on this object, it prints "in web page".

We can also create an object of the WebHomePage class and declare it as a type of the base page i.e. HomePage class. This is possible because the is-a relation due to inheritance (WebHomePage is-a HomePage).

So the code could look like this:

const page: HomePage = new WebHomePage();
page.searchBooks();

This would also print "in web page" since the object is of the WebHomePage class.

But how is this useful?

It's useful if it's used with a Simple Factory.

Simple Factory

A simple factory creates objects without exposing the creation logic to the clients and refers to the newly created object using a common interface.

This means that a simple factory can be used to create the page objects that we need and the type of the page objects can be that of the parent type.

Let's see this in code to get a better understanding.

Creating parent pages

Let's create abstract parent page classes. We can create a directory called parent inside the pages directory and create home.page.ts and search-results.page.ts files.

The parent home.page.ts file contains the abstract class that declares the searchBooks() method:

export default abstract class HomePage {
  abstract searchBooks(searchTerm: string): Promise<void>;
}

The parent search-results.page.ts file contains the abstract class that declares the getSearchResultsTitles() method:

export default abstract class SearchResultsPage {
  abstract getSearchResultTitles(): Promise<string[]>;
}

Extend child pages

We can now update the web home and search results pages to extend the corresponding parent pages.

The web home.page.ts will now look like this:

import { findElement } from '../../helpers/element.helper';
import BaseHomePage from '../parent/home.page';

const searchBoxLocator = '.search-box';

export default class HomePage extends BaseHomePage {
  async searchBooks(searchTerm: string): Promise<void> {
    const searchBox = await findElement(searchBoxLocator);
    await searchBox.setValue(searchTerm);
    await driver.keys('Enter');
  }
}

We can use the term BaseHomePage for parent page so that there is no compilation error.

Similarly, the web search-results.page.ts file will now look like this:

import { findElements } from '../../helpers/element.helper';
import BaseSearchResultsPage from '../parent/search-results.page';

const bookTitleLocator = '.book-title';

export default class SearchResultsPage extends BaseSearchResultsPage {
  async getSearchResultTitles(): Promise<string[]> {
    const titleElements: WebdriverIO.Element[] = await findElements(bookTitleLocator);
    const titlePromises = titleElements.map(async element => element.getText());
    return Promise.all(titlePromises);
  }
}

Simple Page Factory

We have a page factory in our code that creates the page objects. This class creates the page objects without exposing the logic of creation.

The page factory takes the name of the page as an argument, understands which platform we need the object for (based on which tests we are running) and creates and returns the correct page object.

Using PageFactory in steps

We currently create objects in the step file. We can modify it to use page factory for object creation. We can also mention the type of the object as the parent page type.

So, in the search.steps.ts file, we can use the page factory to create a home page and mention that it is of type parent home page. The code is as mentioned below:

import { When } from '@wdio/cucumber-framework';
import PageFactory from '../../src/pages/factory/page.factory';
import type HomePage from '../../src/pages/parent/home.page';

When(/^the user searches for a book named "(\w+)"$/, async (searchTerm: string) => {
  const homePage = await PageFactory.getInstance('home.page') as HomePage;
  await homePage.searchBooks(searchTerm);
});

Similarly, we can update the search-results.steps.ts to make use of page factory to create the object of search results page and mark it as type "parent" search results page.

import { Then } from '@wdio/cucumber-framework';
import PageFactory from '../../src/pages/factory/page.factory';
import type SearchResultsPage from '../../src/pages/parent/search-results.page';

Then(/^the search results should display "(\w+)" books$/, async (expectedTitle: string) => {
  const searchResultsPage = await PageFactory.getInstance('search-results.page') as SearchResultsPage;
  const actualTitles = await searchResultsPage.getSearchResultTitles();

  expect(actualTitles.length).toBeGreaterThan(0);
  expect(actualTitles.every(title => title.toLowerCase().includes(expectedTitle))).toBe(true);
});

In the next post, we will look at how this helps us easily add tests for other platforms.