Home > Blockchain >  Cypress JS using multiple page objects in one method
Cypress JS using multiple page objects in one method

Time:06-22

i have been struggling with some logic about using multiple page objects in one method, i couldnt find any idea to use like that logic, for example;

These are my methods in my page object called usersTable;

 get rolesAndStatusMenu() {
    return cy.get("#menu- > .MuiPaper-root > .MuiMenu-list>li");
  }

  get usersPartialRow() {
    return cy.get(".MuiTableBody-root>tr>td");
  }

settings(options: string) {
    return cy
      .get(
        "[style='position: fixed; z-index: 1300; inset: 0px;'] > .MuiPaper-root > .MuiList-root",
      )
      .contains(options);
  }

  menuButton(userId: string) {
    return cy.get(`.user_${userId}>td>button`);
  }

  userRow(userId?: string) {
    const userrow = ".MuiTableBody-root>tr";

    if (userId === undefined) {
      return cy.get(userrow);
    }

    return cy.get(userrow).get(`.user_${userId}`);
  }

im using userRow method in this test like that;

usersTable.userRow(userId).should("not.exist");

And for exaple im using my userMenu and settings method in this test;

usersTable.menuButton(userId).click();
usersTable.settings("Impersonate").click();

Let's come to the idea that I want to do but I can't find the way to do it;

usersTable.userRow(userId).settings.menuButton.click()

usersTable.userRow(userId).settings.impersonate.click()

Is there a any way to use like that ? All ideas are accepted

Update

I have one more page object, i define my usersTable component modal inside called usersPage page modal

    import { UsersTable } from "../components/UsersTable ";
    
    export class Users {
      visit() {
        return cy.visit("/users");
      }
      get headingText() {
        return cy.get(".MuiTypography-h5");
      }
      get inviteUserBtn() {
        return cy.get(".MuiGrid-root> .MuiButtonBase-root");
      }
      get inviteUserModal() {
        return cy.get(".MuiDialogContent-root");
      }
    get usersTable() {
    return new UsersTable();
  }
    }

So my code looks like this

usersPage.usersTable.menuButton(userId).click();
usersPage.usersTable.settings("Impersonate").click();
usersPage.visit();
usersPage.usersTable.menuButton(userId).click();
usersPage.usersTable.settings("Delete").click();
usersPage.usersTable.userRow(userId).should("not.exist");

For example using this way

usersPage.usersTable.userRow(userId).settings.menuButton.click()

So maybe i can create class inside UsersTable

export class UsersTable {
...
}
class userTableRow {
}
**and returning it in `UsersTable` or something like that ?**

Second Update

Now i create a class inside UsersTable file;

class UserRow {
  userRow(userId?: string) {
    const userrow = ".MuiTableBody-root>tr";
    if (userId === undefined) {
      return cy.get(userrow);
    }

    return cy.get(userrow).find(`.user_${userId}`);
  }
  get menuButton() {
    return this.userRow(`>td>button`); //Btw im not sure this one is working, i think something is wrong here;
  }
  get impersonate() {
    return cy
      .get(
        "[style='position: fixed; z-index: 1300; inset: 0px;'] > .MuiPaper-root > .MuiList-root",
      )
      .contains("Impersonate");
  }
  get delete() {
    return cy
      .get(
        "[style='position: fixed; z-index: 1300; inset: 0px;'] > .MuiPaper-root > .MuiList-root",
      )
      .contains("Delete");
  }
}

And for using this class returned in UsersTable class;

 userRow(userId?: string) {
    const userrow = ".MuiTableBody-root>tr";

    if (userId === undefined) {
      return cy.get(userrow);
    }

    return new UserRow(userId); **// but got error, it says Expected 0 arguments, but got 1.**
  }

If i use like this comment section;

 // get UserRow() {
  //   return new UserRow();  
  // }

I can able to reach everything inside user but i can't use my test like this;

usersPage.usersTable.UserRow(userId).settings.menuButton.click()

I can use like this;

usersPage.usersTable.UserRow.userRow(userId).settings.menuButton.click()

How can i define userId?: string for UserRow userId is constantly changing every time, I get it from API inside test, So I can't define for sure.

CodePudding user response:

To make class methods fluid, you will need to return this.

class UsersTable {

  settings(options: string) {
    cy.get(...).contains(options);
    return this                         // call next method on return value
  }

  menuButton(userId: string) {
    return cy.get(`.user_${userId}>td>button`); // cannot call another after this one
  }

  userRow(userId?: string) {
    cy.get(userrow).get(`.user_${userId}`);
    return this;                              // call next method on return value
  }
}

Should work,

usersTable.userRow(userId).settings().menuButton().click()

But now, where are the values from first two methods?

You would need to store them in the class

class UsersTable {

  userId: string = '';
  setting: string = '';

  settings(options: string) {
    cy.get(...).contains(options)
      .invoke('text')
      .then(text => this.setting = text)

    return this                        
  }

  menuButton() {                       // no parameter, take value from field
    return cy.get(`.user_${this.setting}>td>button`); 
  }

  userRow() {            // no parameter, take value from field
    cy.get(userrow).get(`.user_${this.userId}`)
      .invoke('text')
      .then(text => this.userId = text)

    return this;                              // call next method on return value
  }
}

But now have lost flexibility, methods are tightly coupled, not independent anymore.

CodePudding user response:

The UserTable methods return the Cypress.Chainable type, so you will need to unwrap the result to pass it to the next method.

Also, it's returning the element but next method needs text content, so extract that as well.

usersTable.userRow(userId)
  .then((userIdElement: JQuery<HTMLElement>) => {  // unwrap Chainable
    const text = userIdElement.text()              // extract text
    usersTable.settings(text)
  })
  .then((settingsElement: JQuery<HTMLElement>) => {  // unwrap Chainable
    const text = settingsElement.text()              // extract text
    usersTable.menuButton(text).click()
  })

If any of the elements are HTMLInputElement, use userIdElement.val() instead.

An adjustment to userRow():

class UsersTable {

  ...

  userRow(userId?: string): Cypress.Chainable<JQuery<HTMLElement>> {  
    const userrow = ".MuiTableBody-root>tr";

    if (userId === undefined) {
      return cy.get(userrow);
    }

    return cy.get(userrow)
      .find(`.user_${userId}`)  // use find instead of get
  }
}

How to do it with Custom Commands instead of pageObject

Chaining is a natural code pattern for Cypress commands, so using Custom Commands

commands.js

/// <reference types="cypress" />

declare namespace Cypress {
  interface Chainable<Subject = any> {
    settings(options?: string): Chainable<JQuery<HTMLElement>>;
    menuButton(userId?: string): Chainable<JQuery<HTMLElement>>;
    userRow(userId?: string): Chainable<JQuery<HTMLElement>>;
  }
}

Cypress.Commands.add('settings', {prevSubject: 'optional'}, (subject: any, options?: string): Cypress.Chainable<JQuery<HTMLElement>>  => {
  if (options === undefined) {
    options = subject as string;
  }
  return cy.get("[style='position: fixed; z-index: 1300; inset: 0px;'] > .MuiPaper-root > .MuiList-root")
    .contains(options)
})

Cypress.Commands.add('menuButton', {prevSubject: 'optional'}, (subject: any, userId?: string): Cypress.Chainable<JQuery<HTMLElement>> => {
  if (userId === undefined) {
    userId = subject as string;
  }
  return cy.get(`.user_${userId}>td>button`);
})

Cypress.Commands.add('userRow', (userId?: string): Cypress.Chainable<JQuery<HTMLElement>> => {
  const userrow = ".MuiTableBody-root>tr";

  if (userId === undefined) {
    return cy.get(userrow);
  }

  return cy.get(userrow)
    .find(`.user_${userId}`)
})

test

it('tests with userId from userRow()', () => {
  const userId = '1'

  cy.userRow(userId)
    .settings()      // as child command, userId from previous command
    .menuButton()
    .click()
});

it('tests with userId hard-coded', () => {

  cy.settings('abc')    // as parent command, userId passed as parameter
    .menuButton()
    .click()
});
  • Related