From 39181332d663d64211dc01424d31fa7b582b0532 Mon Sep 17 00:00:00 2001 From: kobenguyent Date: Tue, 16 Jan 2024 17:24:52 +0100 Subject: [PATCH 1/4] csss to xpath backward compatiblity --- .eslintignore | 1 + lib/css2xpath/js/css_to_xpath.js | 20 +++ lib/css2xpath/js/expression.js | 23 +++ lib/css2xpath/js/renderer.js | 239 +++++++++++++++++++++++++++++++ lib/locator.js | 16 ++- package.json | 4 +- test/unit/locator_test.js | 9 +- 7 files changed, 309 insertions(+), 3 deletions(-) create mode 100644 lib/css2xpath/js/css_to_xpath.js create mode 100644 lib/css2xpath/js/expression.js create mode 100644 lib/css2xpath/js/renderer.js diff --git a/.eslintignore b/.eslintignore index 65b969be4..f39debef2 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ test/data/output +lib/css2xpath/* diff --git a/lib/css2xpath/js/css_to_xpath.js b/lib/css2xpath/js/css_to_xpath.js new file mode 100644 index 000000000..7ea0174f9 --- /dev/null +++ b/lib/css2xpath/js/css_to_xpath.js @@ -0,0 +1,20 @@ +(function() { + var self = this; + var parser, xpathBuilder, Expression, parse, convertToXpath; + parser = require("bo-selector").parser; + xpathBuilder = require("xpath-builder").dsl(); + Expression = require("./expression"); + parser.yy.create = function(data) { + var self = this; + return new Expression(data); + }; + parse = function(selector) { + return parser.parse(selector).render(xpathBuilder, "descendant"); + }; + convertToXpath = function(selector) { + return parse(selector).toXPath(); + }; + convertToXpath.parse = parse; + convertToXpath.xPathBuilder = xpathBuilder; + module.exports = convertToXpath; +}).call(this); \ No newline at end of file diff --git a/lib/css2xpath/js/expression.js b/lib/css2xpath/js/expression.js new file mode 100644 index 000000000..039c104ec --- /dev/null +++ b/lib/css2xpath/js/expression.js @@ -0,0 +1,23 @@ +(function() { + var self = this; + var Renderer, Expression, extend; + Renderer = require("./renderer"); + Expression = function(data) { + extend(this, data); + return this; + }; + Expression.prototype.render = function(xpath, combinator) { + var self = this; + return new Renderer().render(self, xpath, combinator); + }; + extend = function(o, d) { + var k; + for (k in d) { + (function(k) { + o[k] = d[k]; + })(k); + } + return void 0; + }; + module.exports = Expression; +}).call(this); \ No newline at end of file diff --git a/lib/css2xpath/js/renderer.js b/lib/css2xpath/js/renderer.js new file mode 100644 index 000000000..688f4fe7e --- /dev/null +++ b/lib/css2xpath/js/renderer.js @@ -0,0 +1,239 @@ +(function() { + var self = this; + var xpathBuilder, Renderer; + xpathBuilder = require("xpath-builder").dsl(); + Renderer = function() { + return this; + }; + Renderer.prototype = { + render: function(node, xpath, combinator) { + var self = this; + var fn; + fn = self[node.type]; + if (!fn) { + throw new Error("No renderer for '" + node.type + "'"); + } + return fn.call(self, node, xpath, combinator); + }, + selector_list: function(node, xpath, combinator) { + var self = this; + var x, i; + x = self.render(node.selectors[0], xpath, combinator); + for (i = 1; i < node.selectors.length; ++i) { + x = x.union(self.render(node.selectors[i], xpath, combinator)); + } + return x; + }, + constraint_list: function(node, xpath, combinator) { + var self = this; + return self.element(node, xpath, combinator); + }, + element: function(node, xpath, combinator) { + var self = this; + return self.applyConstraints(node, xpath[combinator].call(xpath, node.name || "*")); + }, + combinator_selector: function(node, xpath, combinator) { + var self = this; + var left; + left = self.render(node.left, xpath, combinator); + return self.render(node.right, left, node.combinator); + }, + immediate_child: function(node, xpath) { + var self = this; + return self.render(node.child, xpath, "child"); + }, + pseudo_func: function(node, xpath) { + var self = this; + var fn; + fn = self[node.func.name.replace(/-/g, "_")]; + if (fn) { + return fn.call(self, node, xpath); + } else { + throw new Error("Unsupported pseudo function :" + node.func.name + "()"); + } + }, + pseudo_class: function(node, xpath) { + var self = this; + var fn; + fn = self[node.name.replace(/-/g, "_")]; + if (fn) { + return fn.call(self, node, xpath); + } else { + throw new Error("Unsupported pseudo class :" + node.name); + } + }, + has: function(node, xpath) { + var self = this; + return self.render(node.func.args, xpathBuilder, "descendant"); + }, + not: function(node, xpath) { + var self = this; + var firstChild, childType; + firstChild = node.func.args.selectors[0]; + childType = firstChild.type; + if (childType === "constraint_list") { + return self.combineConstraints(firstChild, xpath).inverse(); + } else { + return self.matchesSelectorList(node.func.args, xpath).inverse(); + } + }, + nth_child: function(node, xpath) { + var self = this; + return xpath.nthChild(Number(node.func.args)); + }, + first_child: function(node, xpath) { + var self = this; + return xpath.firstChild(); + }, + last_child: function(node, xpath) { + var self = this; + return xpath.lastChild(); + }, + nth_last_child: function(node, xpath) { + var self = this; + return xpath.nthLastChild(Number(node.func.args)); + }, + only_child: function(node, xpath) { + var self = this; + return xpath.onlyChild(); + }, + only_of_type: function(node, xpath) { + var self = this; + return xpath.onlyOfType(); + }, + nth_of_type: function(node, xpath) { + var self = this; + var type; + type = node.func.args.type; + if (type === "odd") { + return xpath.nthOfTypeOdd(); + } else if (type === "even") { + return xpath.nthOfTypeEven(); + } else if (type === "an") { + return xpath.nthOfTypeMod(Number(node.func.args.a)); + } else if (type === "n_plus_b") { + return xpath.nthOfTypeMod(1, Number(node.func.args.b)); + } else if (type === "an_plus_b") { + return xpath.nthOfTypeMod(Number(node.func.args.a), Number(node.func.args.b)); + } else { + return xpath.nthOfType(Number(node.func.args)); + } + }, + nth_last_of_type: function(node, xpath) { + var self = this; + var type; + type = node.func.args.type; + if (type === "odd") { + return xpath.nthLastOfTypeOdd(); + } else if (type === "even") { + return xpath.nthLastOfTypeEven(); + } else if (type === "an") { + return xpath.nthLastOfTypeMod(Number(node.func.args.a)); + } else if (type === "n_plus_b") { + return xpath.nthLastOfTypeMod(1, Number(node.func.args.b)); + } else if (type === "an_plus_b") { + return xpath.nthLastOfTypeMod(Number(node.func.args.a), Number(node.func.args.b)); + } else { + return xpath.nthLastOfType(Number(node.func.args)); + } + }, + last_of_type: function(node, xpath) { + var self = this; + return xpath.lastOfType(); + }, + empty: function(node, xpath) { + var self = this; + return xpath.empty(); + }, + has_attribute: function(node, xpath) { + var self = this; + return xpathBuilder.attr(node.name); + }, + attribute_equals: function(node, xpath) { + var self = this; + return xpathBuilder.attr(node.name).equals(node.value); + }, + attribute_contains: function(node, xpath) { + var self = this; + return xpathBuilder.attr(node.name).contains(node.value); + }, + attribute_contains_word: function(node, xpath) { + var self = this; + return xpath.concat(" ", xpathBuilder.attr(node.name).normalize(), " ").contains(" " + node.value + " "); + }, + attribute_contains_prefix: function(node) { + var self = this; + return xpathBuilder.attr(node.name).startsWith(node.value).or(xpathBuilder.attr(node.name).startsWith(node.value + "-")); + }, + attribute_starts_with: function(node, xpath) { + var self = this; + return xpathBuilder.attr(node.name).startsWith(node.value); + }, + attribute_ends_with: function(node) { + var self = this; + return xpathBuilder.attr(node.name).endsWith(node.value); + }, + "class": function(node) { + var self = this; + return self.attribute_contains_word({ + name: "class", + value: node.name + }, xpathBuilder); + }, + id: function(node) { + var self = this; + return xpathBuilder.attr("id").equals(node.name); + }, + previous_sibling: function(node, xpath, combinator) { + var self = this; + var left; + left = self.render(node.left, xpath, combinator); + return self.applyConstraints(node.right, left.axis("following-sibling", node.right.name)); + }, + adjacent_sibling: function(node, xpath, combinator) { + var self = this; + var left; + left = self.render(node.left, xpath, combinator); + return self.applyConstraints(node.right, left.axis("following-sibling::*[1]/self", node.right.name)); + }, + first_of_type: function(node, xpath) { + var self = this; + return xpath.firstOfType(); + }, + matchesSelectorList: function(node, xpath) { + var self = this; + var condition, i; + if (node.selectors.length > 0) { + condition = self.matchesSelector(node.selectors[0], xpathBuilder); + for (i = 1; i < node.selectors.length; ++i) { + condition = condition.or(self.matchesSelector(node.selectors[i], xpathBuilder)); + } + return condition; + } else { + return xpath; + } + }, + matchesSelector: function(node, xpath) { + var self = this; + return xpath.name().equals(node.name); + }, + combineConstraints: function(node, xpath) { + var self = this; + var condition, i; + condition = self.render(node.constraints[0], xpath); + for (i = 1; i < node.constraints.length; ++i) { + condition = condition.and(self.render(node.constraints[i], condition)); + } + return condition; + }, + applyConstraints: function(node, xpath) { + var self = this; + if (node.constraints.length > 0) { + return xpath.where(self.combineConstraints(node, xpath)); + } else { + return xpath; + } + } + }; + module.exports = Renderer; +}).call(this); \ No newline at end of file diff --git a/lib/locator.js b/lib/locator.js index a50e13958..4df583c4e 100644 --- a/lib/locator.js +++ b/lib/locator.js @@ -1,4 +1,4 @@ -const cssToXPath = require('csstoxpath'); +let cssToXPath; const { sprintf } = require('sprintf-js'); const { xpathLocator } = require('./utils'); @@ -162,6 +162,20 @@ class Locator { * @returns {string} */ toXPath(pseudoSelector = '') { + const limitation = [':nth-of-type', ':first-of-type', ':last-of-type', ':nth-last-child', ':nth-last-of-type', ':checked', ':disabled', ':enabled', ':required', ':lang']; + + if (pseudoSelector.includes(':text-')) { + cssToXPath = require('csstoxpath'); + } else if (this.value.includes('-')) { + if (!limitation.some(item => this.value.includes(item))) { + cssToXPath = require('csstoxpath'); + } + } else if (limitation.some(item => this.value.includes(item))) { + cssToXPath = require('./css2xpath/js/css_to_xpath'); + } else { + cssToXPath = require('./css2xpath/js/css_to_xpath'); + } + if (this.isXPath()) return this.value; if (this.isCSS()) return cssToXPath(`${this.value}${pseudoSelector}`); diff --git a/package.json b/package.json index 3beecd1e4..c07e91e4f 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "acorn": "8.11.2", "arrify": "2.0.1", "axios": "1.6.3", + "bo-selector": "^0.0.10", "chai": "4.3.8", "chai-deep-match": "1.2.1", "chai-exclude": "^2.1.0", @@ -109,7 +110,8 @@ "promise-retry": "1.1.1", "resq": "1.11.0", "sprintf-js": "1.1.1", - "uuid": "9.0" + "uuid": "9.0", + "xpath-builder": "^0.0.7" }, "optionalDependencies": { "@codeceptjs/detox-helper": "1.0.2" diff --git a/test/unit/locator_test.js b/test/unit/locator_test.js index baf4b28b0..5e230905e 100644 --- a/test/unit/locator_test.js +++ b/test/unit/locator_test.js @@ -50,6 +50,7 @@ const xml = ` + @@ -197,7 +198,7 @@ describe('Locator', () => { .inside(Locator.build('label').withText('Hello')); const nodes = xpath.select(l.toXPath(), doc); - expect(nodes).to.have.length(1, l.toXPath()); + expect(nodes).to.have.length(2, l.toXPath()); expect(nodes[0].firstChild.data).to.eql('Please click', l.toXPath()); }); @@ -301,4 +302,10 @@ describe('Locator', () => { expect(nodes).to.have.length(1, l.toXPath()); expect(nodes[0].firstChild.data).to.eql('Authoring', l.toXPath()); }); + + it('should find element with last of type', () => { + const l = Locator.build('').find('.n1:last-of-type button'); + const nodes = xpath.select(l.toXPath(), doc); + expect(nodes).to.have.length(0, l.toXPath()); + }); }); From cfa1084484a24fe4b36fad2289c048b9c9808e4b Mon Sep 17 00:00:00 2001 From: kobenguyent Date: Tue, 16 Jan 2024 17:26:42 +0100 Subject: [PATCH 2/4] csss to xpath backward compatiblity --- package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index c07e91e4f..5991d1465 100644 --- a/package.json +++ b/package.json @@ -75,14 +75,14 @@ "acorn": "8.11.2", "arrify": "2.0.1", "axios": "1.6.3", - "bo-selector": "^0.0.10", + "bo-selector": "0.0.10", "chai": "4.3.8", "chai-deep-match": "1.2.1", - "chai-exclude": "^2.1.0", - "chai-json-schema": "^1.5.1", - "chai-json-schema-ajv": "^5.2.4", - "chai-match-pattern": "^1.3.0", - "chai-string": "^1.5.0", + "chai-exclude": "2.1.0", + "chai-json-schema": "1.5.1", + "chai-json-schema-ajv": "5.2.4", + "chai-match-pattern": "1.3.0", + "chai-string": "1.5.0", "chalk": "4.1.2", "commander": "11.1.0", "cross-spawn": "7.0.3", @@ -94,7 +94,7 @@ "fn-args": "4.0.0", "fs-extra": "11.2.0", "glob": "6.0.1", - "html-minifier-terser": "^7.2.0", + "html-minifier-terser": "7.2.0", "inquirer": "6.5.2", "joi": "17.11.0", "js-beautify": "1.14.11", @@ -111,7 +111,7 @@ "resq": "1.11.0", "sprintf-js": "1.1.1", "uuid": "9.0", - "xpath-builder": "^0.0.7" + "xpath-builder": "0.0.7" }, "optionalDependencies": { "@codeceptjs/detox-helper": "1.0.2" @@ -121,7 +121,7 @@ "@faker-js/faker": "7.6.0", "@pollyjs/adapter-puppeteer": "6.0.6", "@pollyjs/core": "5.1.0", - "@types/chai": "^4.3.7", + "@types/chai": "4.3.7", "@types/inquirer": "9.0.3", "@types/node": "20.10.7", "@wdio/sauce-service": "8.27.0", From a7989bff4d747d81904594e2651c3b58c74544be Mon Sep 17 00:00:00 2001 From: kobenguyent Date: Wed, 17 Jan 2024 06:57:58 +0100 Subject: [PATCH 3/4] fix some logic --- lib/css2xpath/js/css_to_xpath.js | 20 --- lib/css2xpath/js/expression.js | 23 --- lib/css2xpath/js/renderer.js | 239 ------------------------------- lib/locator.js | 19 +-- package.json | 1 + test/unit/locator_test.js | 12 +- 6 files changed, 17 insertions(+), 297 deletions(-) delete mode 100644 lib/css2xpath/js/css_to_xpath.js delete mode 100644 lib/css2xpath/js/expression.js delete mode 100644 lib/css2xpath/js/renderer.js diff --git a/lib/css2xpath/js/css_to_xpath.js b/lib/css2xpath/js/css_to_xpath.js deleted file mode 100644 index 7ea0174f9..000000000 --- a/lib/css2xpath/js/css_to_xpath.js +++ /dev/null @@ -1,20 +0,0 @@ -(function() { - var self = this; - var parser, xpathBuilder, Expression, parse, convertToXpath; - parser = require("bo-selector").parser; - xpathBuilder = require("xpath-builder").dsl(); - Expression = require("./expression"); - parser.yy.create = function(data) { - var self = this; - return new Expression(data); - }; - parse = function(selector) { - return parser.parse(selector).render(xpathBuilder, "descendant"); - }; - convertToXpath = function(selector) { - return parse(selector).toXPath(); - }; - convertToXpath.parse = parse; - convertToXpath.xPathBuilder = xpathBuilder; - module.exports = convertToXpath; -}).call(this); \ No newline at end of file diff --git a/lib/css2xpath/js/expression.js b/lib/css2xpath/js/expression.js deleted file mode 100644 index 039c104ec..000000000 --- a/lib/css2xpath/js/expression.js +++ /dev/null @@ -1,23 +0,0 @@ -(function() { - var self = this; - var Renderer, Expression, extend; - Renderer = require("./renderer"); - Expression = function(data) { - extend(this, data); - return this; - }; - Expression.prototype.render = function(xpath, combinator) { - var self = this; - return new Renderer().render(self, xpath, combinator); - }; - extend = function(o, d) { - var k; - for (k in d) { - (function(k) { - o[k] = d[k]; - })(k); - } - return void 0; - }; - module.exports = Expression; -}).call(this); \ No newline at end of file diff --git a/lib/css2xpath/js/renderer.js b/lib/css2xpath/js/renderer.js deleted file mode 100644 index 688f4fe7e..000000000 --- a/lib/css2xpath/js/renderer.js +++ /dev/null @@ -1,239 +0,0 @@ -(function() { - var self = this; - var xpathBuilder, Renderer; - xpathBuilder = require("xpath-builder").dsl(); - Renderer = function() { - return this; - }; - Renderer.prototype = { - render: function(node, xpath, combinator) { - var self = this; - var fn; - fn = self[node.type]; - if (!fn) { - throw new Error("No renderer for '" + node.type + "'"); - } - return fn.call(self, node, xpath, combinator); - }, - selector_list: function(node, xpath, combinator) { - var self = this; - var x, i; - x = self.render(node.selectors[0], xpath, combinator); - for (i = 1; i < node.selectors.length; ++i) { - x = x.union(self.render(node.selectors[i], xpath, combinator)); - } - return x; - }, - constraint_list: function(node, xpath, combinator) { - var self = this; - return self.element(node, xpath, combinator); - }, - element: function(node, xpath, combinator) { - var self = this; - return self.applyConstraints(node, xpath[combinator].call(xpath, node.name || "*")); - }, - combinator_selector: function(node, xpath, combinator) { - var self = this; - var left; - left = self.render(node.left, xpath, combinator); - return self.render(node.right, left, node.combinator); - }, - immediate_child: function(node, xpath) { - var self = this; - return self.render(node.child, xpath, "child"); - }, - pseudo_func: function(node, xpath) { - var self = this; - var fn; - fn = self[node.func.name.replace(/-/g, "_")]; - if (fn) { - return fn.call(self, node, xpath); - } else { - throw new Error("Unsupported pseudo function :" + node.func.name + "()"); - } - }, - pseudo_class: function(node, xpath) { - var self = this; - var fn; - fn = self[node.name.replace(/-/g, "_")]; - if (fn) { - return fn.call(self, node, xpath); - } else { - throw new Error("Unsupported pseudo class :" + node.name); - } - }, - has: function(node, xpath) { - var self = this; - return self.render(node.func.args, xpathBuilder, "descendant"); - }, - not: function(node, xpath) { - var self = this; - var firstChild, childType; - firstChild = node.func.args.selectors[0]; - childType = firstChild.type; - if (childType === "constraint_list") { - return self.combineConstraints(firstChild, xpath).inverse(); - } else { - return self.matchesSelectorList(node.func.args, xpath).inverse(); - } - }, - nth_child: function(node, xpath) { - var self = this; - return xpath.nthChild(Number(node.func.args)); - }, - first_child: function(node, xpath) { - var self = this; - return xpath.firstChild(); - }, - last_child: function(node, xpath) { - var self = this; - return xpath.lastChild(); - }, - nth_last_child: function(node, xpath) { - var self = this; - return xpath.nthLastChild(Number(node.func.args)); - }, - only_child: function(node, xpath) { - var self = this; - return xpath.onlyChild(); - }, - only_of_type: function(node, xpath) { - var self = this; - return xpath.onlyOfType(); - }, - nth_of_type: function(node, xpath) { - var self = this; - var type; - type = node.func.args.type; - if (type === "odd") { - return xpath.nthOfTypeOdd(); - } else if (type === "even") { - return xpath.nthOfTypeEven(); - } else if (type === "an") { - return xpath.nthOfTypeMod(Number(node.func.args.a)); - } else if (type === "n_plus_b") { - return xpath.nthOfTypeMod(1, Number(node.func.args.b)); - } else if (type === "an_plus_b") { - return xpath.nthOfTypeMod(Number(node.func.args.a), Number(node.func.args.b)); - } else { - return xpath.nthOfType(Number(node.func.args)); - } - }, - nth_last_of_type: function(node, xpath) { - var self = this; - var type; - type = node.func.args.type; - if (type === "odd") { - return xpath.nthLastOfTypeOdd(); - } else if (type === "even") { - return xpath.nthLastOfTypeEven(); - } else if (type === "an") { - return xpath.nthLastOfTypeMod(Number(node.func.args.a)); - } else if (type === "n_plus_b") { - return xpath.nthLastOfTypeMod(1, Number(node.func.args.b)); - } else if (type === "an_plus_b") { - return xpath.nthLastOfTypeMod(Number(node.func.args.a), Number(node.func.args.b)); - } else { - return xpath.nthLastOfType(Number(node.func.args)); - } - }, - last_of_type: function(node, xpath) { - var self = this; - return xpath.lastOfType(); - }, - empty: function(node, xpath) { - var self = this; - return xpath.empty(); - }, - has_attribute: function(node, xpath) { - var self = this; - return xpathBuilder.attr(node.name); - }, - attribute_equals: function(node, xpath) { - var self = this; - return xpathBuilder.attr(node.name).equals(node.value); - }, - attribute_contains: function(node, xpath) { - var self = this; - return xpathBuilder.attr(node.name).contains(node.value); - }, - attribute_contains_word: function(node, xpath) { - var self = this; - return xpath.concat(" ", xpathBuilder.attr(node.name).normalize(), " ").contains(" " + node.value + " "); - }, - attribute_contains_prefix: function(node) { - var self = this; - return xpathBuilder.attr(node.name).startsWith(node.value).or(xpathBuilder.attr(node.name).startsWith(node.value + "-")); - }, - attribute_starts_with: function(node, xpath) { - var self = this; - return xpathBuilder.attr(node.name).startsWith(node.value); - }, - attribute_ends_with: function(node) { - var self = this; - return xpathBuilder.attr(node.name).endsWith(node.value); - }, - "class": function(node) { - var self = this; - return self.attribute_contains_word({ - name: "class", - value: node.name - }, xpathBuilder); - }, - id: function(node) { - var self = this; - return xpathBuilder.attr("id").equals(node.name); - }, - previous_sibling: function(node, xpath, combinator) { - var self = this; - var left; - left = self.render(node.left, xpath, combinator); - return self.applyConstraints(node.right, left.axis("following-sibling", node.right.name)); - }, - adjacent_sibling: function(node, xpath, combinator) { - var self = this; - var left; - left = self.render(node.left, xpath, combinator); - return self.applyConstraints(node.right, left.axis("following-sibling::*[1]/self", node.right.name)); - }, - first_of_type: function(node, xpath) { - var self = this; - return xpath.firstOfType(); - }, - matchesSelectorList: function(node, xpath) { - var self = this; - var condition, i; - if (node.selectors.length > 0) { - condition = self.matchesSelector(node.selectors[0], xpathBuilder); - for (i = 1; i < node.selectors.length; ++i) { - condition = condition.or(self.matchesSelector(node.selectors[i], xpathBuilder)); - } - return condition; - } else { - return xpath; - } - }, - matchesSelector: function(node, xpath) { - var self = this; - return xpath.name().equals(node.name); - }, - combineConstraints: function(node, xpath) { - var self = this; - var condition, i; - condition = self.render(node.constraints[0], xpath); - for (i = 1; i < node.constraints.length; ++i) { - condition = condition.and(self.render(node.constraints[i], condition)); - } - return condition; - }, - applyConstraints: function(node, xpath) { - var self = this; - if (node.constraints.length > 0) { - return xpath.where(self.combineConstraints(node, xpath)); - } else { - return xpath; - } - } - }; - module.exports = Renderer; -}).call(this); \ No newline at end of file diff --git a/lib/locator.js b/lib/locator.js index 4df583c4e..93d1fd0c8 100644 --- a/lib/locator.js +++ b/lib/locator.js @@ -162,22 +162,17 @@ class Locator { * @returns {string} */ toXPath(pseudoSelector = '') { + const locator = `${this.value}${pseudoSelector}`; const limitation = [':nth-of-type', ':first-of-type', ':last-of-type', ':nth-last-child', ':nth-last-of-type', ':checked', ':disabled', ':enabled', ':required', ':lang']; - if (pseudoSelector.includes(':text-')) { - cssToXPath = require('csstoxpath'); - } else if (this.value.includes('-')) { - if (!limitation.some(item => this.value.includes(item))) { - cssToXPath = require('csstoxpath'); - } - } else if (limitation.some(item => this.value.includes(item))) { - cssToXPath = require('./css2xpath/js/css_to_xpath'); + if (limitation.some(item => locator.includes(item))) { + cssToXPath = require('css-to-xpath'); } else { - cssToXPath = require('./css2xpath/js/css_to_xpath'); + cssToXPath = require('csstoxpath'); } if (this.isXPath()) return this.value; - if (this.isCSS()) return cssToXPath(`${this.value}${pseudoSelector}`); + if (this.isCSS()) return cssToXPath(locator); throw new Error('Can\'t be converted to XPath'); } @@ -264,7 +259,7 @@ class Locator { */ withText(text) { text = xpathLocator.literal(text); - const xpath = this.toXPath(`:text-contains-case(${text})`); + const xpath = sprintf('%s[%s]', this.toXPath(), `contains(., ${text})`); return new Locator({ xpath }); } @@ -275,7 +270,7 @@ class Locator { */ withTextEquals(text) { text = xpathLocator.literal(text); - const xpath = this.toXPath(`:text-case(${text})`); + const xpath = sprintf('%s[%s]', this.toXPath(), `.= ${text}`); return new Locator({ xpath }); } diff --git a/package.json b/package.json index 5991d1465..1a6a25902 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "chalk": "4.1.2", "commander": "11.1.0", "cross-spawn": "7.0.3", + "css-to-xpath": "0.1.0", "csstoxpath": "1.6.0", "devtools": "8.27.2", "envinfo": "7.11.0", diff --git a/test/unit/locator_test.js b/test/unit/locator_test.js index 5e230905e..15acd2949 100644 --- a/test/unit/locator_test.js +++ b/test/unit/locator_test.js @@ -68,7 +68,7 @@ const xml = ` `; -describe('Locator', () => { +describe.only('Locator', () => { beforeEach(() => { doc = new DOMParser().parseFromString(xml, 'application/xhtml+xml'); }); @@ -303,8 +303,14 @@ describe('Locator', () => { expect(nodes[0].firstChild.data).to.eql('Authoring', l.toXPath()); }); - it('should find element with last of type', () => { - const l = Locator.build('').find('.n1:last-of-type button'); + it('should find element with last of type with text', () => { + const l = Locator.build('.p-confirm-popup:last-of-type button').withText('delete'); + const nodes = xpath.select(l.toXPath(), doc); + expect(nodes).to.have.length(0, l.toXPath()); + }); + + it('should find element with last of type without text', () => { + const l = Locator.build('.p-confirm-popup:last-of-type button'); const nodes = xpath.select(l.toXPath(), doc); expect(nodes).to.have.length(0, l.toXPath()); }); From f987ca9535bafc6f149580b52211405cd386f88a Mon Sep 17 00:00:00 2001 From: kobenguyent Date: Wed, 17 Jan 2024 07:00:42 +0100 Subject: [PATCH 4/4] chore: remove unused libs --- package.json | 4 +--- test/unit/locator_test.js | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 1a6a25902..755b91186 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,6 @@ "acorn": "8.11.2", "arrify": "2.0.1", "axios": "1.6.3", - "bo-selector": "0.0.10", "chai": "4.3.8", "chai-deep-match": "1.2.1", "chai-exclude": "2.1.0", @@ -111,8 +110,7 @@ "promise-retry": "1.1.1", "resq": "1.11.0", "sprintf-js": "1.1.1", - "uuid": "9.0", - "xpath-builder": "0.0.7" + "uuid": "9.0" }, "optionalDependencies": { "@codeceptjs/detox-helper": "1.0.2" diff --git a/test/unit/locator_test.js b/test/unit/locator_test.js index 15acd2949..e173ed523 100644 --- a/test/unit/locator_test.js +++ b/test/unit/locator_test.js @@ -68,7 +68,7 @@ const xml = ` `; -describe.only('Locator', () => { +describe('Locator', () => { beforeEach(() => { doc = new DOMParser().parseFromString(xml, 'application/xhtml+xml'); });