Skip to content
New issue

Have a question about this project?Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of serviceand privacy statement.We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Add these cypress like locators #23718

Open
RantiBabaopened this issue Jun 14, 2023 · 17 comments
Open

[Feature] Add these cypress like locators #23718

RantiBabaopened this issue Jun 14, 2023 · 17 comments

Comments

@RantiBaba
Copy link

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.

@RantiBaba RantiBaba changed the title [Feature] [Feature] Add these cypress like locators Jun 14, 2023
@yury-s
Copy link
Member

yury-s commented Jun 14, 2023

Related request#16155

@harmin-parra
Copy link

Alsoparent(),in addition toparents()

@harmin-parra
Copy link

harmin-parra commented Nov 3, 2023

also scrolling

scrollTo('bottom')
scrollTo('topRight', { duration: 2000 })
scrollTo('center', { easing: 'linear' })
scrollTo(250, 250)
scrollTo('75%', '25%')

@lucasdonato
Copy link

Please, this feature will be great

@WeberLucas
Copy link

It will be wonderful

@joaoh9
Copy link

joaoh9 commented Dec 31, 2023

this would be game changer!

@fasatrix
Copy link

This feature would be greatly appreciated as these days frameworks likeMuiare 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)

@marinamarin91
Copy link

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.

@cctui-dev
Copy link

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.

@bogdanbotin
Copy link

It will be great to have this feature!

@marinamarin91
Copy link

It's almost 1 year since this feature task is open. Will it be taken into consideration soon? Thank you!

@sindresteen
Copy link

This would make my testing much easier!

@basickarl
Copy link

Think it's a little crazy how these things were not built-in to begin with.

@alexkuc
Copy link

alexkuc commented May 15, 2024

While not available out of the box, I believe itmightbe possible to polyfill the missing functionality byadding 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.

@nissanthen
Copy link

A sibling locator would be a gamechanger.

@lamlamla
Copy link

This will reduce my locator params length sooooo much. We need these, especially theparent()method

@alexkuc
Copy link

alexkuc commented Jun 19, 2024

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…

How-to example

test('Sample',async({page})=>{
constlocator=page.getByRole('heading');

constquery=find(locator);// will try 5 times by default
constquery2=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)
});

find.ts

import{xpath}from'./xpath';
import{iterator}from'./iterator';

importtype{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
*/
exportfunctionfind(source:Locator,tries:number=5){
constlocator=augmentLocator(source);
construn=iterator(locator,tries);
const{query}=xpath;

constsanitize={
/**
* Remove any non Alpha betic characters from the given string
*/
tag:(input:string):string=>input.replaceAll(/[^A-z]/g,''),
};

constfindSelf=()=>locator.xpath(query.self);

constfindChilden=()=>locator.xpath(query.children).all();

constfindRoot=()=>locator.xpath(query.root);

constfindParent=Object.assign(()=>locator.xpath(query.parent),{
until:{
/**
* @param parent Parent ID (full match & case sensitive)
*/
id:(parent:string):Promise<Locator>=>{
returnrun(query.parent,(el,id)=>el.id===id,parent);
},
/**
* @param parent CSS class (full match & case sensitive!)
*/
class:(parent:string):Promise<Locator>=>{
returnrun(query.parent,(el,css)=>el.classList.contains(css),parent);
},
/**
* @param parent Tag for which we will search, e.g. `<span>` or `img
*/
tag:(parent:string):Promise<Locator>=>{
consttag=sanitize.tag(parent).toUpperCase();
returnrun(query.parent,(el,tag)=>el.tagName===tag,tag);
},
},
});

constsibling={
next:locator.xpath(query.sibling.next),
prev:locator.xpath(query.sibling.previous),
};

constfindSibling={
/**
* @param includingSelf Should the source [locator](https://playwright.dev/docs/api/class-locator) be included or not? (default `false`)
*/
all:async(includingSelf:boolean=false)=>{
constprev=awaitsibling.prev.all();
constnext=awaitsibling.next.all();
if(!includingSelf)return[...prev,...next];
return[...prev,locator,...next];
},
next:Object.assign(()=>sibling.next.first(),{
all:()=>sibling.next.all(),
until:{
id:(id:string)=>{
returnrun(query.sibling.next,(el,id)=>el.id===id,id);
},
/**
* @param css Target css class (case sensitive & full match!)
*/
class:(css:string):Promise<Locator>=>{
returnrun(query.sibling.next,(el,css)=>el.classList.contains(css),css);
},
/**
* @param tag Tag for which we will search, e.g. `<span>` or `img
*/
tag:(tag:string):Promise<Locator>=>{
consttargetTag=sanitize.tag(tag).toUpperCase();
returnrun(query.sibling.next,(el,tag)=>el.tagName===tag,targetTag);
},
},
}),
prev:Object.assign(()=>sibling.prev.last(),{
all:()=>sibling.prev.all(),
until:{
id:(id:string)=>{
returnrun(query.sibling.previous,(el,id)=>el.id===id,id);
},
class:(css:string)=>{
returnrun(query.sibling.previous,(el,css)=>el.classList.contains(css),css);
},
tag:(tag:string)=>{
consttargetTag=sanitize.tag(tag).toUpperCase();
returnrun(query.sibling.previous,(el,tag)=>el.tagName===tag,targetTag);
},
},
}),
};

return{
self:findSelf,
/**
* Implementing root xpath locator in Playwright is problematic
* because unless the locator is node type 'document', query
* will *always* prepend a leading dot making it relative
* @link https://github /microsoft/playwright/blob/e1e6c287226e4503e04b1b704ba370b9177a6209/packages/playwright-core/src/server/injected/xpathSelectorEngine.ts#L21-L22
*/
// root: findRoot,
children:findChilden,
parent:findParent,
sibling:findSibling,
};
}

interfaceAugmentedLocatorextendsLocator{
/**
* Execute XPath query
* @param query XPath query
*/
xpath:(query:string)=>Locator;
}

/**
* Add method `xpath` to [locator](https://playwright.dev/docs/api/class-locator)
* @param locator
*/
constaugmentLocator=(locator:Locator):AugmentedLocator=>{
returnObject.assign(locator,{
xpath:(query:string)=>{
if(!query.startsWith('xpath='))query='xpath='+query;
returnlocator.locator(query);
},
});
};

iterator.ts

importtype{ElementHandle,JSHandle,Locator}from'@playwright/test';
importtype{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
*/
exportfunctioniterator(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 /a/69183016/4343719) StackOverflow answer (even though it's for Puppeteer the same principle still applies to Playwright).
*/
returnasync<EextendsElement=Element,AextendsArgument=Argument>(
selector:string,
evaluateFn:((element:E,argument:A)=>Promise<boolean>)|((element:E,argument:A)=>boolean),
argument:A=undefinedasA
):Promise<Locator>=>{
constquery=makeXPathQuery(selector);
lettargetLocator:Locator=locator;
letcount:number=0;

do{
targetLocator=targetLocator.locator(query).first();
constresult=awaittargetLocator.evaluate(evaluateFn,argument);
if(result)returntargetLocator;
}while(count!==tries);

thrownewError('Internal error!',);
};
}

constmakeXPathQuery=(selector:string):string=>{
returnselector.startsWith('xpath=')?selector:'xpath='+selector;
};

typeElement=SVGElement|HTMLElement;
typeArgument=Serializable|JSHandle|ElementHandle|undefined;

xpath.ts

constqueries={
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()',
},
}asconst;

exportconstxpath={
query:queries,
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests