Bug: Inheritance Issue when more than one child is issue in the same scenario
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
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?
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.
Fair enough! So in your example, on what fixture should we call the first step I select cards?
I see 3 options:
- on
homeCardPage - on
userCardPage - on
emailCardPage
In homeCardPage, which is the first page, where the app display the cards, for the user to select them.
@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.
Yeah, I am refactoring my page objects and now it seems to work as expected. Thank for this upgrade!