Open RantiBaba opened 1 year ago
Related request https://github.com/microsoft/playwright/issues/16155
Also parent(), in addition to parents()
also scrolling
scrollTo('bottom')
scrollTo('topRight', { duration: 2000 })
scrollTo('center', { easing: 'linear' })
scrollTo(250, 250)
scrollTo('75%', '25%')
Please, this feature will be great
It will be wonderful
this would be game changer!
This feature would be greatly appreciated as these days frameworks like Mui
are making testing really challenging because of the agnostic way of creating selectors that are rarely unique (I have used xpath as fallback plan however very ugly solution)
This will be great to have (moving from Testcafe to Playwright and sometimes it is harder to go bottom-up to find an element which appears several times in the page and it has dynamic ids and no other element that can be used). For example : editing an element in the page (there are many) and the only thing that makes the difference between them is some specific text set by me which is the last element in the DOM.
Adding parent(), parents(), child(), children(), sibling() would be so good. It would allow us to traverse the DOM with native Playwright functionality. Working on a multi-language site, I need to either flood my page with test ids, rely on css selectors or xpath.
It will be great to have this feature !
It's almost 1 year since this feature task is open. Will it be taken into consideration soon? Thank you!
This would make my testing much easier!
Think it's a little crazy how these things were not built-in to begin with.
While not available out of the box, I believe it might be possible to polyfill the missing functionality by adding custom selectors. I do agree it would be nice to have it out-of-the-box, but we don't have that yet… If my time permits, I might give it a shot.
A sibling locator would be a gamechanger.
This will reduce my locator params length sooooo much. We need these, especially the parent()
method
This is my take at tackling this issue. Below is a quick example and the source code follows. I don't have the capacity to maintain a FOSS library hence providing the source directly. If someone wants to take this code (given that credit will be given) and convert it to a library, by all means! I've briefly tested the code and it seems okay but it is possible there are bugs in it…
```typescript test('Sample', async ({ page }) => { const locator = page.getByRole('heading'); const query = find(locator); // will try 5 times by default const query2 = find(locator, 99); // will try 99 before throwing an error query.children(); // get all children of locator (@return Locator[]) query.parent(); // get immediate parent of the locator (@return Locator) query.parent.until.class(''); // keep iterating "up" until parent matches target class (without leading dot!) (@return Locator) query.parent.until.id(''); // keep iterating "up" until parent matches target id (without leading #!) (@return Locator) query.parent.until.tag(''); // keep iterating "up" until parent matches target tag (@return Locator) query.self(); // points to itself, just for completeness sake (@return Locator) query.sibling.all(); // get all siblings of locator, excludes self by default (@return Locator[]) query.sibling.next(); // get the next sibling (@return Locator) query.sibling.next.all(); // get all next siblings (@return Locator[]) query.sibling.next.until.class(''); // keep iterating "forward" until sibling matches target class (without leading dot!) (@return Locator) query.sibling.next.until.id(''); // keep iterating "forward" until sibling matches target id (without leading #!) (@return Locator) query.sibling.next.until.tag(''); // keep iterating "forward" until sibling matches target id (@return Locator) query.sibling.prev(); // get previous sibling of locator (@return Locator) query.sibling.prev.all(); // get all previous siblings (@return Locator[]) query.sibling.prev.until.class(''); // keep iterating "backwards" until sibling matches target class (without leading dot!) (@return Locator) query.sibling.prev.until.id(''); // keep iterating "backwards" until sibling matches target id (without leading #!) (@return Locator) query.sibling.prev.until.tag; // keep iterating "backwards" until sibling matches target tag (@return Locator) }); ```
```typescript
import { xpath } from './xpath';
import { iterator } from './iterator';
import type { Locator } from '@playwright/test';
/**
* Inpspired by Cypress queries
* @param source Starting locator
* @param tries How many times to iterate before throwing an error (default: `5`)
* @link https://docs.cypress.io/api/table-of-contents#Queries
*/
export function find(source: Locator, tries: number = 5) {
const locator = augmentLocator(source);
const run = iterator(locator, tries);
const { query } = xpath;
const sanitize = {
/**
* Remove any non alphabetic characters from the given string
*/
tag: (input: string): string => input.replaceAll(/[^A-z]/g, ''),
};
const findSelf = () => locator.xpath(query.self);
const findChilden = () => locator.xpath(query.children).all();
const findRoot = () => locator.xpath(query.root);
const findParent = Object.assign(() => locator.xpath(query.parent), {
until: {
/**
* @param parent Parent ID (full match & case sensitive)
*/
id: (parent: string): Promise
```typescript
import type { ElementHandle, JSHandle, Locator } from '@playwright/test';
import type { Serializable } from 'child_process';
/**
* This function "iterates" (do-while loop) until the given predicate is fullfilled or the number of allowed tries is reached and an error is thrown
* @param locator Source locator
* @param tries How many times to iterate before throwing an error
*/
export function iterator(locator: Locator, tries: number = 5) {
/**
* "Iterate" (do-while loop) until the `evaluateFn` either returns `true` or the allowed number of `tries` is reached and an error is thrown
* @param selector XPath selector which would allow recursive looping, e.g. `parent::node()` or `following-sibling::node()`
* @param evaluateFn A predicate function which receives the current element as its first argument and the custom supplied argument as its second argument. Since this function is run *inside* whatever browser Playwright is using, it will *not* inherit scope, which is why we need to supply the custom argument manually (if one is required)
* @param argument Custom argument we supply to the predicate function (optional, defaults to `undefined`). Has to be [a serializable object](https://developer.mozilla.org/en-US/docs/Glossary/Serializable_object) so live DOM nodes are no-go. See [this](https://stackoverflow.com/a/69183016/4343719) StackOverflow answer (even though it's for Puppeteer the same principle still applies to Playwright).
*/
return async
```typescript const queries = { root: '/', self: 'self::node()', parent: 'parent::node()', children: 'self::node()/child::node()', sibling: { next: 'self::node()/following-sibling::node()', previous: 'self::node()/preceding-sibling::node()', }, } as const; export const xpath = { query: queries, }; ```
I’ve been using Cypress for a long time now and have enjoyed it quite a lot. A colleague asked that I try Playwright, and I must say WOW!, just WOW!
I wouldn’t say I’ve fallen in love with Playwright yet as I’m still experimenting with it at the moment however I’m liking it already. I think there a a few things Cypress did to make testing easy especially their locator commands. I think Playwright might just win me over completely if they add similar Cypress commands like: contain(), parents(), parentsUntil(), sibling(), next(), prev(), children(), within(), etc.