diff --git a/protractor/constants.js b/protractor/constants.js index 4121d49..61549e2 100644 --- a/protractor/constants.js +++ b/protractor/constants.js @@ -1,4 +1,32 @@ -exports.SUPPORTED_SELECTORS = ['id', 'model', 'css', 'binding', 'cssContainingText'] +const { format } = require('util') + +const UNSUPPORTED_SELECTOR_STRATEGY_ERROR = 'The selector "%s" is not supported, please consider refactor this line.' +exports.UNSUPPORTED_SELECTOR_STRATEGIES = { + binding: format(UNSUPPORTED_SELECTOR_STRATEGY_ERROR, 'by.binding'), + deepCss: 'WebdriverIO does not natively support deep CSS queries yet (https://github.com/webdriverio/webdriverio/issues/6709). We advise to use this plugin: https://github.com/Georgegriff/query-selector-shadow-dom.', + exactRepeater: format(UNSUPPORTED_SELECTOR_STRATEGY_ERROR, 'by.exactRepeater'), + repeater: format(UNSUPPORTED_SELECTOR_STRATEGY_ERROR, 'by.repeater'), + exactBinding: format(UNSUPPORTED_SELECTOR_STRATEGY_ERROR, 'by.exactBinding') +} + +exports.SUPPORTED_SELECTORS = [ + ...Object.keys(exports.UNSUPPORTED_SELECTOR_STRATEGIES), + 'id', + 'model', + 'css', + 'className', + 'cssContainingText', + 'xpath', + 'tagName', + 'partialLinkText', + 'name', + 'js', + 'linkText', + 'options', + 'buttonText', + 'partialButtonText' +] + exports.ELEMENT_COMMANDS = [ 'sendKeys', 'isPresent', diff --git a/protractor/index.js b/protractor/index.js index dc54d04..14af398 100644 --- a/protractor/index.js +++ b/protractor/index.js @@ -82,6 +82,7 @@ module.exports = function transformer(file, api) { root.find(j.ExpressionStatement) .filter((path) => ( path.value.expression.callee && + path.value.expression.callee.property && COMMANDS_TO_REMOVE.includes(path.value.expression.callee.property.name) )) .replaceWith((path) => null) diff --git a/protractor/utils.js b/protractor/utils.js index 3f97161..6e6aa19 100644 --- a/protractor/utils.js +++ b/protractor/utils.js @@ -4,6 +4,7 @@ const { format } = require('util') const { IGNORED_CONFIG_PROPERTIES, UNSUPPORTED_CONFIG_OPTION_ERROR, + UNSUPPORTED_SELECTOR_STRATEGIES, REPLACE_CONFIG_KEYS, IGNORED_CAPABILITIES } = require('./constants') @@ -33,49 +34,65 @@ class TransformError extends Error { } function getSelectorArgument (j, path, callExpr, file) { - const args = [] const bySelector = callExpr.callee.property.name + const arg = callExpr.arguments[0] - if (bySelector === 'id') { - args.push(j.literal(`#${callExpr.arguments[0].value}`)) + if (Object.keys(UNSUPPORTED_SELECTOR_STRATEGIES).includes(bySelector)) { + throw new TransformError( + UNSUPPORTED_SELECTOR_STRATEGIES[bySelector], + path.value, + file + ) + } else if (bySelector === 'id') { + return [j.literal(`#${arg.value}`)] } else if (bySelector === 'model') { - args.push(j.literal(`*[ng-model="${callExpr.arguments[0].value}"]`)) + return [j.literal(`*[ng-model="${arg.value}"]`)] } else if (bySelector === 'css') { - args.push(...callExpr.arguments) + return [...callExpr.arguments] } else if (bySelector === 'cssContainingText') { - const selector = callExpr.arguments[0] const text = callExpr.arguments[1] - if (text.type === 'Literal') { - args.push(j.literal(`${selector.value}=${text.value}`)) - } else if (text.type === 'Identifier') { - args.push( - j.binaryExpression( - '+', - j.literal(selector.value + '='), - j.identifier(text.name) - ) - ) - } else { - throw new TransformError('expect 2nd parameter of cssContainingText to be a literal or identifier', path.value, file) + if (text.type === 'Literal') { + return [j.literal(`${arg.value}=${text.value}`)] + } else if (text.type === 'Identifier') { + return [ + j.binaryExpression( + '+', + j.literal(arg.value + '='), + j.identifier(text.name) + ) + ] + } if (text.regex) { + throw new TransformError('this codemod does not support RegExp in cssContainingText', path.value, file) + } else { + throw new TransformError('expect 2nd parameter of cssContainingText to be a literal or identifier', path.value, file) + } + } else if (bySelector === 'xpath' || bySelector === 'tagName' || bySelector === 'js') { + return [arg] + } else if (bySelector === 'linkText') { + return [j.literal(`=${arg.value}`)] + } else if (bySelector === 'partialLinkText') { + return [j.literal(`*=${arg.value}`)] + } else if (bySelector === 'name') { + return [j.literal(`*[name="${arg.value}"]`)] + } else if (bySelector === 'className') { + return [j.literal(`.${arg.value}`)] + } else if (bySelector === 'options') { + return [j.literal(`select[ng-options="${arg.value}"] option`)] + } else if (bySelector === 'buttonText') { + return [j.literal(`button=${arg.value}`)] + } else if (bySelector === 'partialButtonText') { + return [j.literal(`button*=${arg.value}`)] } - if (text.regex) { - throw new TransformError('this codemod does not support RegExp in cssContainingText', path.value, file) - } - } else if (bySelector === 'binding') { - throw new TransformError('Binding selectors (by.binding) are not supported, please consider refactor this line', path.value, file) - } else { - // we assume a custom locator strategy - const selectorStrategyName = callExpr.callee.property.name - const selector = callExpr.arguments[0].value - args.push( - j.literal(selectorStrategyName), - j.literal(selector) - ) - } - return args + // we assume a custom locator strategy + const selectorStrategyName = callExpr.callee.property.name + const selector = callExpr.arguments[0].value + return [ + j.literal(selectorStrategyName), + j.literal(selector) + ] } function matchesSelectorExpression (path) { diff --git a/test/__fixtures__/protractor/source/element.js b/test/__fixtures__/protractor/source/element.js index 0593f2d..4c34f9e 100644 --- a/test/__fixtures__/protractor/source/element.js +++ b/test/__fixtures__/protractor/source/element.js @@ -10,3 +10,20 @@ $('body').allowAnimations(false); var liDog = element(by.css('.dog')).getWebElement(); var liCat = liDog.getDriver().findElement(by.css('.cat')); var lis = liDog.getDriver().findElements(by.css('li')); + +var li = element(by.xpath('//ul/li/a')); +expect(element(by.tagName('a')).getText()).toBe('Google'); +var doge = element(by.partialLinkText('Doge')); +var dog = element(by.name('dog_name')); +var wideElement = element(by.js(function() { + var spans = document.querySelectorAll('span'); + for (var i = 0; i < spans.length; ++i) { + if (spans[i].offsetWidth > 100) { + return spans[i]; + } + } +})); +expect(element(by.linkText('Google')).getTagName()).toBe('a'); +var allOptions = element.all(by.options('c for c in colors')); +element(by.partialButtonText('Save')); +element(by.buttonText('Save')); diff --git a/test/__fixtures__/protractor/source/failing_selector.js b/test/__fixtures__/protractor/source/failing_selector.js new file mode 100644 index 0000000..a6317b1 --- /dev/null +++ b/test/__fixtures__/protractor/source/failing_selector.js @@ -0,0 +1,6 @@ +expect(element(by.exactBinding('person.name')).isPresent()).toBe(true); +expect(element(by.exactBinding('person-email')).isPresent()).toBe(true); +expect(element(by.exactBinding('person')).isPresent()).toBe(false); +expect(element(by.exactBinding('person_phone')).isPresent()).toBe(true); +expect(element(by.exactBinding('person_phone|uppercase')).isPresent()).toBe(true); +expect(element(by.exactBinding('phone')).isPresent()).toBe(false); diff --git a/test/__fixtures__/protractor/transformed/element.js b/test/__fixtures__/protractor/transformed/element.js index 9b6b058..4698565 100644 --- a/test/__fixtures__/protractor/transformed/element.js +++ b/test/__fixtures__/protractor/transformed/element.js @@ -7,3 +7,20 @@ browser.$('.parent'); var liDog = $('.dog'); var liCat = liDog.parentElement().$('.cat'); var lis = liDog.parentElement().$$('li'); + +var li = $('//ul/li/a'); +expect($('a').getText()).toBe('Google'); +var doge = $("*=Doge"); +var dog = $("*[name=\"dog_name\"]"); +var wideElement = $(function() { + var spans = document.querySelectorAll('span'); + for (var i = 0; i < spans.length; ++i) { + if (spans[i].offsetWidth > 100) { + return spans[i]; + } + } +}); +expect($("=Google").getTagName()).toBe('a'); +var allOptions = $$("select[ng-options=\"c for c in colors\"] option"); +$("button*=Save"); +$("button=Save"); diff --git a/test/runner.js b/test/runner.js index d0114a8..ca49ff3 100644 --- a/test/runner.js +++ b/test/runner.js @@ -19,7 +19,8 @@ const frameworkTests = { ['./failing_unsupported.js'], ['./failing_evaluate.js'], ['./failing_getCssValue.js'], - ['./failing_expectedConditions.js'] + ['./failing_expectedConditions.js'], + ['./failing_selector.js'] ] }