1

OK, so I have a simple test with POM.

This is one of the PageObjects Class:

import { Expect, Page, Locator } from "@playwright/test";

export class InventoryPage {
  readonly page: Page;
  readonly addToCartBtn: Locator;
  readonly addToCartBtnAll: Promise<Array<Locator>>;
  readonly cartIcon: Locator;

  constructor(page: Page) {
    this.page = page;
    this.addToCartBtn = page.locator("button.btn_inventory");
    this.addToCartBtnAll = page.locator("button.btn_inventory").all();
    this.cartIcon = page.locator(".shopping_cart_badge");
  }

  async addAllItemsToCart() {
    for (let item of await this.addToCartBtnAll) await item.click();
  }

  async addRandomItemToCart() {
    await this.addToCartBtn
      .nth(Math.floor(Math.random() * (await this.addToCartBtnAll).length))
      .click();
  }
}

problematic are:

readonly addToCartBtnAll: Promise<Array<Locator>>;
this.addToCartBtnAll = page.locator("button.btn_inventory").all();

When I create a object of this class in a test in beforeEach hook, the test ends with error before it even start...

Error: locator.all: Execution context was destroyed, most likely because of a navigation
    at InventoryPage

Does someone have any idea whats wrong with this locator().all() at the page obejct?

I have an workaround of this issue (need to avoid Promise<Array<Locator>> type and use .all() at the rest of the code) but still wondering why Playwright doesn't like a type of Promise<Array<Locator>> in constructors...

ggorlen
  • 44,755
  • 7
  • 76
  • 106
  • Showing your test case would be nice so we have a [mcve]. You're probably instantiating your POM, then calling a `page.goto` which destroys the context the `.all()` promise is working in. – ggorlen Apr 17 '23 at 23:20

1 Answers1

1

.all() is a locator action that causes Playwright to perform automation with the browser. All actions need to be awaited 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.

ggorlen
  • 44,755
  • 7
  • 76
  • 106