.all()
is a locator action that causes Playwright to perform automation with the browser. All actions need to be await
ed since they communicate with a totally separate process, the browser, which takes time to finish its work.
If you declare a locator but don't take an action, nothing happens in the browser. The code doesn't need to be awaited.
Asynchronous constructors aren't really a thing. Constructors return an object, not a promise. Promises are used to communicate with network or system APIs, and those actions shouldn't be part of building a local process object.
The POM approach works like this:
- Define all relevant locators for the page in the constructor synchronously. This keeps the locators together in one "configuration" area.
- Provide a series of high-level action methods that use the pre-defined locators to perform low-level actions (
.click()
, .type()
, .evaluate()
, etc) and (possibly) assertions (expect(...)
). Your current method names are good examples of this.
- One action method should be
goto
. This is typically the first thing you call after instantiating the POM. It's usually a wrapper on page.goto
and maybe a few other steps necessary to get the page set up.
For your code, there's no need for separate locators. Code like
this.addToCartBtn = page.locator("button.btn_inventory");
this.addToCartBtnAll = page.locator("button.btn_inventory").all();
can be simply:
this.addToCartBtn = page.locator("button.btn_inventory");
Since locators are re-evaluated on each action call, this locator can work in both a singular and plural context. However, if you're running in strict mode (which you should be), you'll probably want to pick a more precise selector than this so you don't have to use discouraged .first()
, .last()
or nth()
actions.
As for your error, your test case probably instantiates the POM, which kicks off the .all()
action on the page
, then you run page.goto
which destroys the context for .all()
:
const inventoryPage = new InventoryPage(page); // kicks off .all()
await page.goto(...); // destroys context
await inventoryPage.addAllItemsToCart(); // throws
If you adhere to the standard POM pattern I've described above, goto
will be your first step after instantiation, but by the time you start taking actions and performing assertions on your locators, the page won't be incurring unexpected navigations:
const inventoryPage = new InventoryPage(page); // doesn't trigger anything
await inventoryPage.goto(); // note the difference!
await inventoryPage.addAllItemsToCart(); // take actions (other than goto)
While you could avoid the error by moving your page.goto()
above your POM instantiation, I'd recommend against it since it's hard to reason about constructors that trigger async side effects and have picky ordering needs. Part of the idea with the POM is to encapsulate the set-up and navigation steps and not leave that to the test case to have to figure out.
In general, Playwright discourages selecting elements with CSS selectors because they're not user-facing attributes. Prefer selecting by role, text, titles, labels or values.
I can't make a more specific recommendation without seeing your use case, but I can tell you that your code doesn't adhere to recommended Playwright idioms currently.
I'd also caution against random numbers in tests. A nondeterministic test can create difficult-to-replicate or occasional failures, which can be a nightmare to debug.