antonmedv / finder Goto Github PK
View Code? Open in Web Editor NEWCSS Selector Generator ๐บ
License: MIT License
CSS Selector Generator ๐บ
License: MIT License
When the seedMinLength property is set to 20, the execution speed is very slow, and the browser will be stuck for a while. Can the execution speed be improved?
let selector = finder(e.target, {
className: (name) => {
return !name.startsWith('is-');
},
tagName: (name) => true,
seedMinLength: 20,
optimizedMinLength: 15
});
Chrome Dev Tools can generate a "JS Path" for an element:
https://developers.google.com/web/updates/2018/11/devtools#copy
It would be great if finder could generate a Chrome Dev Tools-style JS Path for an element, that would be an important step to developing record/replay integration tests for shadow-dom enabled apps.
AFAICT This is a pre-requisite for checkly/headless-recorder#51
I have come across an issue with my specific use case and the "unique" functionality.
I am using this library to find a path to the element from a root node (its an SVG element). This SVG element happens to be duplicated on the page so when the library runs the unique
check the selectors it generates from the root passed in is not unique (to the page) so i always get no path.
It doesn't look like there is a way to specify that I only need the path to be unique from the root element passed in as that is what I will do the resulting query in. I don't know if this is too much of an edge case to implement into the library but I don't know any other way of doing it (without writing my own).
I will open a pull request to see if I can make this work but any pointers are appreciated.
I am not familiar with TS and couldn't get it to compile due to some errors already present it seems. Would it be simple to add a way to use the root node as the "rootDocument" for unique checking?
I planned on using this for content within a TinyMCE editor document, which is another iframe, but the document
reference is not configurable.
I would suggest a new configuration option:
finder(node, {
document: editor.getDoc()
})
We want to be able to set a timeout on finder. If we are not able to generate CSS selectors in a certain amount of time, we'd like to return early if the timeout is breached.
Is this something that could be possible?
Happy to create a PR and give it a shot as well.
Instead 3 rollbacks by threshold, test selectors generated by single pass by reducing number of checked selectors by heuristic.
Lines 220 to 229 in feef199
My current workaround is:
{
name: name.replace(/^(.+:)(.+)$/, "*|$2"),
}
Examples:
m:math
==> *|math
svg:a
==> *|a
div
==> div
Unfortunately the wildcard *
prefix is necessary because the querySelector()
API does not support namespace prefixes, unlike CSS stylesheets:
https://developer.mozilla.org/en-US/docs/Web/SVG/Element/a
...consequently, for example *|a
will match not only SVG link elements but also HTML hyperlinks! :(
https://www.w3.org/TR/selectors-api/#namespace-prefix-needs-to-be-resolved
I've been doing quite a bit of background research on this space, and finder
seems to be smarter than the competition by quite a bit. I also like the testing on real-world fixtures.
One feature that I'd love to see added is the ability to support HTML attributes. Some attributes like type=password
are very strong signals and oftentimes unique single-level selectors.
Cheers ๐
the option 'maxNumberOfTries' exist in the documantion but when i'm installing the package through npm, the bundled package not conastins this option.
This is occur to me in many sites, when there is large amount of selectors to generate (more the 2000) it crushes.
my conclusion is that it cause by excluding values and names in the attributes.
the new property of "maxNumberOfTries" doesn't help.
Example of my code for the exclude attributes.
excludeByAtributeName=["style", "ng", "on","event"];
excludeAttributeValue=["@","?","$","#","~","&","%","^","*","alert","console.log","confirm"];
attr: (name: string, value: string) =>
!excludeByAtributeName
.some(a => name.includes(a)) &&
!excludeAttributeValue
.some(c => value.includes(c))
Is there bundled browser version?
The script does not work on this website: https://jrdunn.com/
The error is
VM1638:1 Uncaught Error: Selector was not found.
at finder (<anonymous>:1:724)
at HTMLDocument.<anonymous> (<anonymous>:2:20)
I'm importing the default select
and finder
, then doing a test against various HTML dom. To my surprise, finder was not able to produce valid id and was a lot slower than select.
Surely it produced a little selector, but the cost seems too great (4ms vs 19ms).
I tested it against news.hackernews.com, with this selector,
document.querySelector("#hnmain > tbody > tr:nth-child(3) > td > table > tbody > tr:nth-child(2) > td.subtext > a:nth-child(6)")
This is not noticeable if there are less elements,
It's not same everytime, but here are some more tests on same page, just with more elements inside document.body
.
Elements inside document.body: 740
Select: 1.54ms
Finder: 1.84ms
Elements inside document.body: 3496
Select: 4ms
Finder: 16ms
Elements inside document.body: 6941
Select: 6ms
Finder: 31ms
Elements inside document.body: 68951
Select: 60ms
Finder: 506ms
I will try to prepare a re-producable example/benchmark. But it really seems something is not right here.
The benchmark on this repo confirms this. It says it's slower speed but still fast enough for regular usage.
Can someone confirm this issue somehow and how can we address this?
#Offending page
Link
In the 'Save extra with 4 offers' section, there is a custom HTML element
<dptags:querylogoperation methodname="addCount" metric="SOPP:sellerPromotionBTF">
..content
</dptags:querylogoperation>
It worked when I clicked on the same control after refreshing, but I went back and clicked it again and made an error ๐ข
document.addEventListener('click', event => {
const element = event.target;
console.log('element---------------->', element);
const selector = finder(element);
console.log('selector---------------->', selector);
}
success๏ผ
element---------------->'<โimg _ngcontent-serverapp-c96="" src="receive.png" class="h-12 w-12"โ>
selector----------------> .relative:nth-child(3) > .h-12
When the failure๏ผ
element----------------> <โimg _ngcontent-serverapp-c96="" src="receive.png" class="h-12 w-12"โ>
ERROR Error: Can't select any node with this selector: .h-12
at unique (index.js:188) [angular]
at findUniquePath (index.js:161) [angular]
at _loop_1 (index.js:136) [angular]
at bottomUpSearch (index.js:145) [angular]
at default_1 (index.js:79) [angular]
at HTMLDocument. (all-track-action-listener.ts:38) [angular]
at Object.onInvokeTask (core.js:41762) [angular]
at ZoneTask.invokeTask [as invoke] (zone-evergreen.js:480) []
at invokeTask (zone-evergreen.js:1621) []
at HTMLDocument.globalZoneAwareCallback (zone-evergreen.js:1658) []
Add before nth-child fallback, first-match fallback:
Please add support for multiple elements to be provided
For an embedded iframe, I defined root: body
but this then fails if I try to find the body
element since body.querySelectorAll('body')
returns an empty array. This scenario seems to be handled for the default case since the ownerDocument is returned for the rootDocument
instead of the body
. It seems like the root config should allow both Document
and Element
. Then the default for root
could be updated to document
and I think findRootDocument could then be removed.
As a workaround, I am passing the ownerDocument
and ignoring the TypeScript error.
I'm trying to use this in a typescript project as a dependency.
After importing I get this error in the TS compiler:
semantic error TS7016 Could not find a declaration file for module '@medv/finder'. '/Users/abc/xyz/node_modules/@medv/finder/dist/index.js' implicitly has an 'any' type.
Try `npm install @types/medv__finder` if it exists or add a new declaration (.d.ts) file containing `declare module 'medv__finder';
Could you publish the generated typings file? Thanks!
Can you please either support UMD/global or add some usage notes to the README? I assume you're expecting users to include this into source via build pipeline like webpack, but just to play around I tried including it via script
and it fails with this error:
index.js:47 Uncaught ReferenceError: exports is not defined
at index.js:47
Thanks!
So I'm not sure if this is out of scope for this project.
I'm not so much interested as the shortest selector, as the most meaningful one.
From that point of view, any of the following tags are interesting:
https://www.w3schools.com/html/html5_semantic_elements.asp
I'd like those to be always included in the selector if they are present in the ancestor list.
I guess these would have to be qualified if they are not unique, so article:nth-of-type(2)
.
Does this request make any sense or is it a different project?
It took me quite a while to work out what was going on with ids when they started with a numeral:
#\\34 69bb17329ad2642a43d .jss138
IMO, the above is not great for display purposes.
Could we switch to the following format instead?
[id="469bb17329ad2642a43d"] .jss138"
Can we please have a UMD build too? This would help users run this in browser environments as well, using something like unpkg
.
I tried https://unpkg.com/@medv/[email protected]/dist/index.js and it doesn't work in browsers.
Hey.
First off thank you for creating Finder. It is awesome and it has been the back bone to some awesome features we have on our site.
We are having a small issue with how finder handles errors when it can't find nodes etc, because it throws them. This is a problem because it's picked up by Sentry, and it is creating a lot of noise in our error reporting dashboard.
Would you be open to adding on option for, or changing the error handling so it's a little more graceful? I would personally be happy to write a PR for this if you accept the idea of the improvement.
Thanks again!
Generation hangs if you try to generate css selector for
Hi, for this test document, I am attempting to find a selector for <p>Line 1</p>
When no selector root is explicitly set, it returns:
#module > p
However, when I set the root selector to document.querySelector("#module")
, it still returns the same thing. I would think it should return a selector that doesn't contain #module since we are using that as the selector root?
test("root:setandfind", (t) => {
const html = `
<div class="document">
<div id="module">
<span id="span1"></span>
<p>Line 1</p>
<div id="introduction">
<h2>
Introduction
</h2>
<p>Line 2</p>
</div>
</div>
</div>
`;
document.write(html);
let rootelem = document.querySelector("#module");
let pElement = rootelem.querySelector("p");
const css = selector(pElement);
console.log("Default selector (no options): ", css);
const newcss2 = selector(pElement, { root: rootelem });
console.log("Selector after setting root to (#module): ", newcss2);
});
Output:
Default selector (no options): #module > p
Selector after setting root to (#module): #module > p
As you can see, the selector still seems to reference the ID of the root selector.
Is this a bug or am I misunderstanding the behavior of setting the root selector?
I'd like to customize the penalty so that the selectors this generates primarily use a specific attribute, but if that attribute isn't present on a descendant fallback to using a classname.
What are your thoughts on adding a new option called penalties that could look like this for example
penalties: { tag: 10, attr: 0, id: 8, classname: 10, any: 20 }
For some pages ->
function sort(paths: Iterator<Path> & Iterable<Path>): Path[] {
return Array.from(paths).sort((a, b) => penalty(a) - penalty(b))
}
Output of "Array.from(paths)" is returning => [ ] (empty array)
Adding a polyfill substitute in this method is solving the issue ->
ex- code
function transformToArray <a>(a: Iterator<a>) {
let result: a[] = []
let current = a.next()
while(current.done == false) {
result.push(current.value)
current = a.next()
}
return result
}
function sort(paths: Iterator<Path> & Iterable<Path>): Path[] {
return transformToArray(paths).sort((a, b) => penalty(a) - penalty(b))
}
Let me know, I can raise a PR for the same.
Angular dynamically adds attributes called โ_ngcontent-#โ to elements by default.
As those attribute names are regenerated on every load, the finder should ignore these attributes while generating css pointers.
Example Angular 9
Pointer generated by your tool:
[_ngcontent-vrx-c260=โโ] > [href=โmailto:[email protected]โ]
after a reload the same elements pointer would need to be:
[_ngcontent-wok-c260=โโ] > [href=โmailto:[email protected]โ]
Different Angular versions have different ng-content id structures, but it would be fixed by generally ignore all attributes that start with "_ngcontent"
Hi there,
I'm using SizzleJs (https://sizzlejs.com) library as extension for my selectors.
Is that possible to extend default finder
selectors with that?
Thanks
Can be tested with something like:
<form id="test">
<input type="text" name="id">
</form>
In this case input.id
in index.ts:168
will be the input element, not "test".
input.getAttribute('id')
might work better.
If someone wants to work on this. :)
I've never seen a .ts in an npm module before.
My typescript tries to typecheck it and complains, and I haven't been able to find a way to get it turned off. I'm not sure why a source file would belong in the published module anyhow-- could we remove it?
In my case I'm getting a Node
as my event target but finder
expects a Element
which makes TypeScript complain about it.
document.querySelector('.123') => document.querySelector('[class="123"]')
document.querySelector('#123') => document.querySelector('[id="123"]')
https://benfrain.com/when-and-where-you-can-use-numbers-in-id-and-class-names/
I love this library, but I am having many troubles to use it in some contexts, like for example in a Chrome Extension I am implementing.
The fact that it is packaged like a module is making things more complicate.
Is is possible to have a version that is not using modules?
Thanks
First of all, thank you for the library! I think this is the best most robust CSS query generator. I use it in a lot of my projects.
For one of my project, I needed to generate css selector for ALL dom elements on the page. For this specific case, finder is also perfect, unfortunately if page is big, time taken to generate unique css selector for all dom elements can add up, up to 250 seconds, which is unusable for my use case.
After doing performance analysis, "unique" function is called a lot of times to figure out if selector is not unique, and it is called repeatedly with the same arguments. Caching this function drops execution time for that specific page (and other pages) from 250 seconds to under 1 second at the cost of memory.
something like this worked for me:
const cache = new Map();
function unique(a) {
const key = JSON.stringify(a);
if (cache.has(key)) {
return cache.get(key);
} else {
switch (rootDocument.querySelectorAll(selector(a)).length) {
case 0:
cache.set(key, !1);
throw new Error(`Can't select any node with this selector: ${selector(a)}`);
case 1:
cache.set(key, !0);
return !0;
default:
cache.set(key, !1);
return !1;
}
}
}
In IE 11, I get a syntax error that looks to be due to the use of an arrow function, which is not supported in IE 11.
The following HTML element, (taken from the Hacker News website):
<tr class="athing" id="244329320> ... </tr>
will generate the following, invalid, selector:
#\32 4432930
Providing js variant to use it in static pages will be helpful to many . please consider including it in repo or some kind of CDN link will be helpful
So let's say I have generated a lovely selector like
.opened .single-link > span
My problem is that while the opened
class is unique on the page at the moment, with my next click, I will open another 'section' of the page, and then there will be two opened
classes present, and my original selector is no longer unique.
I guess I could use a MutationObserver to watch for class changes and build up a list of 'volatile' classnames, however this will not work if the HTML has exactly one .opened
class at the start.
So I was thinking that it would be nice to have the ability to generate a 'backup' selector, which would be the equivalent of the following
finder(target,{
className: (e) => e !== 'opened' && e !== 'single-link',
})
Is that something that is worthwhile to elevate to a single config option, which takes into account what would normally be generated, but then operates on alternative basis that whatever is currently unique, may not be in future?
I am working on a POC to allow element style changes & preview and the library is throwing error: "Selector was not found" for all the elements (event.target)on a specific page. Even if root, element values are properly being passed to the finder function.
Sample webpage where it is failing - https://www.sanaullastore.com/
It is able to return the selector while using extension version of code but failing if same code is added via javascript.
I've been struggling of bundling the source code past week, I've tried a lot of ways to bundle it but nothing seems working well with my product. so I think It'd be really helpful for me and others as well If you provide prebundled source.
On this page: https://www.buildzoom.com/contractor/live-wire-electric-1-llc
Calling finder on the element returned by this selector: "#credentials > div.team-wrap.row > div:nth-child(2) > div:nth-child(1) > div.biz_info.no-bottom-border.biz-info- > div.bz-content-wrapper > div > table > tbody > tr:nth-child(1) > td.table-item.u-hyphenate"
will make finder return the selector ".table-item", which is non-unique.
Cause a warning:
(Emitted value instead of an instance of Error) Cannot find source file '../src/index.ts': Error: Can't resolve '../src/index.ts'
Currently the library tries to generate selectors that are unique and as a short as possible. Which is great in most cases, but sometimes in a changing web-page, it results in selectors that are not future-proof .
If a page has one link to start with, the library would probably produce a
as selector for that link. While this works for the webpage as is, it may not work when the webpage has another link added soon. (Example, take blogs or new websites for example where the links can keep changing). In such a case, having the ability to specific which attribute to always keep a part of the selector would help. (in this case, href
is a good candidate)
Perhaps some option where one can specify attributes to keep at a tag basis. And when the library sees an element have one of these tags, it includes it in the final selector even if that doesn't necessarily result in a shorter selector.
{
prioritizeAttributes: {
tag: [<the attributes>]
}
}
Great library btw ๐
Thank you
Hi there ๐
I've run into a bug where finder will throw an error if two elements on the same page have the same ID. This is the error being thrown:
Line 69 in 1757700
Very small reproducible example:
<h1 id="duplicate_id">This ID is not unique</h1>
<h1 id="duplicate_id" >This ID is not unique either</h1>
Maybe an unoptimized selector could be returned instead here?
Hello @antonmedv, thank you for the amazing library!
I oppened this issue to let you know that the CDN(https://medv.io/finder/finder.js) in README.md stopped working.
For anyone having trouble the alternative CDN link https://cdn.jsdelivr.net/npm/@medv/finder is working.
Hi, Could you please tell me what is the difference between cssesc and CSS.escape ? Is it possible to replace cssesc with CSS.escape ?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.