Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: Inheritance Issue when more than one child is issue in the same scenario #102

Closed
jzaratei opened this issue Mar 8, 2024 · 6 comments

Comments

@jzaratei
Copy link
Sponsor

jzaratei commented Mar 8, 2024

Given
Scenario: Add todos
When I add todo "foo"
And I add todo "bar"
Then visible todos count is 2

When
I create multiple child classes for todo page: AdminTodoPage, OtherTodoPage

export
@Fixture<typeof test>("adminTodoPage")
class AdminTodoPage extends TodoPage {
  constructor(public page: Page) {
    super(page);
  }

  @When("I add todo {string}")
  async addToDo(text: string) {
    await this.inputBox.fill(text);
    await this.inputBox.press("Enter");
  }
}
export
@Fixture<typeof test>("otherTodoPage")
class OtherTodoPage extends TodoPage {
  constructor(public page: Page) {
    super(page);
  }

  @When("I complete todo {string}")
  async completeTodo(hasText: string) {
    const checkbox = this.todoItems.filter({ hasText }).getByRole("checkbox");
    if (!(await checkbox.isChecked())) {
      await checkbox.click();
    }
  }
}

Then
I run npx bddgen and I get

Error: Can't guess fixture for decorator step "I filter todos as "Completed"" in file: features\todopage.feature. Please refactor your Page Object classes or set one of the following tags: @fixture:adminTodoPage, @fixture:otherTodoPage

Adding @fixture:otherTodoPage or @fixture:adminTodoPage tag does not work

But I expect
test cases to be generated successfully. The step "I filter todos as "Completed" should use parent fixture 'todoPage'

Isolated demo
https://github.com/jzaratei/playwright-bdd-example/tree/inheritance-issue

Environment

@jzaratei jzaratei added the bug Something isn't working label Mar 8, 2024
@vitalets
Copy link
Owner

vitalets commented Mar 9, 2024

This is kind of "expected", but lets discuss the use case.
Looking at the scenario itself, I've marked POM for each step:

  Scenario: Complete todos
    When I add todo "foo" # AdminTodoPage
    And I add todo "bar" # AdminTodoPage
    And I complete todo "bar" # OtherTodoPage
    And I filter todos as "Completed" # TodoPage
    Then visible todos count is 1 # TodoPage

So scenario uses 3 POMs that are in non-linear relationship:

TodoPage
AdminTodoPage extends TodoPage
OtherTodoPage extends TodoPage

Technically we can instantiate two POMs (AdminTodoPage and OtherTodoPage) and call common step (And I filter todos as "Completed") on one of them. But imagine if these POMs have some state created by previous steps? In that case calling common step on one of them can lead to unexpected behavior.

One of the ideas is to introduce config option statelessPoms: true and allow such situations. But I suggest to investigate more do we really need this. Maybe we can solve it with linear inheritance model.
Could you share your real use-case for such inheritance?

@vitalets vitalets added collecting feedback and removed bug Something isn't working labels Mar 9, 2024
@jzaratei
Copy link
Sponsor Author

jzaratei commented Mar 11, 2024

I got an e2e scenario where I need to select some cards, lets call it homeCardsPage (parent). Then I need to check (assert) those cards selected in two different pages: userCardsPage (child1) and emailCardPage(child2). These last two pages share the same list of cards, but they are different pages with different elements, that's why we could say they are siblings. They all share a global variable (fixture) cardList, with the card selected by the user.

When I select cards (homeCardPage)
Then I check cards selected in userPage (userCardPage)
And I send cards selected by email (emailCardPage)

I know that one way to cover this, would be to have two scenarios, one for checking the userCardPage and the other for emailCardPage.
But In the case of an e2e scenario I should be able to assert the same element in different components (pages).
So, making emailCardPage extends userCardPage, to cover the linear inheritance, could be a workaround, but I don't feel its the best solution, because I'm forcing emailCardPage inherit elements that are not needed.

@vitalets
Copy link
Owner

Fair enough! So in your example, on what fixture should we call the first step I select cards?
I see 3 options:

  1. on homeCardPage
  2. on userCardPage
  3. on emailCardPage

@jzaratei
Copy link
Sponsor Author

In homeCardPage, which is the first page, where the app display the cards, for the user to select them.

@vitalets
Copy link
Owner

@jzaratei
I've improved guess-algorithm in v6.3.0. Now it assumes that all POM classes are stateless by default. It means that in case of several POMs involved in a single scenario, each decorator step will just bind to the corresponding class.

Taking the example above:

When I select cards (homeCardPage)
Then I check cards selected in userPage (userCardPage)
And I send cards selected by email (emailCardPage)

The generated test should be:

  test("my test", async ({ When, Then, And, homeCardPage, userCardPage, emailCardPage }) => {
    await When("I select cards", null, { homeCardPage });
    await Then("I check cards selected in userPage", null, { userCardPage });
    await And("I send cards selected by email", null, { emailCardPage });
  });

If it is still relevant for your project, could you re-check on your side?

Note: to return to previous behavior there is a new config option statefulPoms: true.

@jzaratei
Copy link
Sponsor Author

Yeah, I am refactoring my page objects and now it seems to work as expected. Thank for this upgrade!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants