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