Utilizing multiple page objects within a single method in Cypress JS

I have been grappling with the concept of utilizing multiple page objects within a single method. I haven't been able to come up with a suitable approach for implementing this logic. For instance, consider the following methods in my page object named "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}`);
  }

Previously, I utilized the userRow method in my test as shown below:

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

In addition, I used the userMenu and settings methods in this test:

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

Now, I want to combine different methods in a single call, but I'm uncertain about how to achieve this. For example:

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

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

Is it possible to use methods in this manner? Any suggestions are welcome.

Update

I have another page object where I encapsulated the usersTable component, called usersPage:

    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();
  }
    }

My code now appears 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, I am considering creating a class inside UsersTable:

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

Second Update

Now I have created a class within the 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`); //I am unsure if this line is correct;
  }
  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");
  }
}

To integrate this class returned in the 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 utilize the following method in the comment section:

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

I can access everything within the user but cannot use my test like this:

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

or perhaps

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

But I can use it like this:

 usersPage.usersTable.UserRow.menuButton.click()

How can I define userId?: string for UserRow? The userId constantly varies and is obtained from an API within the test, making it impossible to define in advance.

Answer №1

To ensure smooth flow in class methods, it is important to remember to return this each time.

class UsersTable {

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

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

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

This setup should work:

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

However, if you need to access the values from the first two methods, you will 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() {                     
    return cy.get(`.user_${this.setting}>td>button`); 
  }

  userRow() {            
    cy.get(userrow).get(`.user_${this.userId}`)
      .invoke('text')
      .then(text => this.userId = text)

    return this;                              
  }
}

Keep in mind that this approach may lead to a loss of flexibility, as the methods become tightly coupled and are no longer independent.

Answer №2

In the UserTable methods, the return type is Cypress.Chainable. Therefore, to pass it to the next method, you must unwrap the result.

Additionally, the method returns the element, but the next method requires text content. So, you will also need to extract the text.

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
  }
}

Implementing Custom Commands instead of pageObject

Chaining is a common code pattern for Cypress commands, so it's advisable to use 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()
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Adding URL path in Angular 7's .ts file

I currently have the following code in my component's HTML file: <button mat-flat-button class="mat-flat-button mat-accent ng-star-inserted" color="accent" (click)="playVideo(video)"> <mat-icon [svgIcon]="video.type === 'external' ...

Creating a BPMN web-based designer using JavaScript

In search of a web-based UI tool to design and save bpmn workflows as XML for integration with an Angular front end. As a starting point, I need to draw bpmn shapes. Does anyone have suggestions on the best method to accomplish this using JavaScript? I&apo ...

Challenge when providing particular strings in Typescript

Something seems to be wrong with the str variable on line number 15. I would have expected the Typescript compiler to understand that str will only ever have the values 'foo' or 'bar' import { useEffect } from 'react' type Ty ...

How can I include the cypress.io baseUrl environment variable in my package.json run scripts?

Is it possible to pass the baseUrl from the cypress.json file into the scripts of the package.json file for my cypress test project? I have searched through the cypress documentation and stack overflow, but I haven't found a solution that doesn' ...

Using Angular 6 HttpClient to retrieve an object of a specific class

Previously, we were able to validate objects returned from http api calls using the instanceof keyword in Angular. However, with the introduction of the new HttpClient Module, this method no longer works. I have tried various simple methods, but the type c ...

Encountered an unexpected interpolation ({{}}) in column 3 of Data Bind RouterLink (Angular 2) that was expecting an expression

Encountering difficulties passing data to my routerLink. The goal is to change the route when the id reaches 4 within the ngFor loop. <input type="button" class="btn-cards" [ngClass]="getStyle(negociacao)" [routerLink]="['/{{negociacao.rota}}&apo ...

Difficulty Loading Static JavaScript File in Express.js

Currently in the process of setting up an express server with create-react-app. Encountering this error in the console: Uncaught SyntaxError: Unexpected token < bundle.js:1 Upon clicking the error, it directs me to the homepage htm ...

"Exploring the process of comparing dates using HTML, AngularJS, and Ionic

I am working on an HTML file that shows a list of notification messages. I am trying to figure out how to display the time difference between each notification. The code snippet below displays the notifications and includes the time for each one: <ion- ...

Exploring the integration of d3 in an Express application - encountering an error: document is not recognized

I am facing a challenge in my expressjs application where I need to dynamically render vertices in a graph using d3. However, the code execution order seems to be causing issues for me. When attempting to use the d3.select function, I encounter the followi ...

Error: The function initMap() is not recognized in the Google Maps API

I have been experimenting with the Flickr API and I'm currently working on asynchronously loading images along with their metadata. To accomplish this, I have a script that utilizes three AJAX calls: $(document).ready(function() { var latLon = { ...

Catching exceptions with jQuery Ajax

I'm facing a tricky issue with an exception that seems to slip through my fingers: //myScript.js.coffee try $.ajax async: false type: "GET" url: index_url success: -> //Do something error: -> //Do something els ...

Jquery Error: Unable to split object

Why am I getting an error saying "Uncaught TypeError: Object # has no method 'split'" when trying to run this page by clicking on the dropdown and triggering a change event that sends an AJAX request? <html xmlns="http://www.w3.org/1999/xhtm ...

Alter text within a string situated between two distinct characters

I have the following sentence with embedded links that I want to format: text = "Lorem ipsum dolor sit amet, [Link 1|www.example1.com] sadipscing elitr, sed diam nonumy [Link 2|www.example2.com] tempor invidunt ut labore et [Link 3|www.example3.com] m ...

Access the JavaScript variable in a webview and store it in an Android variable

I have been attempting to retrieve a variable from a webview, but I am only able to make modifications like this: browser.loadUrl("javascript:var x = document.getElementById('login').value = 'something';"); However, I need to be able ...

What is the best way to utilize window.find for adjusting CSS styles?

Incorporating both AJAX and PHP technologies, I have placed specific text data within a span element located at the bottom of my webpage. Now, my objective is to search this text for a given string. The page consists of multiple checkboxes, with each check ...

Struggling to retrieve the value in Node.js

Currently, I am in the process of developing a Node.js script to perform the following tasks: Read a file line by line Identify a regex match and store it in a variable Utilize this value in subsequent operations Below is the code snippet that I have im ...

Optimizing jqGrid: Enhancing saveRow function to properly synchronize with editRow function

Exploring how to customize jqGrid's add function for my own needs. I have a navButton with specific requirements: When the user clicks the button, a new row in edit mode should appear on the grid. Once the user enters data and presses enter, the dat ...

The Angularfire library encountered an issue when trying to access the 'push' property of a null object

I am currently in the process of creating a new object in the database for an assessment. Right now, I have hardcoded it to test its functionality, but ultimately, it will be dynamic based on user input from the view. However, I am encountering an error th ...

Ways to circumvent ng switch and create a component based on type

In my current code, I have an array called resourceTypes and I am using ngSwitch to create different components/directives based on the TypeName. However, I find this approach cumbersome as I have to update the code every time I add a new resource editor. ...

Storing the array with the highest length in a temporary array using Javascript

I am currently working with two arrays that can be of equal length or one may be longer than the other. My goal is to determine the longest array if they are not equal in length, and then use this length to control a loop. $.ajax({ url: "/static/Dat ...