ウェブサイト検索

javascript - Cypress JSで1つのメソッドで複数のページオブジェクトを使用する


1つのメソッドで複数のページオブジェクトを使うことについて、いくつか論理的に悩んでいます。そのようなロジックを使うアイデアが見つかりませんでした。例えば;

これらは、私のページオブジェクト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}`);
  }

私はこのテストで userRow メソッドを次のように使っています;

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

例えば、このテストで私の userMenu と settings メソッドを使用しています;

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

私がやりたいけれど、どうすればいいかわからないというアイデアに行きましょう;

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

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

そのように使う方法はありますか?どんなアイデアでも受け付けます

更新

私はもう一つページオブジェクトを持っており、その中で usersPage ページモーダルの中に usersTable コンポーネントモーダルを定義します

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

だから私のコードはこんな感じです

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");

例えばこの方法を使って

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

だからおそらく私は UsersTable 内にクラスを作成できるかもしれません

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

2回目の更新

今、私は UsersTable ファイルの中にクラスを作成します;

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

そして、このクラス returned を UsersTable クラスで使用するために;

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

もし私がこのコメント欄のように使ったら;

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

私はユーザーの中のすべてにアクセスすることができますが、このようにテストを使うことはできません;

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

あるいは

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

しかし、このように使うことができます;

 usersPage.usersTable.UserRow.menuButton.click()

UserRow の userId?: string をどのように定義できますか。userId は毎回常に変わるので、テスト内で API から取得しています。そのため、確実に定義することができません。

回答 #1

クラスのメソッドを流れるようにするには、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
  }
}

うまくいくはずです,

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

しかし今、最初の二つの方法の値はどこにありますか?

それらをクラスに保存する必要があります

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

しかし今では柔軟性を失い、方法は密接に結びついており、もはや独立していません。

回答 #2

UserTable のメソッドは Cypress.Chainable 型を返すので、次のメソッドに渡すには結果をアンラップする必要があります。

また、要素を返していますが、次のメソッドではテキストコンテンツが必要なので、それも抽出してください。

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

もし任意の要素がHTMLInputElementの場合は、代わりにuserIdElement.val()を使用してください。

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

pageObject の代わりにカスタムコマンドでそれを行う方法

チェイニングはCypressのコマンドにおける自然なコードパターンであり、カスタムコマンドを使用することも同様です

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

テスト

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