Coder Social home page Coder Social logo

fczbkk / css-selector-generator Goto Github PK

View Code? Open in Web Editor NEW
510.0 9.0 90.0 4.29 MB

JavaScript object that creates unique CSS selector for given element.

License: MIT License

HTML 15.58% JavaScript 30.30% TypeScript 54.06% Shell 0.05%
dom element css-selector css

css-selector-generator's Introduction

CSS Selector Generator

JavaScript object that creates a unique CSS selector for a given DOM element or multiple DOM elements.

It also generates shorter selectors and is faster and/or more robust than many other libraries - see this comparison and select the best alternative for your use case.

Install

Add the library to your project via NPM or Yarn.

npm install css-selector-generator
yarn add css-selector-generator

Then include it in your source code:

import { getCssSelector } from "css-selector-generator";

How to use

Simplest way to use it is to provide an element reference, without any options.

<body>
  <!-- targetElement -->
  <div class="myElement"></div>
</body>
getCssSelector(targetElement);
// ".myElement"

Typical example is to create a selector for any element that the user clicks on:

// track every click
document.body.addEventListener("click", function (event) {
  // get reference to the element user clicked on
  const element = event.target;
  // get unique CSS selector for that element
  const selector = getCssSelector(element);
  // do whatever you need to do with that selector
  console.log("selector", selector);
});

Usage without NPM

If you don't want to use this library with NPM, you can download it directly from the "build" folder and insert it to your HTML document directly. In this case, the library is wrapped in namespace CssSelectorGenerator. So the usage would look something like this:

<!-- link the library -->
<script src="build/index.js"></script>
<script>
	CssSelectorGenerator.getCssSelector(targetElement)
</script

Usage with virtual DOM (e.g. JSDOM)

If you want to use this library with Node, usually for testing, don't require it directly into the Node process. It will not work, because there's no window object and there are no elements to select. Instead, you have to add the library to the virtual window object. Here are instructions how to do it in JSDOM, other libraries will work in a similar way: https://github.com/jsdom/jsdom/wiki/Don't-stuff-jsdom-globals-onto-the-Node-global

Multi-element selector

This library also allows you to create selector targeting multiple elements at once. You do that by calling the same function, but you provide an array of elements instead of single element:

<body>
  <!-- firstElement -->
  <div class="aaa bbb"></div>
  <!-- secondElement -->
  <span class="bbb ccc"></span>
</body>
getCssSelector([firstElement, secondElement]);
// ".bbb"

If it is not possible to construct single selector for all elements a standalone selector for each element will be generated:

<body>
  <!-- firstElement -->
  <div></div>
  <!-- secondElement -->
  <span></span>
</body>
getCssSelector([firstElement, secondElement]);
// "div, span"

Fallback

getCssSelector determines the shortest CSS selector for parent -> child relationship, from the input Element until the Root Element.

If there is no unique selector available for any of these relationships (parent -> child), a fallback of * will be used for this relationship.

#wrapper > * > div > .text

In some cases, this selector may not be unique (e.g. #wrapper > * > div > *). In this case, it will fall back to an entire chain of :nth-child selectors like:

":nth-child(2) > :nth-child(4) > :nth-child(1) > :nth-child(12)"

Options

Selector types

You can choose which types of selectors do you want to use:

<body>
  <!-- targetElement -->
  <div class="myElement"></div>
</body>
getCssSelector(targetElement, { selectors: ["class"] });
// ".myElement"
getCssSelector(targetElement, { selectors: ["tag"] });
// "div"

Order of selector types defines their priority:

getCssSelector(targetElement, { selectors: ["class", "tag"] });
// ".myElement"
getCssSelector(targetElement, { selectors: ["tag", "class"] });
// "div"

Valid selector types are:

  • id
  • class
  • tag
  • attribute
  • nthchild
  • nthoftype

Root element

You can define root element, from which the selector will be created. If root element is not defined, document root will be used:

<body>
  <div class="myRootElement">
    <!-- targetElement -->
    <div class="myElement"></div>
  </div>
</body>
getCssSelector(targetElement);
// ".myRootElement > .myElement"
getCssSelector(targetElement, {
  root: document.querySelector(".myRootElement"),
});
// ".myElement"

Blacklist

If you want to ignore some selectors, you can put them on the blacklist. Blacklist is an array that can contain either regular expressions, strings and/or functions.

In strings, you can use an asterisk (*) as a wildcard that will match any number of any characters.

Functions will receive a selector as a parameter. They should always return boolean, true if it is a match, false if it is not. Any other type of return value will be ignored.

<body>
  <!-- targetElement -->
  <div class="firstClass secondClass"></div>
</body>
getCssSelector(targetElement, { blacklist: [".firstClass"] });
// ".secondClass"
getCssSelector(targetElement, { blacklist: [".first*"] });
// ".secondClass"
getCssSelector(targetElement, { blacklist: [/first/] });
// ".secondClass"
getCssSelector(targetElement, {
  blacklist: [(input) => input.startsWith(".second")],
});
// ".secondClass"

You can target selectors of any types using the blacklist.

getCssSelector(targetElement, {
  blacklist: [
    // ID selector
    "#forbiddenId",
    // class selector
    ".forbiddenClass",
    // attribute selector
    "[forbidden-attribute]",
    // tag selector
    "div",
  ],
});

Whitelist

Same as blacklist option, but instead of ignoring matching selectors, they will be prioritised.

<body>
  <!-- targetElement -->
  <div class="firstClass secondClass"></div>
</body>
getCssSelector(targetElement, { whitelist: [".secondClass"] });
// ".secondClass"
getCssSelector(targetElement, { whitelist: [".second*"] });
// ".secondClass"
getCssSelector(targetElement, { whitelist: [/second/] });
// ".secondClass"

Combine within selector

If set to true, the generator will try to look for combinations of selectors within a single type (usually class names) to get better overall selector.

<body>
  <!-- targetElement -->
  <div class="aaa bbb"></div>
  <div class="aaa ccc"></div>
  <div class="bbb ccc"></div>
</body>
getCssSelector(targetElement, { combineWithinSelector: false });
// "body > :nth-child(1)" - in this case no single class name is unique
getCssSelector(targetElement, { combineWithinSelector: true });
// ".aaa.bbb"

This option is set to true by default. It can be set to false for performance reasons.

Combine between selectors

If set to true, the generator will try to look for combinations of selectors between various types (e.g. tag name + class name) to get better overall selector.

<body>
	<!-- targetElement -->
	<div class="aaa"></div>
	<div class="bbb"></div>
	<p class="aaa"></div>
</body>
getCssSelector(targetElement, { combineBetweenSelectors: false });
// "body > :nth-child(1)" - in this case no single class name or tag name is unique
getCssSelector(targetElement, { combineBetweenSelectors: true });
// "div.aaa"

This option is set to true by default. It can be set to false for performance reasons.

Include tag

This option will add tag selector type to every selector:

<body>
  <!-- targetElement -->
  <div class="myElement"></div>
</body>
getCssSelector(targetElement, { includeTag: true });
// "div.myElement"

Max combinations

This is a performance optimization option that can help when trying to find a CSS selector within elements that contain large numbers of class names (e.g. because of frameworks that create atomic styles) or other attributes.

In such case, the number of possible combinations between class names can be too large (it grows exponentially) and can significantly slow down selector generation. In reality, if the selector is not found within first few combinations, it usually won't be found within the rest of combinations.

getCssSelector(targetElement, { maxCombinations: 100 });

Max candidates

Performance optimization option, similar to maxCombinations. This does limit a total number of selector candidates for each element.

You should use it in cases, when there are not too many class names and attributes, but they are numerous enough to produce large number of combinations between them.

getCssSelector(targetElement, { maxCandidates: 100 });

Bug reports, feature requests and contact

If you found any bugs, if you have feature requests or any questions, please, either file an issue on GitHub or send me an e-mail at [email protected]

License

CSS Selector Generator is published under the MIT license.

css-selector-generator's People

Contributors

dandv avatar danschalow avatar dependabot[bot] avatar fczbkk avatar fregante avatar indilo53 avatar joscha avatar kdmurthy avatar kilobyte2007 avatar niranjan94 avatar zongzi531 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

css-selector-generator's Issues

CssSelectorGenerator escapes the selector string

CssSelectorGenerator escapes the selector string.

eg.

3[src='https\3A \/\/site\.com\/folder\/file\.js']

instead of

3[src='https://site.com/folder/file.js']

Is it possible to get the raw (unescaped) selector?

IE 11 Syntax Error

I get a Syntax Error in IE 11 simply by including the following in my code:

import getCssSelector from "css-selector-generator";

The error prevents my page from loading.

I do not get the error in Chrome or Firefox.

Namespaces not considered a valid selector

Hi

I've come across a problem with namespaced html tags, where <h:section> is causing a problem.

Any ideas about where I should be looking in your code to make allowances for namespaces?

Thanks

Tom

Failed to import it in test environment (node)

import getCssSelector from 'css-selector-generator';

// raise "window is not undefined"

Then I try to import it form source instead from 'build/index.js'.

import getCssSelector from 'css-selector-generator/src/index.js';

// raise "document is not defined"

I use jsdom to provide a browser like environment. But I didn't expose the window or Document object to global context.

Is it possible to access these global variables by the element that passed to getCssSelector(). So there's less global variable dependencies (e.g: using elem.ownerDocument to access Document)

Large amount of classes locks up getCombinations

I'm seeing an issue where a website with a large amount of classes on the <html> element causes the getCombinations to lock up on the following code:

for (i = k = 0, ref = items.length - 1; 0 <= ref ? k <= ref : k >= ref; i = 0 <= ref ? ++k : --k) {
  for (j = l = 0, ref1 = result.length - 1; 0 <= ref1 ? l <= ref1 : l >= ref1; j = 0 <= ref1 ? ++l : --l) {
    result.push(result[j].concat(items[i]));
  }
}

In my case, I have 47 classes that are returned by getAllSelectors, which I assume are generated by the UI libraries running on the website.

CssSelectorGenerator is not defined

Hi,

I've spent ages trying to solve this problem and wasn't able to figure it out, so I'm posting an issue as a last resort, sorry.

I'm running css-selector-generator on random websites, depending on it being available on the window object.
One website I'm using seems to have "define" already defined, so it never ends up assigning the CssSelectorGenerator to the window object. I'm reluctant to blow away the "define" object to make it work, as I don't know what implications that would have on the rest of the site that probably depends on it.

Do you know a way around this?

Allow custom filter functions as blacklist/whitelist

The selector filters only accept regexes and strings:

whitelist: [] as Array<CssSelectorMatch>,
blacklist: [] as Array<CssSelectorMatch>,

export type CssSelectorMatch = RegExp | string

Would you accept a PR to add support for functions? Example:

getCssSelector(
  targetElement,
  { blacklist:
      [
        // ID selector
        '#forbiddenId',

        // predicate function
        selector => !isCoolSelector(selector)
      ]
  }
)

Add option to use -nth-child as little as possible

Hi,

I have the problem that your library falls back to something like this: :nth-child(11) > :nth-child(2) > :nth-child(1) > :nth-child(9) but what I need is a selector that goes as far as possible without using :nth-child, i.e. like this one: div#__next > div.styles__MainContent-ghnxum-0.iIIhTW > div.styles__ContainerRoot-evpclk-2.kowoAH:nth-child(9) because all the nth-child-numbers change during the pageload.

So the feature request would be to make as long selectors as possible without using nth-child. This is perfect for me but it outputs a selector matching multiple elements :(

I have tried those configuration options for your utility but it still prefers shortness over a sustainably working selector:

whitelist: ['class', 'tag', 'id', 'attribute'],
          combineWithinSelector: true,
          combineBetweenSelectors: true,
          includeTag: true

Attribute selectors probably should use quotes around the attribute value

Hi,

First, thanks for this library. I noticed that if you're using the optional 'attribute' selector style, the resulting selector is not quoted, which can lead to some errors if it decides to write a selector such as a[href=some-page.htm] or similar.

I've added a modification locally that just adds quotes (so a[href="some-page.htm"] or whatever), would you like a Pull Request with that change?

Related, I've also added an attribute_blacklist (defaults to ['id', 'class']) and attribute_whitelist option (defaults to []) option, at the same level as the existing selectors option. Basically, my use case wants to "prefer" selectors that are based on link targets, so now my usage is like:

new CssSelectorGenerator
  selectors: ['id', 'class', 'attribute', 'tag', 'nthchild']
  attribute_whitelist: ['href','src']

I've also added a class_blacklist option (defaults to []), if there are CSS class names that shouldn't be considered by getClassSelectors. In my case there are a handful of "internal" class names that should be ignored when generating a selector.

Let me know if you'd like PRs for any of these changes. They are all pretty trivial in terms of the actual code changes.

Feature request : multiple elements as parameters

Hi again,

Do you think this could be possible to generate a selector from multiple elements ?

For example :

<body>
  <article class="article1"></article>
  <article class="article2"></article>
</body>

When we pass element article1 element, this generates .article1. (working as this today).

And then, if we pass an array of elements (let's say article1 and article2), this would generate article

Issues on Facebook

Hello,

I am trying to use css-selector-generator on various websites through console. If I copy and paste build/index.js files in console, this works great :

CssSelectorGenerator.getCssSelector($('body'))
prints "body"

But on Facebook, when I do this, I get an error :
Uncaught ReferenceError: CssSelectorGenerator is not defined

I checked window and document objects, and, indeed, neither CssSelectorGenerator nor getCssSelector are defined.

Any idea about what could cause this issue ?

Many thanks,

Root element ignored in options, full path returned

Context page : http://histo.io

Code to execute in order to test it (x = 165, y = 305):

function testCase(x, y) {
  const root = document.querySelector('nav > ul > li > ul');
  const result = CssSelectorGenerator.getCssSelector(document.elementFromPoint(x, y), { blacklist: ['[href=*]', '[src=*]'], root: root });
  console.log(result);
  // :root > :nth-child(2) > :nth-child(4) > :nth-child(2) > :nth-child(1) > :nth-child(1) > :nth-child(2) > :nth-child(5) > :nth-child(1);
}

CSS selector generator silently failing on some sites

Hi - me again (sorry).

I'm running this bit of code in the console

document.addEventListener( "mouseover", function(event) { var CSSstring = findCSSPath(event.target); console.log(CSSstring); });

where the findCSSPath function is just a wrapper for your css selector generator.

On the whole, this works as expected. I've tested it on quite a few sites. However, on

http://observer.com/

I'm getting a silent failure from the code. No errors, but no CSS path sent to the console. Eventually it causes Chrome to crash. There's clearly something odd about the structure of the site.

I don't know if this is the kind of thing you want reported, but I'm struggling to find the problem.

[Feature Request] Grouping Similar Elements

When a very different element is added to a group of very similar elements, the result tends to become the fallback instead of "grouping" similar elements. For example below, the simplest selector is [aaa], [ddd]. Regardless of the arrangements of the children, the selector should still be the same.

  it('should group similar elements (2-2)', () => {
    root.innerHTML = `
      <div aaa="bbb"></div>
      <div aaa="ccc"></div>
      <p ddd="eee"></p>
      <p ddd="fff"></p>
    `
    const result = getCssSelector(Array.from(root.children))
    assert.ok(testSelector(Array.from(root.children), '[aaa], [ddd]'))  // passes
    assert.equal(result, '[aaa], [ddd]')  // fails
  })

  it('should group similar elements (3-1)', () => {
    root.innerHTML = `
      <div aaa="bbb"></div>
      <div aaa="ccc"></div>
      <div aaa="ddd"></div>
      <p eee="fff"></p>
    `
    const result = getCssSelector(Array.from(root.children))
    assert.ok(testSelector(Array.from(root.children), '[aaa], [eee]'))  // passes
    assert.equal(result, '[aaa], [eee]')  // fails
  })

Output:

    โœ– should subgroup similar elements (2-2)
      Chrome Headless 92.0.4515.159 (Mac OS 10.15.7)
    AssertionError: expected '[aaa=\'bbb\'], [aaa=\'ccc\'], [ddd=\'eee\'], [ddd=\'fff\']' to equal '[aaa], [ddd]'

    โœ– should subgroup similar elements (3-1)
      Chrome Headless 92.0.4515.159 (Mac OS 10.15.7)
    AssertionError: expected '[aaa=\'bbb\'], [aaa=\'ccc\'], [aaa=\'ddd\'], p' to equal '[aaa], [eee]'

Doesn't work as a CommonJS module

$ node
Welcome to Node.js v15.14.0.
Type ".help" for more information.
> require('css-selector-generator')
Uncaught ReferenceError: self is not defined

Support: How to import it in typescript?

Sorry I pressed ENTER without notice!

I saw it has typescript definitions but I can not find the way to imported so the types are recognized. What I am doing for now is declaring the module my sefl with:

declare interface CssSelectorGeneratorParams {
  selectors?: string[];
  root?: Element;
  blacklist?: string[];
  whitelist?: string[];
  combineWithinSelector?: true;
  combineBetweenSelectors?: true;
  includeTag?: true;

}
declare module 'css-selector-generator' {
  export default function getCssSelector(targetElement: Element, options: CssSelectorGeneratorParams): string;
}

But of course it could break at any time and it does not have and documentation included.

No Service Worker support

I'm loading this library in a service worker and it fails to run because it attempts to use document even if it's getCssSelector isn't called.

I think the library should not expect these globals before it's called.

The same happens if I try to run build/index.js in node (just as a test), except it complains about self

Can changes be made so the module doesn't break the rest of the app by just "existing"?

Can't find variable: getCssSelector

I was hoping to get some help to start using this package. I followed the instructions on the read me page, but when calling the getCssSelector function, I get the following error: Can't find variable: getCssSelector. Below is the minimally reproducible code. I am running MacOS 11.2.2 and Safari 14.0.3. I installed from npm and bundled with browserify, hence the local file reference to 'CSS-Selector-Generator.js' in the code below.

<div>
<span>Field:</span><input type='text' />
</div>
<script type="text/javascript" src="CSS-Selector-Generator.js"></script>
<script type="text/javascript">
var body = document.getElementsByTagName("BODY")[0];
body.addEventListener('click', function(event) {
	var selector = getCssSelector(event.target);
	alert(selector);
}, false);
</script>

Content of 'CSS-Selector-Generator.js':

(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}(g.StandaloneCSSSelectorGenerator || (g.StandaloneCSSSelectorGenerator = {})).js = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({"css-selector-generator":[function(require,module,exports){
!function(t,r){"object"==typeof exports&&"object"==typeof module?module.exports=r():"function"==typeof define&&define.amd?define([],r):"object"==typeof exports?exports.CssSelectorGenerator=r():t.CssSelectorGenerator=r()}(self,(function(){return(()=>{var t={426:(t,r,n)=>{var e=n(529);function o(t,r,n){Array.isArray(t)?t.push(r):t[n]=r}t.exports=function(t){var r,n,i,a=[];if(Array.isArray(t))n=[],r=t.length-1;else{if("object"!=typeof t||null===t)throw new TypeError("Expecting an Array or an Object, but `"+(null===t?"null":typeof t)+"` provided.");n={},i=Object.keys(t),r=i.length-1}return function n(u,c){var f,l,s,p;for(l=i?i[c]:c,Array.isArray(t[l])||(void 0===t[l]?t[l]=[]:t[l]=[t[l]]),f=0;f<t[l].length;f++)o((p=u,s=Array.isArray(p)?[].concat(p):e(p)),t[l][f],l),c>=r?a.push(s):n(s,c+1)}(n,0),a}},529:t=>{t.exports=function(){for(var t={},n=0;n<arguments.length;n++){var e=arguments[n];for(var o in e)r.call(e,o)&&(t[o]=e[o])}return t};var r=Object.prototype.hasOwnProperty}},r={};function n(e){if(r[e])return r[e].exports;var o=r[e]={exports:{}};return t[e](o,o.exports,n),o.exports}n.n=t=>{var r=t&&t.__esModule?()=>t.default:()=>t;return n.d(r,{a:r}),r},n.d=(t,r)=>{for(var e in r)n.o(r,e)&&!n.o(t,e)&&Object.defineProperty(t,e,{enumerable:!0,get:r[e]})},n.o=(t,r)=>Object.prototype.hasOwnProperty.call(t,r),n.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var e={};return(()=>{"use strict";n.r(e),n.d(e,{default:()=>W,getCssSelector:()=>G});var t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol?"symbol":typeof t};function r(r){return null!=r&&"object"===(void 0===r?"undefined":t(r))&&1===r.nodeType&&"object"===t(r.style)&&"object"===t(r.ownerDocument)}function o(t){var n=t.parentNode;if(n)for(var e=0,o=n.childNodes,i=0;i<o.length;i++)if(r(o[i])&&(e+=1,o[i]===t))return[":nth-child(".concat(e,")")];return[]}var i=" > ";function a(t){return Object.assign({},u,{root:t.ownerDocument.querySelector(":root")})}var u={selectors:["id","class","tag","attribute"],includeTag:!1,whitelist:[],blacklist:[],combineWithinSelector:!0,combineBetweenSelectors:!0},c=new RegExp(["^$","\\s","^\\d"].join("|")),f=new RegExp(["^$","^\\d"].join("|")),l=["nthoftype","tag","id","class","attribute","nthchild"],s=n(426),p=n.n(s);function y(t,r){(null==r||r>t.length)&&(r=t.length);for(var n=0,e=new Array(r);n<r;n++)e[n]=t[n];return e}function d(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],r=[[]];return t.forEach((function(t){r.forEach((function(n){r.push(n.concat(t))}))})),r.shift(),r.sort((function(t,r){return t.length-r.length}))}function v(t){return t.replace(/[|\\{}()[\]^$+?.]/g,"\\$&").replace(/\*/g,".+")}function m(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];if(0===t.length)return new RegExp(".^");var r=t.map((function(t){return"string"==typeof t?v(t):t.source})).join("|");return new RegExp(r)}function b(t){return function(t){if(Array.isArray(t))return g(t)}(t)||function(t){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(t))return Array.from(t)}(t)||function(t,r){if(t){if("string"==typeof t)return g(t,r);var n=Object.prototype.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?g(t,r):void 0}}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function g(t,r){(null==r||r>t.length)&&(r=t.length);for(var n=0,e=new Array(r);n<r;n++)e[n]=t[n];return e}function h(t,r){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:document,e=n.querySelectorAll(r);return 1===e.length&&e[0]===t}function A(t,r){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:document,e=b(n.querySelectorAll(r)),o=e.filter((function(t){return t.parentNode===n}));return 1===o.length&&o.includes(t)}function S(t){for(var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:j(t),e=[],o=t;r(o)&&o!==n;)e.push(o),o=o.parentElement;return e}function j(t){return t.ownerDocument.querySelector(":root")}function w(t){return[N(t.tagName.toLowerCase())]}function O(t,r){(null==r||r>t.length)&&(r=t.length);for(var n=0,e=new Array(r);n<r;n++)e[n]=t[n];return e}var x=m(["class","id","ng-*"]);function E(t){var r=t.nodeName,n=t.nodeValue;return"[".concat(r,"='").concat(N(n),"']")}function I(t){var r=t.nodeName;return!x.test(r)}function T(t){return function(t){if(Array.isArray(t))return C(t)}(t)||function(t){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(t))return Array.from(t)}(t)||function(t,r){if(t){if("string"==typeof t)return C(t,r);var n=Object.prototype.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?C(t,r):void 0}}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function C(t,r){(null==r||r>t.length)&&(r=t.length);for(var n=0,e=new Array(r);n<r;n++)e[n]=t[n];return e}var $=":".charCodeAt(0).toString(16).toUpperCase(),M=/[ !"#$%&'()\[\]{|}<>*+,./;=?@^`~\\]/;function N(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";return t.split("").map((function(t){return":"===t?"\\".concat($," "):M.test(t)?"\\".concat(t):escape(t).replace(/%/g,"\\")})).join("")}var U={tag:w,id:function(t){var r=t.getAttribute("id")||"",n="#".concat(N(r));return!c.test(r)&&h(t,n,t.ownerDocument)?[n]:[]},class:function(t){return(t.getAttribute("class")||"").trim().split(/\s+/).filter((function(t){return!f.test(t)})).map((function(t){return".".concat(N(t))}))},attribute:function(t){return(r=t.attributes,function(t){if(Array.isArray(t))return O(t)}(r)||function(t){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(t))return Array.from(t)}(r)||function(t,r){if(t){if("string"==typeof t)return O(t,r);var n=Object.prototype.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?O(t,r):void 0}}(r)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()).filter(I).map(E);var r},nthchild:o,nthoftype:function(t){var r=w(t)[0],n=t.parentElement;if(n)for(var e=n.querySelectorAll(r),o=0;o<e.length;o++)if(e[o]===t)return["".concat(r,":nth-of-type(").concat(o+1,")")];return[]}};function q(t,r){if(t.parentNode)for(var n=(a=function(t,r){return function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=r.selectors,e=r.combineBetweenSelectors,o=r.includeTag,i=e?d(n):n.map((function(t){return[t]}));return o?i.map(P):i}(t,r).map((function(r){return n=t,e={},r.forEach((function(t){var r=n[t];r.length>0&&(e[t]=r)})),p()(e).map(R);var n,e})).filter((function(t){return""!==t}))}(function(t,r){var n=r.blacklist,e=r.whitelist,o=r.combineWithinSelector,i=m(n),a=m(e);return function(t){var r=t.selectors,n=t.includeTag,e=[].concat(r);return n&&!e.includes("tag")&&e.push("tag"),e}(r).reduce((function(r,n){var e=function(){var t=arguments.length>1?arguments[1]:void 0;return(arguments.length>0&&void 0!==arguments[0]?arguments[0]:[]).sort((function(r,n){var e=t.test(r),o=t.test(n);return e&&!o?-1:!e&&o?1:0}))}(function(){var t=arguments.length>1?arguments[1]:void 0,r=arguments.length>2?arguments[2]:void 0;return(arguments.length>0&&void 0!==arguments[0]?arguments[0]:[]).filter((function(n){return r.test(n)||!t.test(n)}))}(function(t,r){return(U[r]||function(){return[]})(t)}(t,n),i,a),a);return r[n]=o?d(e):e.map((function(t){return[t]})),r}),{})}(t,r),r),(u=[]).concat.apply(u,function(t){if(Array.isArray(t))return y(t)}(i=a)||function(t){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(t))return Array.from(t)}(i)||function(t,r){if(t){if("string"==typeof t)return y(t,r);var n=Object.prototype.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?y(t,r):void 0}}(i)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}())),e=0;e<n.length;e++){var o=n[e];if(A(t,o,t.parentNode))return o}var i,a,u;return"*"}function P(t){return t.includes("tag")||t.includes("nthoftype")?T(t):[].concat(T(t),["tag"])}function D(t,r){return r[t]?r[t].join(""):""}function R(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=T(l);return t.tag&&t.nthoftype&&r.splice(r.indexOf("tag"),1),r.map((function(r){return D(r,t)})).join("")}function _(t,r){(null==r||r>t.length)&&(r=t.length);for(var n=0,e=new Array(r);n<r;n++)e[n]=t[n];return e}function k(t){var r,n=S(t).map((function(t){return o(t)[0]})).reverse();return[":root"].concat((r=n,function(t){if(Array.isArray(t))return _(t)}(r)||function(t){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(t))return Array.from(t)}(r)||function(t,r){if(t){if("string"==typeof t)return _(t,r);var n=Object.prototype.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?_(t,r):void 0}}(r)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}())).join(i)}function B(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return Object.assign({},a(t),r)}function G(t){for(var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=B(t,r),e=S(t,n.root),o=[],a=0;a<e.length;a++){o.unshift(q(e[a],n));var u=o.join(i);if(h(t,u,n.root))return u}return k(t,n.root)}const W=G})(),e})()}));
},{}]},{},[])("css-selector-generator")
});

This looks like a great library, just hoping someone could help find my presumably simple error so I can actually start using it.

Add package to npm

Not working as expected with custom HTML elements

Just tried library on youtube.com and it seems that it doesn't work with custom HTML elements at all.
When trying to get selector for action menu button I got following:

:root > :nth-child(2) > :nth-child(4) > :nth-child(12) > :nth-child(4) > :nth-child(1) > :nth-child(8) > :nth-child(1) > :nth-child(1) > :nth-child(6) > :nth-child(5) > :nth-child(1) > :nth-child(1) > :nth-child(1) > :nth-child(2) > :nth-child(3) > :nth-child(1) > :nth-child(3) > :nth-child(1) > :nth-child(1)

but it should be something like:

ytd-page-manager > ytd-browse > ytd-two-column-browse-results > #primary > #contents > ytd-rich-item-renderer > #content > ytd-rich-grid-media > #details > ytd-menu-renderer > ytd-icon-button > button

according to:
image

I tried passing several combination of options to getCssSelector function but didn't help much.

Am I missing something?

Attribute css selector test error.

Demo url : https://jsfiddle.net/fivesmallq/amfw7vtm/3/

image

In the HTML querySelectorAll function, [width=30%] is wrong, [width="30%"] is right.

result.push("[" + attribute.nodeName + "=" + attribute.nodeValue + "]");

Maybe you should retain the double quotes?

    CssSelectorGenerator.prototype.getAttributeSelectors = function(element) {
      var attribute, blacklist, k, len, ref, ref1, result;
      result = [];
      blacklist = ['id', 'class'];
      ref = element.attributes;
      for (k = 0, len = ref.length; k < len; k++) {
        attribute = ref[k];
        if (ref1 = attribute.nodeName, indexOf.call(blacklist, ref1) < 0) {
          result.push("[" + attribute.nodeName + "=\"" + attribute.nodeValue + "\"]");
        }
      }
      return result;
    };

Detect and exclude random selectors

With more and more websites using CSS modules, css-selector-generator often generates fickle selectors that are only valid until the next deployment.

Would it be possible to automatically detect and exclude these selectors, optionally?

Screen Shot 15 Screen Shot 14 Screen Shot 16

`3.0.1` Bundle Size Problem

As shown by https://bundlephobia.com/package/css-selector-generator, the bundle size of version 3.0.1 jumped significantly, this being because of the introduction of polyfills in f0b9d62.

Regarding this:

  1. Should polyfilling this not be left up to the consumer, as otherwise, the consumer will be forced to use code they likely don't need to have (for my use case, I don't need any of these polyfills, so I will need to stay on [email protected], or migrate to another library)?
  2. Unless I am missing something, I think f0b9d62 results in a very large amount of core-js being included in the build (potentially all of it?), which very likely isn't necessary (see https://babeljs.io/docs/en/babel-preset-env for how to configure babel to only include the polyfills that are needed)?

Also, I believe that https://github.com/fczbkk/css-selector-generator-benchmark#fczbkk-css-selector-generator is no longer correct for the following reasons:

  1. This library does have dependencies.
  2. This library is MIT licensed.

What is the expected value of `this` in css-selector-generator.js ?

I am puzzle by the use of this in css-selector-generator.js. An anonymous function is called and we fix its context with .call(this). It seems that the expectation is that the global object will be somehow aliased to the window object. It's the case with Electron. Is it a general convention that you rely upon?

Can't use sanitizeSelectorItem anymore in version 3.4.4

I currently use version 2.1.0 and trying to upgrate to the latest version. In latest version the sanitizeSelectorItem function is not exported anymore.

The code i'm using is:

import {sanitizeSelectorItem} from 'css-selector-generator/src/utilities-selectors.js'

That file (src/utilities-selectors.js) has become a Type Script file which I can't import anymore.

Error when using root option in jsdom

In the test case below, things work fine until I supply the root option. I have not tried whether the same problem occurs in a browser.

self = globalThis; // css-selector-generator fails without `self` โ€” separate issue?
cssSelectorGenerator = require('css-selector-generator').default
jsdom = require('jsdom').JSDOM;

html = '<b>lorem <i>ipsum</i> dolor <u><i>amet</i> yada <i>yada</i></u></b>';
doc = (new jsdom(html)).window.document;
target = doc.querySelector('u i');
root = doc.querySelector('u');

cssSelectorGenerator(target, { root });

This fails with:

Uncaught DOMException [SyntaxError]: '> i' is not a valid selector

Also, when using some other elements as root, it returns without errors, but the root seems to have been ignored:

b = doc.querySelector('b')
cssSelectorGenerator(target, { root: b });
cssSelectorGenerator(target);

Both of the above return the same value: ':root > :nth-child(2) > :nth-child(1) > :nth-child(2) > :nth-child(1)'

Convert to [attribute] without value

Very cool project! ๐Ÿ’ฏ

Is it possible to further simplify the selectors if the attribute values are different? For example the output below:

[href='https\3A \/\/www\.w3schools\.com\/css\/css_attribute_selectors\.asp'] h3
[href='https\3A \/\/developer\.mozilla\.org\/en-US\/docs\/Web\/CSS\/Attribute_selectors'] h3
[href='https\3A \/\/css-tricks\.com\/almanac\/selectors\/a\/attribute\/'] h3

Can be simplified even more like this:

[href] h3

You can replicate this as these are just title links from Google Search page.

RegExp flags are removed

convertMatchListToRegExp() removes all flags as it uses the RegExp.source property (see utilities-data.ts line 47)

Not so easy to define what should be the correct behavior but I would advocate that the 'i' flag is very useful ;)
BTW the 'g' flag is not useful as css-selector-generator only tests the RegExp..

Why not setting a 'i' flag if at least one of the RegExp of the list defines it ?

I can make the PR if your want to.

Regards

In Shadow DOM & custom root Fallback selector is used when a better selector is available

The test page

<body>
    <div id="shadow-host">
    </div>
    <script>
        var shadow = document.querySelector('#shadow-host').attachShadow({mode: 'open'});
        shadow.innerHTML = '<div id=shadow-content><p>Shadow Root</p><div id="nested-shadow-host"></div></div>';
        var nestedShadow = shadow.querySelector('#nested-shadow-host').attachShadow({mode: 'open'});
        nestedShadow.innerHTML = '<div id="nested-shadow-content"><p>Nested Shadow Root</p></div>';
    </script>
</body>

Test cases:

1. Element outside the shadow DOM (Works)

var shadowHostEl = document.getElementById("shadow-host");
return getCssSelector(shadowHostEl)

Returns #shadow-host โœ…

2. Element inside the shadow DOM with the shadow root as the custom root (Works)

var shadowHostEl = document.getElementById("shadow-host");
var levelOneRoot = shadowHostEl.shadowRoot;
var levelOneEl = levelOneRoot.querySelector("p");

return getCssSelector(levelOneEl, { root: levelOneRoot})

Returns p โœ…

3. Another shadow host in shadow DOM with the shadow root as the custom root (Fails)

var shadowHostEl = document.getElementById("shadow-host");
var levelOneRoot = shadowHostEl.shadowRoot;
var levelOneEl = levelOneRoot.getElementById("nested-shadow-host");

return getCssSelector(levelOneEl, { root: levelOneRoot})

Returns :root > :nth-child(1) > :nth-child(2) โŒ
Expected #nested-shadow-host โœ…


Awesome library btw ๐Ÿ˜„
Very useful.

More robust selector heuristics - GitHub READMEs example

Say we wanted a selector for the "How to use" heading in the README of this project. Currently the returned selector is article > :nth-child(5).

That heading though has as a first element a link with a unique id. This <hX><a id="..."> pattern is pretty common, and an h2 + #... selector would be more robust for that case.

Any thoughts on extending the library with a mechanism to take such heuristics into consideration?

feature: Configurable attribute blacklist

Attributes are currently blacklisted in a non-configurable value:

export const ATTRIBUTE_BLACKLIST = convertMatchListToRegExp([
  'class',
  'id',
  // Angular attributes
  'ng-*',
]);

The values could be configured in the config object like:

{
  attributeBlacklist: [
    'href', 
    'data-*',
  ]
}

Default values should be maintained, if no value is set.

Get selector for multiple items

<ul clas="nav-bar">
<li class="nav-item"><a href="#" class="nav">Item</a></li>
<li class="nav-item"><a href="#" class="nav">Item</a></li>
<li class="nav-item"><a href="#" class="nav">Item</a></li>
</ul>

I want to get: ul.nav-bar > li.nav-item > a.nav when I click on any a tag.

How to do it?

Thanks

[Feature request] Ability to prioritize certain attributes over keeping the selectors as short as possible.

Related comments about a similar feature request by @jribbens : #51 (comment), #51 (comment)


The problem

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 . The best example is what @jribbens has outlined in the above comments

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)

How could the library support this ?

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>]
  }
} 

ES6

Looks like a robust library... Might you consider moving from CoffeeScript to ES6, including with ES6 Modules export (I'd recommend Rollup)? Thanks!

Unreliable for Ember.js Apps

Ember has a tendency to add selectors to certain elements that like #ember1452 (specifically that begin with ember). However, these are IDs that do not necessarily reference the same elements on page loads or even when you re-render that element. They are unique, but only to the current rendered instance of the page.

An example of a selector CssSelectorGenerator created that breaks after a page reload:

#ember1452 > .fn-card__target

I could see this being tricky to fix.

Some possible solutions that come to mind (while having very little knowledge of the codebase):

  • Blacklist selector prefixes (e.g. IDs that begin with ember) as possible unique selectors
  • Allow an optional second argument on getSelector to pass in a function to evaluate whether or not a selector is acceptable that evaluates for each level (Developers would be responsible for maintaining a blacklist, and returning false means "try again with a different selector")
  • Change the precedence for each selector to prioritize tag name over IDs and classes (potentially yucky performance implications, other issues)

Perhaps someone else has better ideas for how to solve this?

Edit: I did not fully read the README. I can fix this by prioritizing class names over IDs. Sorry for the unnecessary email!

Exclude some unwanted classes from being used in selector

Websites that use dynamic content, sometimes also use dynamic classes.
Lets say we have button (e.g. class="btn") and we want to change its background color every second with extra class (e.g. class="btn btn-green") by adding / removing it.
So, because I know "btn-green" class is dynamic, I would like exclude it from being used.

Use this amazing functionality in playwright testing tool

Hi,

I'm trying to integrate this library with playwright.
I tried to integrate it using addScriptTag (https://playwright.dev/docs/api/class-page#page-add-script-tag)

With all options:

<!-- link the library -->
<script src="build/index.js"></script>
<script>
	CssSelectorGenerator.getCssSelector(targtElement)
</script>

And

const { window } = new JSDOM(``, { runScripts: "dangerously" });
const myLibrary = fs.readFileSync("../../whatever.js", { encoding: "utf-8" });

const scriptEl = window.document.createElement("script");
scriptEl.textContent = myLibrary;
window.document.body.appendChild(scriptEl);

And

import getCssSelector from 'css-selector-generator';

// track every click
document.body.addEventListener('click', function (event) {
  // get reference to the element user clicked on
  const element = event.target;
  // get unique CSS selector for that element
  const selector = getCssSelector(element);
  // do whatever you need to do with that selector
  console.log('selector', selector);
});

None of them works for me.

I would appreciate any assistant with it.

Failed to execute 'querySelector' on 'Document': '<classname>' is not a valid selector.

Creating a new issue for v2.0.0 (as recommended here: #18 (comment))

Problem

I am running into an issue in which class names along the tree contains are invalid for use by selectors. While class selectors do have name restrictions, class names do not, which could lead to errors when the targeted element is a child of an element that uses a classname that is invalid as a class selector.

Example

A reduced example that would trigger it:

HTML:

<!doctype html>
<html>
    <head>
    </head>
    <body>
        <div class="valid-class-selector">
            <a href="/a0">Anchor0</a>
        </div>
        <div class="1-invalid-class-selector">
            <a href="/a1">Anchor1</a>
        </div>
    </body>
</html>

JS:

getCssSelector(document.querySelectorAll("a")[1]);

Error:

Uncaught DOMException: Failed to execute 'querySelectorAll' on 'Element': '.1-invalid-class-selector' is not a valid selector.

Possible fix

Taken from my original comment in the older issue: #18 (comment), would be to either wrap querySelectorAll in try/catch and return false:

try { result = root.querySelectorAll(selector); } catch { return false; }

or if you want to keep the functional logic clean of try/catch, another approach could be to add try/catch around the same selector against an empty document fragment

try { document.createDocumentFragment().querySelectorAll(selector); } catch { return false; }
result = root.querySelectorAll(selector);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.