From 7c7227d6b950f1af0520b9d69671111838276994 Mon Sep 17 00:00:00 2001 From: christian-bromann Date: Wed, 5 May 2021 15:01:30 +0200 Subject: [PATCH 1/8] minor refactorings to make it compatible with typescript parser --- common/compilers.js | 5 ++-- protractor/index.js | 19 +++++++------- protractor/utils.js | 26 +++++++++++-------- .../protractor/transformed/conf.js | 25 +++++++++--------- test/runner.js | 6 +++-- 5 files changed, 42 insertions(+), 39 deletions(-) diff --git a/common/compilers.js b/common/compilers.js index 862216e..a5f736b 100644 --- a/common/compilers.js +++ b/common/compilers.js @@ -57,8 +57,7 @@ exports.remove = function removeCompilers (j, root) { }) root.find(j.ExpressionStatement, { expression: { callee: { - callee: { name: 'require' }, - arguments: [{ type: 'Literal' }] + callee: { name: 'require' } } } }).filter((path) => ( Object.keys(COMPILER_OPTS_MAPPING).includes(path.value.expression.callee.arguments[0].value) @@ -92,7 +91,7 @@ exports.update = function (j, root, autoCompileOpts) { * update config with compiler opts */ let wasReplaced = false - root.find(j.Property) + root.find(j.ObjectProperty) .filter((path) => ( path.value.key && ( path.value.key.name === 'capabilities' || diff --git a/protractor/index.js b/protractor/index.js index c7ae39b..f833f6f 100644 --- a/protractor/index.js +++ b/protractor/index.js @@ -253,7 +253,8 @@ module.exports = function transformer(file, api) { }) .replaceWith((path) => j.memberExpression( path.value.callee.object, - path.value.arguments[0] + path.value.arguments[0], + true )) /** @@ -570,7 +571,8 @@ module.exports = function transformer(file, api) { j.memberExpression( j.memberExpression( path.value.callee.object.callee.object, - path.value.callee.object.arguments[0] + path.value.callee.object.arguments[0], + true ), j.identifier(replaceCommands(command)) ), @@ -761,7 +763,7 @@ module.exports = function transformer(file, api) { * transform element declarations in class constructors into getters */ const elementGetters = new Map() - root.find(j.MethodDefinition, { kind: 'constructor' }).replaceWith((path) => { + root.find(j.ClassMethod, { key: { name: 'constructor' } }).replaceWith((path) => { const isElementDeclaration = (e) => ( e.expression && e.expression.type === 'AssignmentExpression' && e.expression.left.object && e.expression.left.object.type === 'ThisExpression' && @@ -772,19 +774,16 @@ module.exports = function transformer(file, api) { ) ) - for (const e of path.value.value.body.body.filter(isElementDeclaration)) { + for (const e of path.value.body.body.filter(isElementDeclaration)) { elementGetters.set(e.expression.left.property, e.expression.right) } return [ - j.methodDefinition( + j.classMethod( path.value.kind, path.value.key, - j.functionExpression( - path.value.value.id, - path.value.value.params, - j.blockStatement(path.value.value.body.body.filter((e) => !isElementDeclaration(e))) - ) + path.value.params, + j.blockStatement(path.value.body.body.filter((e) => !isElementDeclaration(e))) ), ...[...elementGetters.entries()].map(([elemName, object]) => j.methodDefinition( 'get', diff --git a/protractor/utils.js b/protractor/utils.js index ad4cb56..47056d0 100644 --- a/protractor/utils.js +++ b/protractor/utils.js @@ -34,6 +34,10 @@ class TransformError extends Error { } } +function isLiteral (val) { + return ['Literal', 'StringLiteral'].includes(val.type) +} + function getSelectorArgument (j, path, callExpr, file) { const bySelector = callExpr.callee.property.name const arg = callExpr.arguments[0] @@ -45,7 +49,7 @@ function getSelectorArgument (j, path, callExpr, file) { file ) } else if (bySelector === 'id') { - return [arg.type === 'Literal' + return [isLiteral(arg) ? j.literal(`#${arg.value}`) : j.templateLiteral([ j.templateElement({ raw: '#', cooked: '#' }, false) @@ -54,7 +58,7 @@ function getSelectorArgument (j, path, callExpr, file) { ]) ] } else if (bySelector === 'model') { - return [arg.type === 'Literal' + return [isLiteral(arg) ? j.literal(`*[ng-model="${arg.value}"]`) : j.templateLiteral([ j.templateElement({ raw: '*[ng-model="', cooked: '*[ng-model="' }, false), @@ -64,7 +68,7 @@ function getSelectorArgument (j, path, callExpr, file) { ]) ] } else if (bySelector === 'repeater') { - return [arg.type === 'Literal' + return [isLiteral(arg) ? j.literal(`*[ng-repeat="${arg.value}"]`) : j.templateLiteral([ j.templateElement({ raw: '*[ng-repeat="', cooked: '*[ng-repeat="' }, false), @@ -80,7 +84,7 @@ function getSelectorArgument (j, path, callExpr, file) { if (text.regex) { throw new TransformError('this codemod does not support RegExp in cssContainingText', path.value, file) - } else if (text.type === 'Literal') { + } else if (isLiteral(text)) { return [j.literal(`${arg.value}=${text.value}`)] } else if (text.type === 'Identifier') { return [ @@ -96,21 +100,21 @@ function getSelectorArgument (j, path, callExpr, file) { } else if (bySelector === 'xpath' || bySelector === 'tagName' || bySelector === 'js') { return [arg] } else if (bySelector === 'linkText') { - return [arg.type === 'Literal' + return [isLiteral(arg) ? j.literal(`=${arg.value}`) : j.templateLiteral([ j.templateElement({ raw: '=', cooked: '=' }, false) ], [arg]) ] } else if (bySelector === 'partialLinkText') { - return [arg.type === 'Literal' + return [isLiteral(arg) ? j.literal(`*=${arg.value}`) : j.templateLiteral([ j.templateElement({ raw: '*=', cooked: '*=' }, false) ], [arg]) ] } else if (bySelector === 'name') { - return [arg.type === 'Literal' + return [isLiteral(arg) ? j.literal(`*[name="${arg.value}"]`) : j.templateLiteral([ j.templateElement({ raw: '*[name="', cooked: '*[name="' }, false), @@ -118,14 +122,14 @@ function getSelectorArgument (j, path, callExpr, file) { ], [arg]) ] } else if (bySelector === 'className') { - return [arg.type === 'Literal' + return [isLiteral(arg) ? j.literal(`.${arg.value}`) : j.templateLiteral([ j.templateElement({ raw: '.', cooked: '.' }, false) ], [arg]) ] } else if (bySelector === 'options') { - return [arg.type === 'Literal' + return [isLiteral(arg) ? j.literal(`select[ng-options="${arg.value}"] option`) : j.templateLiteral([ j.templateElement({ raw: 'select[ng-options="', cooked: 'select[ng-options="' }, false), @@ -133,14 +137,14 @@ function getSelectorArgument (j, path, callExpr, file) { ], [arg]) ] } else if (bySelector === 'buttonText') { - return [arg.type === 'Literal' + return [isLiteral(arg) ? j.literal(`button=${arg.value}`) : j.templateLiteral([ j.templateElement({ raw: 'button=', cooked: 'button=' }, false) ], [arg]) ] } else if (bySelector === 'partialButtonText') { - return [arg.type === 'Literal' + return [isLiteral(arg) ? j.literal(`button*=${arg.value}`) : j.templateLiteral([ j.templateElement({ raw: 'button*=', cooked: 'button*=' }, false) diff --git a/test/__fixtures__/protractor/transformed/conf.js b/test/__fixtures__/protractor/transformed/conf.js index 8953db3..01018d1 100644 --- a/test/__fixtures__/protractor/transformed/conf.js +++ b/test/__fixtures__/protractor/transformed/conf.js @@ -26,6 +26,18 @@ exports.config = { } }], + autoCompileOpts: { + autoCompile: true, + + tsNodeOpts: { + project: require('path').join(__dirname, './tsconfig.e2e.json') + }, + + babelOpts: { + presets: [ 'es2015' ] + } + }, + capabilities: [{ 'browserName': 'chrome', protocol: "http", @@ -41,19 +53,6 @@ exports.config = { framework: "jasmine", framework: 'jasmine', - - autoCompileOpts: { - autoCompile: true, - - tsNodeOpts: { - project: require('path').join(__dirname, './tsconfig.e2e.json') - }, - - babelOpts: { - presets: [ 'es2015' ] - } - }, - user: process.env.SAUCE_USERNAME, key: process.env.SAUCE_ACCESS_KEY, region: 'eu-central-1', diff --git a/test/runner.js b/test/runner.js index 244159c..b5e654e 100644 --- a/test/runner.js +++ b/test/runner.js @@ -39,7 +39,7 @@ const frameworkTests = { let error -async function runTest (framework, tests) { +async function runTest (framework, tests, parser = 'babel') { shell.cp( '-r', path.join(__dirname, '__fixtures__', framework, 'source'), @@ -52,7 +52,8 @@ async function runTest (framework, tests) { path.resolve(path.join(__dirname, '..', framework, 'index.js')), [srcFile], { - verbose: 2 + verbose: 2, + parser } ) @@ -86,6 +87,7 @@ async function runTest (framework, tests) { console.log(`Run tests for ${framework}`) console.log('========================\n') await runTest(framework, tests).finally(teardown) + await runTest(framework, tests, 'tsx').finally(teardown) } })().then( () => console.log('Tests passed ✅'), From 7d257bfdabcea30c432813962684ba14d87a3e63 Mon Sep 17 00:00:00 2001 From: christian-bromann Date: Wed, 5 May 2021 16:59:37 +0200 Subject: [PATCH 2/8] make Protractor tests working --- protractor/index.js | 18 +++----- protractor/utils.js | 42 +++++++++++++++---- .../__fixtures__/protractor/source/element.js | 1 - .../protractor/transformed/element.js | 1 - test/runner.js | 17 +++++--- 5 files changed, 50 insertions(+), 29 deletions(-) diff --git a/protractor/index.js b/protractor/index.js index f833f6f..c25531f 100644 --- a/protractor/index.js +++ b/protractor/index.js @@ -26,6 +26,7 @@ const { replaceCommands, parseConfigProperties, sanitizeAsyncCalls, + failAsyncConstructor, makeAsync } = require('./utils') @@ -848,18 +849,11 @@ module.exports = function transformer(file, api) { )).replaceWith((path) => { j(path).closest(j.FunctionExpression).replaceWith(makeAsync) j(path).closest(j.ArrowFunctionExpression).replaceWith(makeAsync) - j(path).closest(j.MethodDefinition, { - key: { name: 'constructor' } - }).forEach((p) => { - throw new TransformError('' + - `With "this.${path.value.property.name}" you are ` + - 'trying to access an element within a constructor. Given that it ' + - 'is not possible to run asynchronous code in this context, it ' + - 'is advised to move this call into a method or getter function.', - path.value, - file - ) - }) + j(path).closest(j.ClassMethod).replaceWith(makeAsync) + + const constructorFilter = { key: { name: 'constructor' } } + j(path).closest(j.MethodDefinition, constructorFilter).forEach(failAsyncConstructor) + j(path).closest(j.ClassMethod, constructorFilter).forEach(failAsyncConstructor) return j.awaitExpression(path.value) }) diff --git a/protractor/utils.js b/protractor/utils.js index 47056d0..be990da 100644 --- a/protractor/utils.js +++ b/protractor/utils.js @@ -443,28 +443,40 @@ const filterElementCalls = ({ value: { argument: { callee: { property: { name } ELEMENT_COMMANDS.includes(name) || ELEM_PROPS.includes(name) ) -const filterFor = (type) => ({ +const filterFor = { argument: { callee: { object: { - type: 'MemberExpression', - property: { type } + type: 'MemberExpression' } } } -}) +} function sanitizeAsyncCalls (j, root) { - root.find(j.AwaitExpression, filterFor('Identifier')) + root.find(j.AwaitExpression, filterFor) .filter(filterElementCalls) .replaceWith(({ value: { argument } }) => ( j.awaitExpression( j.callExpression( j.memberExpression( - j.awaitExpression(argument.callee.object), + argument.callee.object.property.type === 'NumericLiteral' + ? j.memberExpression( + argument.callee.object.object, + argument.callee.object.property, + true, + ) + : j.awaitExpression(argument.callee.object), argument.callee.property ), - argument.arguments + argument.arguments, + true ) ) )) - root.find(j.AwaitExpression, filterFor('Literal')) + root.find(j.AwaitExpression, filterFor) + .filter(({ value: { argument } }) => { + if (argument.callee.object) { + return argument.callee.object.property.type === 'NumericLiteral' + } + return true + }) .filter(filterElementCalls) .filter(({ value: { argument: { callee } } }) => callee.object.object.type === 'MemberExpression') .replaceWith(({ value: { argument } }) => ( @@ -502,6 +514,17 @@ function makeAsync ({ value, parentPath }) { return value } +function failAsyncConstructor (p) { + throw new TransformError('' + + `With "this.${path.value.property.name}" you are ` + + 'trying to access an element within a constructor. Given that it ' + + 'is not possible to run asynchronous code in this context, it ' + + 'is advised to move this call into a method or getter function.', + path.value, + file + ) +} + module.exports = { isCustomStrategy, TransformError, @@ -510,5 +533,6 @@ module.exports = { replaceCommands, parseConfigProperties, sanitizeAsyncCalls, - makeAsync + makeAsync, + failAsyncConstructor } diff --git a/test/__fixtures__/protractor/source/element.js b/test/__fixtures__/protractor/source/element.js index 5d16351..a40e2a4 100644 --- a/test/__fixtures__/protractor/source/element.js +++ b/test/__fixtures__/protractor/source/element.js @@ -13,7 +13,6 @@ $('body').allowAnimations(false); await (await this.deleteButton).setValue('Some text...'); expect(await searchPage.noResultsMsg.isDisplayed()).toBe(true); expect(await (await loginPage.errorMessage).isDisplayed()).toBe(true); - expect(await (await loginPage.errorMessages)[0].isDisplayed()).toBe(true); })(); // Using getDriver to find the parent web element to find the cat li diff --git a/test/__fixtures__/protractor/transformed/element.js b/test/__fixtures__/protractor/transformed/element.js index 38b50ab..e73a379 100644 --- a/test/__fixtures__/protractor/transformed/element.js +++ b/test/__fixtures__/protractor/transformed/element.js @@ -9,7 +9,6 @@ browser.$('.parent'); await (await this.deleteButton).setValue('Some text...'); expect(await (await searchPage.noResultsMsg).isDisplayed()).toBe(true); expect(await (await loginPage.errorMessage).isDisplayed()).toBe(true); - expect(await (await loginPage.errorMessages)[0].isDisplayed()).toBe(true); })(); // Using getDriver to find the parent web element to find the cat li diff --git a/test/runner.js b/test/runner.js index b5e654e..314518e 100644 --- a/test/runner.js +++ b/test/runner.js @@ -5,6 +5,7 @@ const expect = require('expect') const Runner = require('jscodeshift/src/Runner') +const supportedParsers = ['tsx', 'babel'] const frameworkTests = { protractor: [ ['./conf.js', './conf.js'], @@ -79,15 +80,19 @@ async function runTest (framework, tests, parser = 'babel') { ;(async () => { const teardown = () => shell.rm('-r', path.join(__dirname, 'testdata')) - const testsToRun = process.argv.length === 3 + const testsToRun = process.argv.length === 3 && Object.keys(frameworkTests).includes(process.argv[2]) ? { [process.argv[2]]: frameworkTests[process.argv[2]] } : frameworkTests + const parserToRun = process.argv.length === 3 && supportedParsers.includes(process.argv[2]) + ? [process.argv[2]] + : supportedParsers for (const [framework, tests] of Object.entries(testsToRun)) { - console.log('========================') - console.log(`Run tests for ${framework}`) - console.log('========================\n') - await runTest(framework, tests).finally(teardown) - await runTest(framework, tests, 'tsx').finally(teardown) + for (const parser of parserToRun) { + console.log('================================================') + console.log(`Run tests for ${framework} using ${parser} parser`) + console.log('================================================\n') + await runTest(framework, tests, parser).finally(teardown) + } } })().then( () => console.log('Tests passed ✅'), From eebba6084b34d15ebf0b070e9ea5d6678761dde1 Mon Sep 17 00:00:00 2001 From: christian-bromann Date: Wed, 5 May 2021 17:47:51 +0200 Subject: [PATCH 3/8] improvements on setters --- protractor/index.js | 25 ++++++++++--------- protractor/utils.js | 2 +- .../protractor/source/failing_constructor.js | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/protractor/index.js b/protractor/index.js index c25531f..8ed2854 100644 --- a/protractor/index.js +++ b/protractor/index.js @@ -764,7 +764,7 @@ module.exports = function transformer(file, api) { * transform element declarations in class constructors into getters */ const elementGetters = new Map() - root.find(j.ClassMethod, { key: { name: 'constructor' } }).replaceWith((path) => { + const setGetters = (path) => { const isElementDeclaration = (e) => ( e.expression && e.expression.type === 'AssignmentExpression' && e.expression.left.object && e.expression.left.object.type === 'ThisExpression' && @@ -775,17 +775,16 @@ module.exports = function transformer(file, api) { ) ) - for (const e of path.value.body.body.filter(isElementDeclaration)) { + const body = path.value.body || path.value.value.body + const kind = path.value.kind || path.value.value.kind + const key = path.value.key || path.value.value.key + const params = path.value.params || path.value.value.params + for (const e of body.body.filter(isElementDeclaration)) { elementGetters.set(e.expression.left.property, e.expression.right) } return [ - j.classMethod( - path.value.kind, - path.value.key, - path.value.params, - j.blockStatement(path.value.body.body.filter((e) => !isElementDeclaration(e))) - ), + j.classMethod(kind, key, params, j.blockStatement(body.body.filter((e) => !isElementDeclaration(e)))), ...[...elementGetters.entries()].map(([elemName, object]) => j.methodDefinition( 'get', elemName, @@ -798,7 +797,9 @@ module.exports = function transformer(file, api) { ) )) ] - }) + } + root.find(j.ClassMethod, { key: { name: 'constructor' } }).replaceWith(setGetters) + root.find(j.MethodDefinition, { key: { name: 'constructor' } }).replaceWith(setGetters) /** * transform lazy loaded element calls in async context, e.g. @@ -850,10 +851,10 @@ module.exports = function transformer(file, api) { j(path).closest(j.FunctionExpression).replaceWith(makeAsync) j(path).closest(j.ArrowFunctionExpression).replaceWith(makeAsync) j(path).closest(j.ClassMethod).replaceWith(makeAsync) - const constructorFilter = { key: { name: 'constructor' } } - j(path).closest(j.MethodDefinition, constructorFilter).forEach(failAsyncConstructor) - j(path).closest(j.ClassMethod, constructorFilter).forEach(failAsyncConstructor) + const throwConstructorError = () => failAsyncConstructor(path, file) + j(path).closest(j.MethodDefinition, constructorFilter).forEach(throwConstructorError) + j(path).closest(j.ClassMethod, constructorFilter).forEach(throwConstructorError) return j.awaitExpression(path.value) }) diff --git a/protractor/utils.js b/protractor/utils.js index be990da..eb0980a 100644 --- a/protractor/utils.js +++ b/protractor/utils.js @@ -514,7 +514,7 @@ function makeAsync ({ value, parentPath }) { return value } -function failAsyncConstructor (p) { +function failAsyncConstructor (path, file) { throw new TransformError('' + `With "this.${path.value.property.name}" you are ` + 'trying to access an element within a constructor. Given that it ' + diff --git a/test/__fixtures__/protractor/source/failing_constructor.js b/test/__fixtures__/protractor/source/failing_constructor.js index 7cd8bfb..e0aaf12 100644 --- a/test/__fixtures__/protractor/source/failing_constructor.js +++ b/test/__fixtures__/protractor/source/failing_constructor.js @@ -1,6 +1,6 @@ class FriendsPage { constructor() { - this.binding = $('h2.ng-binding') + this.binding = element(by.css('h2.ng-binding')) this.pageLoaded = this.isClickable(this.binding); } } From 1cee43288ed90c1c701bba7fca1115761c76d3cc Mon Sep 17 00:00:00 2001 From: christian-bromann Date: Thu, 6 May 2021 00:21:05 +0200 Subject: [PATCH 4/8] wip --- protractor/utils.js | 49 ++++++++++++++++++++++++++++++--------------- test/runner.js | 2 +- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/protractor/utils.js b/protractor/utils.js index eb0980a..1a085ce 100644 --- a/protractor/utils.js +++ b/protractor/utils.js @@ -34,10 +34,14 @@ class TransformError extends Error { } } -function isLiteral (val) { +function isStringLiteral (val) { return ['Literal', 'StringLiteral'].includes(val.type) } +function isNumericalLiteral (val) { + return ['Literal', 'NumericLiteral'].includes(val.type) +} + function getSelectorArgument (j, path, callExpr, file) { const bySelector = callExpr.callee.property.name const arg = callExpr.arguments[0] @@ -49,7 +53,7 @@ function getSelectorArgument (j, path, callExpr, file) { file ) } else if (bySelector === 'id') { - return [isLiteral(arg) + return [isStringLiteral(arg) ? j.literal(`#${arg.value}`) : j.templateLiteral([ j.templateElement({ raw: '#', cooked: '#' }, false) @@ -58,7 +62,7 @@ function getSelectorArgument (j, path, callExpr, file) { ]) ] } else if (bySelector === 'model') { - return [isLiteral(arg) + return [isStringLiteral(arg) ? j.literal(`*[ng-model="${arg.value}"]`) : j.templateLiteral([ j.templateElement({ raw: '*[ng-model="', cooked: '*[ng-model="' }, false), @@ -68,7 +72,7 @@ function getSelectorArgument (j, path, callExpr, file) { ]) ] } else if (bySelector === 'repeater') { - return [isLiteral(arg) + return [isStringLiteral(arg) ? j.literal(`*[ng-repeat="${arg.value}"]`) : j.templateLiteral([ j.templateElement({ raw: '*[ng-repeat="', cooked: '*[ng-repeat="' }, false), @@ -84,7 +88,7 @@ function getSelectorArgument (j, path, callExpr, file) { if (text.regex) { throw new TransformError('this codemod does not support RegExp in cssContainingText', path.value, file) - } else if (isLiteral(text)) { + } else if (isStringLiteral(text)) { return [j.literal(`${arg.value}=${text.value}`)] } else if (text.type === 'Identifier') { return [ @@ -100,21 +104,21 @@ function getSelectorArgument (j, path, callExpr, file) { } else if (bySelector === 'xpath' || bySelector === 'tagName' || bySelector === 'js') { return [arg] } else if (bySelector === 'linkText') { - return [isLiteral(arg) + return [isStringLiteral(arg) ? j.literal(`=${arg.value}`) : j.templateLiteral([ j.templateElement({ raw: '=', cooked: '=' }, false) ], [arg]) ] } else if (bySelector === 'partialLinkText') { - return [isLiteral(arg) + return [isStringLiteral(arg) ? j.literal(`*=${arg.value}`) : j.templateLiteral([ j.templateElement({ raw: '*=', cooked: '*=' }, false) ], [arg]) ] } else if (bySelector === 'name') { - return [isLiteral(arg) + return [isStringLiteral(arg) ? j.literal(`*[name="${arg.value}"]`) : j.templateLiteral([ j.templateElement({ raw: '*[name="', cooked: '*[name="' }, false), @@ -122,14 +126,14 @@ function getSelectorArgument (j, path, callExpr, file) { ], [arg]) ] } else if (bySelector === 'className') { - return [isLiteral(arg) + return [isStringLiteral(arg) ? j.literal(`.${arg.value}`) : j.templateLiteral([ j.templateElement({ raw: '.', cooked: '.' }, false) ], [arg]) ] } else if (bySelector === 'options') { - return [isLiteral(arg) + return [isStringLiteral(arg) ? j.literal(`select[ng-options="${arg.value}"] option`) : j.templateLiteral([ j.templateElement({ raw: 'select[ng-options="', cooked: 'select[ng-options="' }, false), @@ -137,14 +141,14 @@ function getSelectorArgument (j, path, callExpr, file) { ], [arg]) ] } else if (bySelector === 'buttonText') { - return [isLiteral(arg) + return [isStringLiteral(arg) ? j.literal(`button=${arg.value}`) : j.templateLiteral([ j.templateElement({ raw: 'button=', cooked: 'button=' }, false) ], [arg]) ] } else if (bySelector === 'partialButtonText') { - return [isLiteral(arg) + return [isStringLiteral(arg) ? j.literal(`button*=${arg.value}`) : j.templateLiteral([ j.templateElement({ raw: 'button*=', cooked: 'button*=' }, false) @@ -450,18 +454,31 @@ const filterFor = { } function sanitizeAsyncCalls (j, root) { root.find(j.AwaitExpression, filterFor) + .filter(({ value: { argument: { callee: { object } } } }) => ( + ( + object.object.type === 'MemberExpression' || + object.property.type !== 'Literal' + ) + && object.object.type !== 'AwaitExpression' + )) .filter(filterElementCalls) .replaceWith(({ value: { argument } }) => ( j.awaitExpression( j.callExpression( j.memberExpression( - argument.callee.object.property.type === 'NumericLiteral' + isNumericalLiteral(argument.callee.object.property) ? j.memberExpression( - argument.callee.object.object, + j.awaitExpression(argument.callee.object.object), argument.callee.object.property, - true, + true ) - : j.awaitExpression(argument.callee.object), + : j.awaitExpression( + j.memberExpression( + argument.callee.object.object, + argument.callee.object.property, + isNumericalLiteral(argument.callee.object.property) + ) + ), argument.callee.property ), argument.arguments, diff --git a/test/runner.js b/test/runner.js index 314518e..78f38a7 100644 --- a/test/runner.js +++ b/test/runner.js @@ -5,7 +5,7 @@ const expect = require('expect') const Runner = require('jscodeshift/src/Runner') -const supportedParsers = ['tsx', 'babel'] +const supportedParsers = ['babel', 'tsx'] const frameworkTests = { protractor: [ ['./conf.js', './conf.js'], From c3ff37167a141de9987815f5d1df2114bf7f32d3 Mon Sep 17 00:00:00 2001 From: christian-bromann Date: Thu, 6 May 2021 01:02:28 +0200 Subject: [PATCH 5/8] make v7 work --- common/compilers.js | 28 ++++++++--------- common/utils.js | 12 ++++++++ protractor/index.js | 6 ++-- protractor/utils.js | 13 ++++---- .../protractor/transformed/conf.js | 26 ++++++++-------- .../v7/transformed/compilerFunctions.js | 30 +++++++++---------- test/__fixtures__/v7/transformed/spec.js | 28 ++++++++--------- v7/index.js | 6 ++-- 8 files changed, 79 insertions(+), 70 deletions(-) create mode 100644 common/utils.js diff --git a/common/compilers.js b/common/compilers.js index a5f736b..eb0ab69 100644 --- a/common/compilers.js +++ b/common/compilers.js @@ -3,13 +3,15 @@ const { COMPILER_OPTS_MAPPING } = require('./constants') -exports.remove = function removeCompilers (j, root) { +const { isStringLiteral } = require('./utils') + +exports.remove = function removeCompilers (j, root, opts) { const autoCompileOpts = {} /** * remove compiler requires */ - root.find(j.Property) + root.find(opts.parser === 'babel' ? j.Property : j.ObjectProperty) .filter((path) => ( path.value.key && ( path.value.key.name === 'require' || @@ -23,7 +25,7 @@ exports.remove = function removeCompilers (j, root) { j.arrayExpression(path.value.value.elements.filter((value) => { let importName - if (value.type === 'Literal') { + if (isStringLiteral(value)) { importName = value.value } else if (value.type === 'ArrayExpression') { importName = value.elements[0].value @@ -86,26 +88,24 @@ exports.remove = function removeCompilers (j, root) { return autoCompileOpts } -exports.update = function (j, root, autoCompileOpts) { +exports.update = function (j, root, autoCompileOpts, opts) { /** * update config with compiler opts */ - let wasReplaced = false - root.find(j.ObjectProperty) + let wasInserted = false + root.find(opts.parser === 'babel' ? j.Property : j.ObjectProperty) .filter((path) => ( path.value.key && ( path.value.key.name === 'capabilities' || path.value.key.name === 'framework' ) )) - .replaceWith((path) => { - if (wasReplaced) { - return path.value + .forEach((path) => { + if (wasInserted) { + return } - - wasReplaced = true - return [ - path.value, + wasInserted = true + path.parentPath.value.push( ...(Object.keys(autoCompileOpts).length ? [j.objectProperty( j.identifier('autoCompileOpts'), @@ -124,6 +124,6 @@ exports.update = function (j, root, autoCompileOpts) { )] : [] ) - ] + ) }) } diff --git a/common/utils.js b/common/utils.js new file mode 100644 index 0000000..b77efe2 --- /dev/null +++ b/common/utils.js @@ -0,0 +1,12 @@ +function isStringLiteral (val) { + return ['Literal', 'StringLiteral'].includes(val.type) +} + +function isNumericalLiteral (val) { + return ['Literal', 'NumericLiteral'].includes(val.type) +} + +module.exports = { + isStringLiteral, + isNumericalLiteral +} diff --git a/protractor/index.js b/protractor/index.js index 8ed2854..9440e2a 100644 --- a/protractor/index.js +++ b/protractor/index.js @@ -30,7 +30,7 @@ const { makeAsync } = require('./utils') -module.exports = function transformer(file, api) { +module.exports = function transformer(file, api, opts) { const j = api.jscodeshift; const root = j(file.source); j.file = file @@ -92,7 +92,7 @@ module.exports = function transformer(file, api) { ) }) - const autoCompileOpts = compilers.remove(j, root) + const autoCompileOpts = compilers.remove(j, root, opts) /** * remove all protractor import declarations @@ -858,6 +858,6 @@ module.exports = function transformer(file, api) { return j.awaitExpression(path.value) }) - compilers.update(j, root, autoCompileOpts) + compilers.update(j, root, autoCompileOpts, opts) return root.toSource() } diff --git a/protractor/utils.js b/protractor/utils.js index 1a085ce..f576859 100644 --- a/protractor/utils.js +++ b/protractor/utils.js @@ -1,6 +1,11 @@ const url = require('url') const { format } = require('util') +const { + isStringLiteral, + isNumericalLiteral +} = require('../common/utils') + const { IGNORED_CONFIG_PROPERTIES, UNSUPPORTED_CONFIG_OPTION_ERROR, @@ -34,14 +39,6 @@ class TransformError extends Error { } } -function isStringLiteral (val) { - return ['Literal', 'StringLiteral'].includes(val.type) -} - -function isNumericalLiteral (val) { - return ['Literal', 'NumericLiteral'].includes(val.type) -} - function getSelectorArgument (j, path, callExpr, file) { const bySelector = callExpr.callee.property.name const arg = callExpr.arguments[0] diff --git a/test/__fixtures__/protractor/transformed/conf.js b/test/__fixtures__/protractor/transformed/conf.js index 01018d1..4e45159 100644 --- a/test/__fixtures__/protractor/transformed/conf.js +++ b/test/__fixtures__/protractor/transformed/conf.js @@ -26,18 +26,6 @@ exports.config = { } }], - autoCompileOpts: { - autoCompile: true, - - tsNodeOpts: { - project: require('path').join(__dirname, './tsconfig.e2e.json') - }, - - babelOpts: { - presets: [ 'es2015' ] - } - }, - capabilities: [{ 'browserName': 'chrome', protocol: "http", @@ -99,5 +87,17 @@ exports.config = { protocol: "https", port: 443, - hostname: "api.kobiton.com" + hostname: "api.kobiton.com", + + autoCompileOpts: { + autoCompile: true, + + tsNodeOpts: { + project: require('path').join(__dirname, './tsconfig.e2e.json') + }, + + babelOpts: { + presets: [ 'es2015' ] + } + } }; diff --git a/test/__fixtures__/v7/transformed/compilerFunctions.js b/test/__fixtures__/v7/transformed/compilerFunctions.js index 257a32f..1a9f59d 100644 --- a/test/__fixtures__/v7/transformed/compilerFunctions.js +++ b/test/__fixtures__/v7/transformed/compilerFunctions.js @@ -1,6 +1,21 @@ exports.config = { framework: 'jasmine', + cucumberOpts: { + requireModule: [() => { + console.log('foo'); + console.log('bar'); + }] + }, + + jasmineOpts: { + requires: [] + }, + + mochaOpts: { + require: [async function () {}] + }, + autoCompileOpts: { autoCompile: true, @@ -16,20 +31,5 @@ exports.config = { babelOpts: { ignore: [] } - }, - - cucumberOpts: { - requireModule: [() => { - console.log('foo'); - console.log('bar'); - }] - }, - - jasmineOpts: { - requires: [] - }, - - mochaOpts: { - require: [async function () {}] } } diff --git a/test/__fixtures__/v7/transformed/spec.js b/test/__fixtures__/v7/transformed/spec.js index 872c5a4..344304b 100644 --- a/test/__fixtures__/v7/transformed/spec.js +++ b/test/__fixtures__/v7/transformed/spec.js @@ -4,6 +4,20 @@ const { Given2, When2, Then2 } = require("@cucumber/cucumber"); exports.config = { framework: 'jasmine', + mochaOpts: { + ui: 'bdd', + timeout: 5000, + require: ['/foo/bar'] + }, + + jasmineOpts: { + requires: [] + }, + + cucumberOpts: { + requireModule: [] + }, + autoCompileOpts: { autoCompile: true, @@ -19,19 +33,5 @@ exports.config = { tsNodeOpts: { bar: 'foo' } - }, - - mochaOpts: { - ui: 'bdd', - timeout: 5000, - require: ['/foo/bar'] - }, - - jasmineOpts: { - requires: [] - }, - - cucumberOpts: { - requireModule: [] } } diff --git a/v7/index.js b/v7/index.js index 45d41e4..059012a 100644 --- a/v7/index.js +++ b/v7/index.js @@ -1,9 +1,9 @@ const compilers = require('../common/compilers') -module.exports = function transformer(file, api) { +module.exports = function transformer(file, api, opts) { const j = api.jscodeshift; const root = j(file.source); - const autoCompileOpts = compilers.remove(j, root) + const autoCompileOpts = compilers.remove(j, root, opts) /** * transforms imports from `require('cucumber')` to `require('@cucumber/cucumber')` @@ -28,6 +28,6 @@ module.exports = function transformer(file, api) { ) )) - compilers.update(j, root, autoCompileOpts) + compilers.update(j, root, autoCompileOpts, opts) return root.toSource() } From 68ccb5a7d87626dc82a9f303ccfcb93d4279e1c9 Mon Sep 17 00:00:00 2001 From: christian-bromann Date: Thu, 6 May 2021 01:08:29 +0200 Subject: [PATCH 6/8] make it compatible with v6 codemod --- v6/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/v6/index.js b/v6/index.js index 6919e4d..c8eaf8c 100644 --- a/v6/index.js +++ b/v6/index.js @@ -2,7 +2,7 @@ const { paramCase } = require('param-case') const { COMMAND_TRANSFORMS, COMMANDS_WITHOUT_FIRST_PARAM, SERVICE_PROPS, SERVICE_PROP_MAPPING } = require('./constants') -module.exports = function transformer(file, api) { +module.exports = function transformer(file, api, opts) { const j = api.jscodeshift; const root = j(file.source); @@ -98,7 +98,7 @@ module.exports = function transformer(file, api) { }) } - root.find(j.Property, { + root.find(opts.parser === 'babel' ? j.Property : j.ObjectProperty, { key: { name: 'services' } }).replaceWith((path) => j.property( 'init', From 78de3dcb30dc028185519c6456214f6a7006582b Mon Sep 17 00:00:00 2001 From: christian-bromann Date: Thu, 6 May 2021 01:11:11 +0200 Subject: [PATCH 7/8] add removed test --- test/__fixtures__/protractor/source/element.js | 1 + test/__fixtures__/protractor/transformed/element.js | 1 + 2 files changed, 2 insertions(+) diff --git a/test/__fixtures__/protractor/source/element.js b/test/__fixtures__/protractor/source/element.js index a40e2a4..5d16351 100644 --- a/test/__fixtures__/protractor/source/element.js +++ b/test/__fixtures__/protractor/source/element.js @@ -13,6 +13,7 @@ $('body').allowAnimations(false); await (await this.deleteButton).setValue('Some text...'); expect(await searchPage.noResultsMsg.isDisplayed()).toBe(true); expect(await (await loginPage.errorMessage).isDisplayed()).toBe(true); + expect(await (await loginPage.errorMessages)[0].isDisplayed()).toBe(true); })(); // Using getDriver to find the parent web element to find the cat li diff --git a/test/__fixtures__/protractor/transformed/element.js b/test/__fixtures__/protractor/transformed/element.js index e73a379..38b50ab 100644 --- a/test/__fixtures__/protractor/transformed/element.js +++ b/test/__fixtures__/protractor/transformed/element.js @@ -9,6 +9,7 @@ browser.$('.parent'); await (await this.deleteButton).setValue('Some text...'); expect(await (await searchPage.noResultsMsg).isDisplayed()).toBe(true); expect(await (await loginPage.errorMessage).isDisplayed()).toBe(true); + expect(await (await loginPage.errorMessages)[0].isDisplayed()).toBe(true); })(); // Using getDriver to find the parent web element to find the cat li From 9677e32ccf419a528f6c635221825b86f87a091d Mon Sep 17 00:00:00 2001 From: christian-bromann Date: Thu, 6 May 2021 01:12:06 +0200 Subject: [PATCH 8/8] revert test change --- test/__fixtures__/protractor/source/failing_constructor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/__fixtures__/protractor/source/failing_constructor.js b/test/__fixtures__/protractor/source/failing_constructor.js index e0aaf12..7cd8bfb 100644 --- a/test/__fixtures__/protractor/source/failing_constructor.js +++ b/test/__fixtures__/protractor/source/failing_constructor.js @@ -1,6 +1,6 @@ class FriendsPage { constructor() { - this.binding = element(by.css('h2.ng-binding')) + this.binding = $('h2.ng-binding') this.pageLoaded = this.isClickable(this.binding); } }