Circular dependencies in Protractor tests with page object model

Hi all - I inherited a suite of Protractor tests that I’m trying to understand and do some refactoring on. It’s written in Typescript with ES6 syntax.

The test suite is using the Page Object Model and I noticed that elements and methods related to navigation currently live in a child page file called Landing Page. In essence:

// LandingPage.ts

import { ParentPage } from './parent-page.ts'
import { HomePage} from './home-page.ts'
import { AboutPage} from './about-page.ts'

export class LandingPage extends ParentPage {
     
     homePageLink = element(by.id('home-page'))
     aboutPageLink = element(by.id('about-page'))

       navigateToHomePage: async (): Promise<HomePage> => {
            await homePageLink.click()
            return new HomePage();
       }

       navigateToAboutPage: async (): Promise<AboutPage> => {
          await aboutPageLink.click()
          return new AboutPage();
       }
 
}

However - clicking navigation links and navigating to new pages is something that you can do from any page, not just the Landing, so it made sense to me to move these methods into the Parent Page Object itself. But when I do, I get this error:
Error: TypeError: Class extends value undefined is not a constructor or null

I learned that this is because I created a circular dependency from this Stackoverflow article and the ones linked in the answers: javascript - TypeError: Class extends value undefined is not a function or null - Stack Overflow

Essentially:

// ParentPage Object
import {HomePage} from './home-page'

export class ParentPage {

     navigateToHomePage() {
          // do some stuff 
          return new HomePage();
     }
}
// Home Page Object
import {ParentPage} from './parent-page'

export class HomePage extends ParentPage {}

I have a method in my Parent Page object that returns an instance of a class that extends the Parent itself, which seems to be the crux of the problem. And also why all these navigation methods are in a child page class and not the parent.

But, how do I get around this while still using the page object model? All I can think of is not returning a Home Page object from the navigateToHomePage() method, just going through the steps and making it a void return type like so:

// ParentPage Object
import {HomePage} from './home-page'

export class ParentPage {

     navigateToHomePage() {
          // do some stuff 
     }
}
// a test file
import {HomePage} from './home-page'

it('tests I can get to the home page', async() => {
     await landingPage.navigateToHomePage();
     let homePage = new HomePage();
})

This test would have to import all of the pages it would navigate to in the course of the test - not sure if that is an anti-pattern or not.

Anyone have thoughts on how to best handle this?

1 Like

Had similar confusion in Python land and found the only way around was to have navigateToXXX actually call a factory class, that returns the desired object as an opaque ref, and then just blindly return whatever the factory gives you from your navigateToXXX. The downside is that you then must be sure to register all classes with the factory, first, and always construct them using a “string” to look up each page constructor in a “map” which is built when each class registers. I’ve not written much Java, (or Typescript) but the Python pattern is mostly a copy-paste of the Java Factory pattern, and so long as you register every page with the factory, and any needed parameters like the driver object and other state are available to the factory, it’s pretty much like in the book. Design Pattern - Factory Pattern you will want the abstract factory pattern I believe. In Python this was a pain to implement right as I had to write a decorator function which in turn needed it’s own func wrapper, hope it’s simpler in Java.
The trick was ensuring that if you forgot to register or include a page-class, to instead throw a meaningful exception to make troubleshooting easier when you add new pages but forget to tell the factory about them.

Someone may even have a Typescript factory pattern example someplace?

1 Like

That’s super helpful and sounds like a learning opportunity for me, I haven’t used this design pattern before.
I figured this must be something fiarly often encountered unless I was missing an obvious way to implement the POM.

Yeah , sorry I was only able to give a hint. I literally have 5 minutes of Typescript experience from 4 years ago when I tested a web app for a short while. I deplore using Stack Overflow else I would have posted a link to a Typescript question from there.

I had built my entire POM setup base classes, written single page self-tests for it and documented it all only found this design bug when I started using it in anger, so had to retrofit a lot.

Out of curiosity, why the dislike for Stack Overflow?

SO is great for solving common problems. But I’ve found it’s terrible for solving anything remotely rare or niche, as well as any question that has a bad sell by date. For example, it’s possible to use a filter to remove answers more than 5 years old, but you have to explicitly do that. And many new users to the site don’t know how to ignore old answers that did not have good shelf lives.

I’ve always thought that code examples without error handling are a bad way to teach people how to code, and SO has been very good at pointing out not to copy their code - a very good lesson. However, I’ve seen far too many code examples around the software testing tools usage questions which are not only bad examples, but demonstrate that clean-code “developers” rarely read “test” related threads and improve them. The result is that many test related questions have very low quality answers on the platform. You are better of going into the chat rooms of StackOverflow or going to the tool vendors own website for test tool related help.

All this does make SO really good for answering old questions and code sample questions though, like design patterns in Java. So there is a definite upside. You just need to be in filter mode when you use StackOverflow.

1 Like