k6 icon indicating copy to clipboard operation
k6 copied to clipboard

Locator API

Open robingustafsson opened this issue 4 years ago • 4 comments

We started the development of the first version on May 16th.

Strict forwarders (milestone: v0.7.0)

  • [x] grafana/xk6-browser#660

Strict forwarders (milestone: v0.4.0)

The locator methods that forward calls to the Frame methods with the strict mode on (meaning: only select a single element).

  • [x] grafana/xk6-browser#308
  • [x] grafana/xk6-browser#334
  • [x] grafana/xk6-browser#311
  • [x] grafana/xk6-browser#331
  • [x] grafana/xk6-browser#359
  • [x] grafana/xk6-browser#346
  • [x] grafana/xk6-browser#348
  • [x] grafana/xk6-browser#349
  • [x] grafana/xk6-browser#347
  • [x] grafana/xk6-browser#351
  • [x] grafana/xk6-browser#350
  • [x] grafana/xk6-browser#338
  • [x] grafana/xk6-browser#339
  • [x] grafana/xk6-browser#341
  • [x] grafana/xk6-browser#343
  • [x] grafana/xk6-browser#344
  • [x] grafana/xk6-browser#357
  • [x] grafana/xk6-browser#345
  • [x] grafana/xk6-browser#353
  • [x] grafana/xk6-browser#352
  • [x] grafana/xk6-browser#358
  • [x] grafana/xk6-browser#356
  • [x] grafana/xk6-browser#342
  • [x] grafana/xk6-browser#335
  • [x] grafana/xk6-browser#360

Just-in-time element finder methods

The locator methods wrap around existing ElementHandle methods. For example:

// locator method
async evaluate<R, Arg>(pageFunction: structs.PageFunctionOn, arg?: Arg, options?: TimeoutOptions): Promise<R> {
  return this._withElement(h => h.evaluate(pageFunction, arg), options?.timeout);
}

// wrapped method (jsHandle.ts)
async evaluate<R, Arg>(pageFunction: structs.PageFunctionOn, arg?: Arg): Promise < R > { ... }

_withElement queries the element using waitForSelector in the current frame and returns a disposed element handle:

  private async _withElement<R>(task: (handle: ElementHandle<SVGElement | HTMLElement>, timeout?: number) => Promise<R>, timeout?: number): Promise<R> {
    timeout = this._frame.page()._timeoutSettings.timeout({ timeout });
    const deadline = timeout ? monotonicTime() + timeout : 0;

    return this._frame._wrapApiCall<R>(async () => {
      const result = await this._frame._channel.waitForSelector({ selector: this._selector, strict: true, state: 'attached', timeout });
      const handle = ElementHandle.fromNullable(result.element) as ElementHandle<SVGElement | HTMLElement> | null;
      if (!handle)
        throw new Error(`Could not resolve ${this._selector} to DOM Element`);
      try {
        return await task(handle, deadline ? deadline - monotonicTime() : 0);
      } finally {
        await handle.dispose();
      }
    });
  }

Locator-specific methods

Allows users to move between the selected element.

Blocked (milestone: unknown)

  • [ ] grafana/k6#4451 — Blocked 🚨
  • [ ] grafana/k6#4346 — Blocked 🚨
  • [ ] grafana/k6#4450 — Blocked 🚨
  • [ ] grafana/xk6-browser#354 — Blocked 🚨

Explanation

Add support for Locator, a way to capture a pointer to DOM element(s) without attaching to an actual DOM node as ElementHandle does. Sort of a just-in-time version of querying the DOM and getting an ElementHandle back to do actions on.

Relevant links:

  • Playwright docs: https://playwright.dev/docs/api/class-locator and https://playwright.dev/docs/api/class-page#page-locator

Below are the May 11th, 2022, findings for this feature.

Summary

The whole idea of locators is that you can carry on selectors with you—without dealing with actual elements. Locators enable the Page Object Model for reducing code duplication and improving test maintenance.

A locator is created with a selector for a specific frame. When you invoke one of the Locator methods, they query the frame and run the action. Since they query the frame, each call can resolve to a different DOM element.

Benefits:

  • Allows enabling working with dynamic web pages and SPAs like Swelte, React, Vue, etc.
  • Allows creating a Page Object Model (POM).
  • POM reduces code duplication.
  • POM improves test maintenance.

Good explanation of the strict mode and locators API:

  • https://www.youtube.com/watch?v=LczBDR0gOhk&t=180s
  • https://github.com/microsoft/playwright/blob/main/docs/src/selectors.md
  • https://github.com/microsoft/playwright/blob/main/docs/src/locators.md

Key knowledge

  • Each locator belongs to a single frame.
  • Every time locator is used for some action, an up-to-date DOM element is located on the page.
  • Locator is resolved to the element immediately before acting, so a series of actions on the same locator can be performed on different DOM elements. What would happen if the DOM structure between those actions changed.
  • PW recommends using ElementHandle in rare cases when you need to perform extensive DOM traversal on a static page. For all user actions and assertions, PW recommends using a locator instead.
  • The difference between the Locator and ElementHandle is that the latter points to a particular element, while Locator captures the logic of how to retrieve that element.
  • Locators are strict. All operations on locators that imply some target DOM element will throw an exception if more than one element matches the given selector.
  • In lazy-loaded pages, it can be useful to wait until an element is visible with locator.waitFor([options]).

robingustafsson avatar Nov 12 '21 08:11 robingustafsson

I checked out the Playwright Locator API and its underlying code. It seems like we need to do this for every ElementHandle action, etc. It looks involved.

Maybe we should separate this issue into sub-issues and handle the main issue together?

Could you also check out the PW docs and tell me WDYT, @imiric?

inancgumus avatar Mar 01 '22 11:03 inancgumus

Yeah, this seems more involved than I originally thought. We should definitely split it up somehow. Let's discuss it tomorrow.

We don't have to finish everything for v0.3.0, and we likely won't have time to, but we can release it gradually. It should be easier once we lay the groundwork and start ticking off each method.

imiric avatar Mar 02 '22 09:03 imiric

My findings on this feature are in the description. I'll start working on this next week as there is some stuff that I'm trying to figure out.

inancgumus avatar May 11 '22 11:05 inancgumus

TODO

I'm noticing a lot of repetitions.

  • I'll refactor this out after we upgrade to Go 1.18 (#279) and use generics/interface somehow (depending on which one is clearer).
  • ~Additionally, I will rename strict_link.html to locator.html when I'm done.~

inancgumus avatar Jun 02 '22 08:06 inancgumus