k6
k6 copied to clipboard
Locator API
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.boundingBox([options])
- [ ] locator.evaluate(pageFunction[, arg, options])
- [ ] locator.evaluateHandle(pageFunction[, arg, options])
- [ ] locator.screenshot([options])
- [ ] locator.scrollIntoViewIfNeeded([options])
- [ ] locator.selectText([options])
Locator-specific methods
Allows users to move between the selected element.
- [ ] grafana/k6#4251
- [ ] locator.page()
- [ ] page.frameLocator(selector) — creates a new locator for a frame (usually for iframes)
- [ ] locator.first()
- [ ] locator.last()
- [ ] locator.nth(index)
- [ ] https://github.com/grafana/k6/pull/4797
- [ ] locator.allInnerTexts()
- [ ] locator.allTextContents()
- [ ] locator.elementHandles()
- [ ] locator.evaluateAll(pageFunction[, arg])
- [ ] grafana/k6#4452
- [ ] grafana/k6#4453
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]).
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?
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.
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.
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.htmltolocator.htmlwhen I'm done.~