├── .gitignore
├── .gitmodules
├── README.md
├── config
└── webpack.medv.finder.js
├── index.html
├── package-lock.json
├── package.json
├── test.js
└── vendor
└── snowflake
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /.idea/
3 | /temp/
4 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "vendor/requirejs"]
2 | path = vendor/requirejs
3 | url = git@github.com:jrburke/requirejs.git
4 | [submodule "vendor/selector-generator"]
5 | path = vendor/selector-generator
6 | url = git@github.com:tildeio/selector-generator.git
7 | [submodule "vendor/dompath"]
8 | path = vendor/dompath
9 | url = git@github.com:jhartikainen/dompath.git
10 | [submodule "vendor/CSSelector.js"]
11 | path = vendor/CSSelector.js
12 | url = git@github.com:stevoland/CSSelector.js.git
13 | [submodule "vendor/ellocate.js"]
14 | path = vendor/ellocate.js
15 | url = git@github.com:bimech/ellocate.js.git
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CSS Selector Generator Benchmark
2 |
3 | This is an attempt to create a benchmark for various JavaScript libraries for generating CSS selectors. It is inspired by @dandv's [question](https://github.com/fczbkk/css-selector-generator/issues/2).
4 |
5 | ## Usage
6 |
7 | ```sh
8 | npm install
9 | npm test
10 | ```
11 |
12 | `index.html` should open in a browser tab with further instructions.
13 |
14 | ## Results
15 |
16 | ### @antonmedv [finder](https://github.com/antonmedv/finder)
17 |
18 | * NPM package, written in TypeScript
19 | * no dependencies
20 | * has tests
21 | * has documentation
22 | * MIT license
23 | * slower speed than other libraries (still fast enough for regular use)
24 | * creates efficient and robust selectors using ID, class, tags and child marker
25 | * does not seem to support attribute selectors
26 | * generates **shortest selectors** among all tested libraries
27 |
28 | Longest selector:
29 |
30 | ```
31 | .block:nth-child(3) li:nth-child(2) > .icon-eye-open
32 | ```
33 |
34 | ### @autarc [optimal-select](https://github.com/autarc/optimal-select)
35 |
36 | * supports UMD (Browser & Node)
37 | * no dependencies
38 | * no tests
39 | * MIT license
40 | * allows single and multiple input elements
41 | * separate handling of selection and optimization (export ES2015 Modules)
42 | * creates efficient and robust selectors using ID, class, attributes, tags and child marker
43 |
44 | Longest selector:
45 |
46 | ```
47 | .clearfix:nth-of-type(3) li:nth-of-type(2) .icon-eye-open
48 | ```
49 |
50 |
51 | ### @bimech [ellocate.js](https://github.com/bimech/ellocate.js)
52 |
53 | * supports Bower
54 | * depends on Jquery
55 | * has tests
56 | * has documentation
57 | * no license
58 | * average speed
59 | * uses ID, class and tag selectors
60 | * **WARNING:** doesn't use `nth-child` selectors, so it **produces a lot of non-unique selectors**
61 |
62 | Longest selector:
63 |
64 | ```
65 | html > body > div > div#wrap > div#main > div.container > div.main-content > div.row > div.span12 > div.row > div.span4.sidebar > div.block.clearfix > div.block-header.clearfix > div.block-action > a.btn.btn-success.btn-small > i.icon-plus.icon-white
66 | ```
67 |
68 | ### Chromium's [DOMPresentationUtils](https://chromium.googlesource.com/chromium/blink/+/master/Source/devtools/front_end/components/DOMPresentationUtils.js)
69 |
70 | NOTE: Used [version on NPM](https://www.npmjs.com/package/cssman) adapted for use in browser.
71 |
72 | * supports NPM
73 | * no dependencies
74 | * no tests
75 | * has documentation
76 | * see source code for license
77 | * average speed
78 | * uses ID, class, tag, attribute (for inputs) and `nth-child` child selectors
79 | * **WARNING: produces a lot of non-unique selectors** in both optimized and non-optimized version
80 |
81 | Example of non-unique selector:
82 |
83 | ```
84 | div#main > div > div > div > div > div > div.span4.sidebar > div.block.clearfix > div.block-content > ul > li.show-all > a
85 |
86 | [
87 | Show all ,
88 | Show all ,
89 | Show all ,
90 | Show all
91 | ]
92 | ```
93 |
94 | Longest selector:
95 |
96 | ```
97 | div#main > div > div > div > div > div > div.span4.sidebar > div.block.clearfix > div.block-content > ul > li:nth-child(1) > a
98 | ```
99 |
100 | ### @desmondw [snowflake](https://github.com/desmondw/snowflake)
101 |
102 | This is a Chrome extension, not a stand-alone library.
103 |
104 | * average speed
105 | * uses combination of tag and class or `nth-child`
106 |
107 | Longest selector:
108 |
109 | ```
110 | div.span12 > div:nth-of-type(1) > div:nth-of-type(1) > ul:nth-of-type(1) > li:nth-of-type(10) > div:nth-of-type(1) > div:nth-of-type(2) > span:nth-of-type(1)
111 | ```
112 |
113 | ### @fczbkk [css-selector-generator](https://github.com/fczbkk/css-selector-generator)
114 |
115 | * supports Bower and NPM
116 | * no dependencies
117 | * has tests
118 | * has documentation
119 | * Unlicense license
120 | * tries to use optimized ID, class, tag child selectors or their combination, uses `nth-child` as fallback
121 |
122 | Longest selector:
123 |
124 | ```
125 | .span12 > :nth-child(1) > .span8 > ul > :nth-child(1) > :nth-child(1)
126 | ```
127 |
128 | ### @jhartikainen [dompath](https://github.com/jhartikainen/dompath)
129 |
130 | * no support for Bower or NPM
131 | * no dependencies
132 | * has tests
133 | * has documentation
134 | * no license
135 | * very fast
136 | * uses ID or tagg + `nth-child` child selector, so the selectors tend to become quite long
137 |
138 | Longest selector:
139 |
140 | ```
141 | #main > div:nth-child(1) > div:nth-child(1) > div:nth-child(1) > div:nth-child(1) > div:nth-child(1) > div:nth-child(1) > ul:nth-child(2) > li:nth-child(10) > div:nth-child(2) > div:nth-child(2) > span:nth-child(4)
142 | ```
143 |
144 | ### @martinsbalodis [css-selector](https://github.com/martinsbalodis/css-selector)
145 |
146 | Sorry, I wasn't able to make it work.
147 |
148 |
149 | ### @ngs [jquery-selectorator](https://github.com/ngs/jquery-selectorator)
150 |
151 | * supports NPM and Bower
152 | * depends on Jquery
153 | * has tests
154 | * has documentation
155 | * MIT license
156 | * **very slow**
157 | * generates selectors using Jquery's `:eq()` selector, so most of the results are not valid CSS selectors and are only usable within Jquery
158 |
159 | Longest selector: n/a
160 |
161 |
162 | ### @olivierrr [selector-query](https://github.com/olivierrr/selector-query)
163 |
164 | * supports NPM only
165 | * no dependencies
166 | * no tests
167 | * has documentation
168 | * MIT license
169 | * quite fast
170 | * generates the [most complex descendant selector](https://github.com/olivierrr/selector-query/issues/1#issuecomment-133116659) for each element (ID, class, tag, `nth-child`), so it produces the **longest selectors** among tested libraries
171 | * **WARNING:** uses descendant selectors instead of child selectors, so it sometimes **produces non-unique selectors**
172 |
173 | Longest selector:
174 |
175 | ```
176 | #main div.container:nth-child(1) div.main-content:nth-child(1) div.row:nth-child(1) div.span12:nth-child(1) div.row:nth-child(1) div.span4.sidebar:nth-child(2) div.block.clearfix:nth-child(2) div.block-header.clearfix:nth-child(1) div.block-action:nth-child(2) a.btn.btn-success.btn-small:nth-child(1) i.icon-plus.icon-white:nth-child(1)
177 | ```
178 |
179 | ### @rishihahs [domtalk](https://github.com/rishihahs/domtalk)
180 |
181 | * supports NPM only
182 | * no dependencies
183 | * has tests
184 | * has documentation
185 | * MIT license
186 | * very fast
187 | * uses ID or `nth-child` descendant selector, selectors are of average length
188 | * **WARNING:** uses descendant selectors instead of child selectors, so it **produces a lot of non-unique selectors**
189 |
190 | Longest selector:
191 |
192 | ```
193 | #wrap *:nth-child(1) *:nth-child(1) *:nth-child(1) *:nth-child(3) *:nth-child(1) *:nth-child(1) *:nth-child(1) *:nth-child(3) *:nth-child(11) *:nth-child(1) *:nth-child(1)
194 | ```
195 |
196 | ### @stevoland [CSSelector.js](https://github.com/stevoland/CSSelector.js)
197 |
198 | * supports NPM (claims to support Bower, but I could not find it in the registry)
199 | * supports AMD
200 | * no dependencies
201 | * no tests
202 | * has documentation
203 | * MIT license
204 | * very fast
205 | * uses ID or tag + `nth-child` child selectors, selectors are quite long
206 |
207 | Longest selector:
208 |
209 | ```
210 | #main > DIV:nth-child(1) > DIV:nth-child(1) > DIV:nth-child(1) > DIV:nth-child(1) > DIV:nth-child(1) > DIV:nth-child(1) > UL:nth-child(2) > LI:nth-child(10) > DIV:nth-child(2) > DIV:nth-child(2) > SPAN:nth-child(4)
211 | ```
212 |
213 | ### @thomaspeklak [get-query-selector](https://github.com/thomaspeklak/get-query-selector)
214 |
215 | * supports NPM only
216 | * no dependencies
217 | * no tests
218 | * has documentation
219 | * looks like BSD license
220 | * very fast
221 | * uses ID or `nth-child` child selector, selectors are of average length
222 |
223 | Longest selector:
224 |
225 | ```
226 | #wrap>:nth-child(1)>:nth-child(1)>:nth-child(1)>:nth-child(3)>:nth-child(1)>:nth-child(1)>:nth-child(1)>:nth-child(3)>:nth-child(11)>:nth-child(1)>:nth-child(1)
227 | ```
228 |
229 | ### @tildeio [selector-generator](https://github.com/tildeio/selector-generator)
230 |
231 | * no NPM or Bower
232 | * requires RequireJS
233 | * has tests
234 | * no documentation
235 | * looks like MIT license
236 | * very fast
237 | * uses tag or tag + `nth-child` child selectors
238 | * **WARNING: produces a lot of non-unique selectors**
239 |
240 | Longest selector:
241 |
242 | ```
243 | html > body > div > div > div > div > div > div > div > div > div:nth-of-type(2) > div:nth-of-type(2) > div:nth-of-type(2) > ul > li:nth-of-type(2) > a
244 | ```
245 |
246 |
247 | ## TODO
248 |
249 | It would be nice to automate the process, run the tests in PhantomJS, etc. Pull requests are welcome.
250 |
251 | ## Bug reports, feature requests and contact
252 |
253 | If you found any bugs, if you have feature requests or any questions, please, either [file an issue at GitHub](https://github.com/fczbkk/css-selector-generator-benchmark/issues) or send me an e-mail at [riki@fczbkk.com](mailto:riki@fczbkk.com?subject=CSSSelectorGeneratorBenchmark)
254 |
--------------------------------------------------------------------------------
/config/webpack.medv.finder.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: 'production',
5 | entry: './node_modules/@medv/finder/dist/index.js',
6 | output: {
7 | path: path.resolve(__dirname, '../temp/finder/'),
8 | filename: 'index.js',
9 | library: 'finder',
10 | libraryTarget: 'var'
11 | }
12 | };
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | CSS Selector Generators Benchmark
7 |
8 |
13 |
14 |
15 |
16 |
17 |
27 |
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
45 |
62 |
63 |
64 |
73 |
74 |
75 |
76 |
83 |
84 |
85 |
86 |
87 |
93 |
94 |
95 |
96 |
97 |
103 |
104 |
105 |
106 |
107 |
113 |
114 |
115 |
122 |
123 |
124 |
125 |
132 |
133 |
134 |
135 |
142 |
143 |
144 |
145 |
150 |
151 |
155 |
156 |
157 |
164 |
165 |
166 |
175 |
176 |
177 |
178 |
185 |
186 |
187 |
188 |
189 |
190 | CSS Selector Generators Benchmark
191 |
192 | This test attempts to compare various JS libraries for generating CSS selectors of HTML elements. Each library is optimized for something else, e.g. speed, browser compatibility, selector length, etc. If some library does not have good score in some
193 | category, it does not mean that the library is rubbish.
194 |
195 | How it works
196 |
197 |
198 | There's some complex HTML code below.
199 | Each library has to go through all elements and make a unique CSS selector for it.
200 |
201 |
202 | Results
203 |
204 | Run tests
205 |
206 |
207 |
208 |
209 | Library
210 | Valid
211 | Invalid
212 | Not found
213 | Non unique
214 | Not matching
215 | Longest selector
216 | Duration
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
Testing HTML code
225 |
226 |
227 |
228 |
229 |
230 |
231 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
What's happening
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
marek committed 12 times to
358 |
test 2
359 |
360 |
6 days ago
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
marek created new topic
486 |
test 2
487 |
488 |
6 months ago
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
marek created new topic
502 |
test
503 |
504 |
9 months ago
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
marek created new topic test
518 |
519 |
9 months ago
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "css-selector-generator-benchmark",
3 | "version": "1.0.0",
4 | "description": "Benchmark for various JS libraries that generate unique CSS selectors.",
5 | "main": "index.html",
6 | "scripts": {
7 | "pretest": "git submodule update --init && npm run compile",
8 | "test": "index.html",
9 | "compile": "npm run compile:finder",
10 | "compile:finder": "webpack --config ./config/webpack.medv.finder.js"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/css-selector-generator-benchmark"
15 | },
16 | "keywords": [
17 | "javascript",
18 | "css",
19 | "benchmark"
20 | ],
21 | "author": "Riki Fridrich (http://fczbkk.com/)",
22 | "license": "MIT",
23 | "dependencies": {
24 | "@medv/finder": "^1.1.2",
25 | "css-selector": "^0.1.0",
26 | "css-selector-generator": "^1.2.0",
27 | "cssman": "0.0.2",
28 | "domtalk": "0.0.2",
29 | "get-query-selector": "0.0.1",
30 | "jquery": "^3.5.0",
31 | "jquery-selectorator": "^0.1.3",
32 | "optimal-select": "^4.0.1",
33 | "selector-query": "^1.0.1"
34 | },
35 | "devDependencies": {
36 | "webpack": "^4.4.1",
37 | "webpack-cli": "^2.0.13"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | var libraries = {}
2 |
3 |
4 | function addLibrary(name, callback) {
5 | libraries[name] = callback;
6 | }
7 |
8 |
9 | function runTests() {
10 |
11 | results = {};
12 |
13 | for (key in libraries) {
14 | var testFunction = libraries[key];
15 | results[key] = getResults(testFunction);
16 | }
17 |
18 | drawResults(results);
19 | }
20 |
21 |
22 | function drawResults(results) {
23 | var wrapper = document.querySelector('#results');
24 | wrapper.innerHTML = '';
25 |
26 | var output = document.createDocumentFragment()
27 |
28 | var resultsList = Object.keys(results).map(function (key) {
29 | var result = results[key]
30 | result.key = key
31 | return result
32 | })
33 | // priority: Valid (v), Longest (^), Duration (^)
34 | .sort(function(curr, next) {
35 | return next.validSelectors.length - curr.validSelectors.length ||
36 | curr.longestSelector.length - next.longestSelector.length ||
37 | curr.duration - next.duration
38 | })
39 |
40 | resultsList.forEach(function(data) {
41 | var row = output.appendChild(document.createElement('tr'));
42 | addCell(row, data.key);
43 | addCell(row, data.validSelectors.length);
44 | addCell(row, data.invalidSelectors.length);
45 | addCell(row, data.notFoundSelectors);
46 | addCell(row, data.nonUniqueSelectors.length);
47 | addCell(row, data.nonMatchingSelectors.length);
48 | addCell(row, "(" + data.longestSelector.length + ") " + data.longestSelector);
49 | addCell(row, data.duration + "ms");
50 | })
51 |
52 | wrapper.appendChild(output);
53 |
54 | console.log(resultsList);
55 | }
56 |
57 | function hasInvalidSelectors (data) {
58 | return data.invalidSelectors.length || data.notFoundSelectors.length ||
59 | data.nonUniqueSelectors.length || data.nonMatchingSelectors.length
60 | }
61 |
62 | function addCell(row, content) {
63 | var cell = row.appendChild(document.createElement('td'));
64 | cell.appendChild(document.createTextNode(content));
65 | return cell;
66 | }
67 |
68 |
69 | function getResults(testFunction) {
70 |
71 | var elements = document.querySelector('#wrap').querySelectorAll('*');
72 |
73 | var result = {
74 | duration: -1,
75 | validSelectors: [],
76 | invalidSelectors: [],
77 | nonUniqueSelectors: [],
78 | nonMatchingSelectors: [],
79 | notFoundSelectors: 0,
80 | longestSelector: ''
81 | };
82 | var outputs = [];
83 |
84 | var timeStart = (new Date).getTime();
85 |
86 | for (var i = 0, j = elements.length; i < j; i++) {
87 | var element = elements[i];
88 | var selector = testFunction(element);
89 | outputs.push({
90 | element: element,
91 | selector: selector
92 | });
93 | }
94 |
95 | var timeEnd = (new Date).getTime();
96 | result.duration = timeEnd - timeStart
97 |
98 | for (i = 0, j = outputs.length; i < j; i++) {
99 | var selector = outputs[i].selector;
100 | var element = outputs[i].element;
101 |
102 | if (selector) {
103 |
104 | var foundElements = []
105 |
106 | try {
107 | foundElements = document.querySelectorAll(selector);
108 | } catch (e) {
109 | result.invalidSelectors.push(selector);
110 | }
111 |
112 |
113 | if (foundElements.length > 1) {
114 | result.nonUniqueSelectors.push(selector);
115 | } else {
116 | if (foundElements[0] === element) {
117 | result.validSelectors.push(selector);
118 | } else {
119 | result.nonMatchingSelectors.push(selector);
120 | }
121 | }
122 |
123 | if (selector.length > result.longestSelector.length) {
124 | result.longestSelector = selector;
125 | }
126 |
127 | } else {
128 | result.notFoundSelectors++;
129 | }
130 |
131 | }
132 |
133 | return result;
134 | }
135 |
--------------------------------------------------------------------------------
/vendor/snowflake/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | code extracted from:
3 | https://github.com/desmondw/snowflake/blob/master/js/inject.js#L55
4 | */
5 |
6 |
7 |
8 |
9 |
10 | // creates a unique css selector for the given element
11 | function generateSelector(el){
12 | var selector = "";
13 | var tree = $(el).parentsUntil(document);
14 |
15 | // generate full selector by traversing DOM from bottom-up
16 | for (var i = -1; i < tree.length; i++){
17 | var e = i < 0 ? el : tree[i];
18 |
19 | var eCSS = querifyElement(e);
20 | var query = eCSS.query + (selector.length ? ' > ' : '') + selector;
21 |
22 | var matches = $(query);
23 |
24 | if (matches.length === 1 && matches[0] === el){
25 | return query;
26 | }
27 | else if (matches.length > 1 && i + 1 < tree.length){
28 |
29 | var parentQuery = generateSelector(tree[i + 1]);
30 | var parentMatches = $(parentQuery).children(eCSS.tag);
31 | var nthQuery = eCSS.tag + ':nth-of-type(' + (parentMatches.index(el) + 1) + ')' + (selector.length ? ' > ' : '') + selector;
32 | var parentNthQuery = parentQuery + ' > ' + nthQuery;
33 | var nthMatches = $(parentNthQuery);
34 |
35 | if (nthMatches.length === 1 && nthMatches[0] === el){
36 | return parentNthQuery;
37 | }
38 | else {
39 | printError("----------")
40 | return 'ERROR';
41 | }
42 | }
43 | else {
44 | if (matches.length === 1) printError("Matched incorrect element. (matches.length = " + matches.length + ")")
45 | else if (matches.length > 1) printError("Multiple matches, but traversed entire tree (algorithm not being specific enough).")
46 | else printError("Could not find match for tag/id/class selector. (matches.length = " + matches.length + ")")
47 | return 'ERROR';
48 | }
49 | }
50 |
51 | return selector;
52 | }
53 |
54 |
55 |
56 | // returns object with element information in query format
57 | function querifyElement(e){
58 | if (!e) return null;
59 |
60 | var tag = e.tagName.toLowerCase();
61 | var ids = e.id ? '#' + e.id.trim().split(' ').join('#') : "";
62 | var classes = e.className ? '.' + e.className.trim().split(' ').join('.') : "";
63 | var query = tag + ids + classes;
64 |
65 | return {
66 | element: e,
67 | tag: tag,
68 | ids: ids,
69 | classes: classes,
70 | query: query
71 | }
72 | }
73 |
--------------------------------------------------------------------------------