Cypress page object model pattern. Extending the elements property

59 views Asked by At

I have the following page object model representing a widget in my app

/**
 * Contains common actions for all widgets
 */

export default abstract class AbstractWidget {
  private widgetId: number;

  elements = {
    widget: () => cy.getDataTestId(`widgetbox-container-${this.widgetId}`),
  };

  constructor(widgetId: number) {
    this.widgetId = widgetId;
  }
  
}

And I have a concrete class extending this class where I would like to append extra elements specific to this widget to the elements property

import AbstractWidget from './AbstractWidget';

export default class SpecificWidget extends AbstractWidget {
    elements = {
        ...super.elements,
        assetSearch: () => cy.getDataTestId('assetSearch'),
    };


    constructor(widgetId: number) {
        super(widgetId);
    }
}

however, when I try to spread the elements from the abstract super class

    elements = {
        ...super.elements,
        assetSearch: () => cy.getDataTestId('assetSearch'),
    };

it results in the typescript error

TS2340: Only public and protected methods of the base class are accessible via the  super  keyword.

What am I doing wrong?

1

There are 1 answers

0
TesterDick On BEST ANSWER

This seems to be a result of this issue
Accessing property on super should be a type error #35314 Nov 24, 2019

If you follow the TS Playground link, it opens with the latest TS version and console.log(super.n) shows an error. If you change the version to 3.7.5 the error goes away.

But if you look at the Handbook Overriding Methods, the equivalent access for methods is permitted, so the above change seems to have created an anomaly

class Base {
  greet() {
    console.log("Hello, world!");
  }
}
 
class Derived extends Base {
  greet(name?: string) {
    if (name === undefined) {
      super.greet();
    } else {
      console.log(`Hello, ${name.toUpperCase()}`);
    }
  }
}

Specifically in Cypress you can either use this.elements instead of super.elements, since the derived class inherits the base class element property:

Testing (with string return values instead of Chainers)

export default class SpecificWidget extends AbstractWidget {
  elements = {
    ...this.elements,
    assetSearch: () => 'assetSearch',
  }

  constructor(widgetId: number) {
    super(widgetId)
  }
}
export default abstract class AbstractWidget {
  private widgetId: number

  elements = {
    widget: () => `widgetbox-container-${this.widgetId}`,
  }

  constructor(widgetId: number) {
    this.widgetId = widgetId
  }
}
const specific = new SpecificWidget(1)
expect(specific.elements.widget()).to.eq('widgetbox-container-1')
expect(specific.elements.assetSearch()).to.eq('assetSearch')

enter image description here


Or you can create a "helper method" in the base class

export default abstract class AbstractWidget {
  private widgetId: number

  elements = {
    widget: () => `widgetbox-container-${this.widgetId}`,
  }

  getElements() {
    return this.elements
  }

  constructor(widgetId: number) {
    this.widgetId = widgetId
  }
}
export default class SpecificWidget extends AbstractWidget {
  elements = {
    ...super.getElements(),
    assetSearch: () => 'assetSearch',
  }

  constructor(widgetId: number) {
    super(widgetId)
  }
}

An additional note, returning Chainers like this

widget: () => cy.getDataTestId(`widgetbox-container-${this.widgetId}`),

can result in unexpected problems if the returned Chainer gets invalidated due to page changes.

See Variables and aliases - Return values

Return Values
You cannot assign or work with the return values of any Cypress command. Commands are enqueued and run asynchronously.

It's ok if you intend to chain immediately with no page actions between calling widgit() and using the result, but that means anyone using the class has to remember that caveat.

Cypress made a change to aliases to defaullt to type: query specifically for this problem.

If an alias becomes stale when used, the chainer is re-run to get a fresh copy of the query result. But this won't happen automatically with your page-object methods.