Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ This project contains various codemods to help migrating from either one major W

- [x] v5 ▶️  v6 (see [migration guide](https://webdriver.io/docs/v6-migration))
- [x] v6 ▶️  v7 (see [migration guide](https://webdriver.io/docs/v7-migration))
- [x] v9 ▶️  v10 legacy command signatures (see [migration guide](https://webdriver.io/docs/v10-migration#legacy-command-signatures))
- [x] Protractor ▶️  WebdriverIO (see [migration guide](https://webdriver.io/docs/protractor-migration))
- [x] Sync ▶️  Async (see [migration guide](https://webdriver.io/docs/async-migration))
- Note: This codemod depends on the [proxying of chained async calls](https://webdriver.io/blog/2021/07/28/sync-api-deprecation/) added in v7.9
Expand All @@ -30,6 +31,8 @@ $ npx jscodeshift -t ./node_modules/@wdio/codemod/<framework> <path>
$ npx jscodeshift -t ./node_modules/@wdio/codemod/v6 ./e2e/
# e.g. to migrate from v6 to v7:
$ npx jscodeshift -t ./node_modules/@wdio/codemod/v7 ./e2e/
# e.g. to update v9 command signatures for v10:
$ npx jscodeshift -t ./node_modules/@wdio/codemod/v10 ./e2e/
# e.g. to transform Protractor code:
$ npx jscodeshift -t ./node_modules/@wdio/codemod/protractor ./e2e/
# e.g. to tranform from sync to async
Expand Down
16 changes: 16 additions & 0 deletions test/__fixtures__/v10/source/spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
browser.addCommand('myFn', fn, true)
browser.addCommand('other', fn, false)
browser.addCommand('already', fn, { attachToElement: true })
browser.overwriteCommand('click', fn, true)
browser.addCommand('withProto', fn, true, proto, instances)

await $('h1').getHTML(false)
await $('h1').getHTML(true)
await $('h1').getHTML({ includeSelectorTag: false })

await browser.getCookies('session')
await browser.getCookie('auth')
await browser.getCookies(['session'])
await browser.getCookies(['session', 'auth'])
await browser.getCookies({ name: 'kept' })
await browser.getCookies()
36 changes: 36 additions & 0 deletions test/__fixtures__/v10/transformed/spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
browser.addCommand('myFn', fn, {
attachToElement: true
})
browser.addCommand('other', fn, {
attachToElement: false
})
browser.addCommand('already', fn, { attachToElement: true })
browser.overwriteCommand('click', fn, {
attachToElement: true
})
browser.addCommand('withProto', fn, {
attachToElement: true,
proto: proto,
instances: instances
})

await $('h1').getHTML({
includeSelectorTag: false
})
await $('h1').getHTML({
includeSelectorTag: true
})
await $('h1').getHTML({ includeSelectorTag: false })

await browser.getCookies({
name: 'session'
})
await browser.getCookies({
name: 'auth'
})
await browser.getCookies({
name: 'session'
})
await browser.getCookies(['session', 'auth'])
await browser.getCookies({ name: 'kept' })
await browser.getCookies()
3 changes: 3 additions & 0 deletions test/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ const frameworkTests = {
['./spec.js', './spec.js'],
['./compilerFunctions.js', './compilerFunctions.js']
],
v10: [
['./spec.js', './spec.js']
],
v6: [
['./spec.js', './spec.js'],
['./conf.js', './conf.js']
Expand Down
117 changes: 117 additions & 0 deletions v10/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* Mechanical v9 → v10 command signature updates:
* - addCommand / overwriteCommand boolean scope → `{ attachToElement }`
* - getHTML(boolean) → getHTML({ includeSelectorTag })
* - getCookies('name') / getCookies(['name']) / getCookie('name') → getCookies({ name })
*/
module.exports = function transformer (file, api) {
const j = api.jscodeshift
const root = j(file.source)

root.find(j.CallExpression).forEach((path) => {
const callee = path.value.callee
const name = memberName(callee)
if (!name) {
return
}

const args = path.value.arguments
if ((name === 'addCommand' || name === 'overwriteCommand') && isBoolean(args[2])) {
const properties = [
property(j, 'attachToElement', args[2])
]
if (args[3] && !isUndefined(args[3])) {
properties.push(property(j, 'proto', args[3]))
}
if (args[4] && !isUndefined(args[4])) {
properties.push(property(j, 'instances', args[4]))
}
path.value.arguments = [args[0], args[1], j.objectExpression(properties)]
return
}

if (name === 'getHTML' && args.length === 1 && isBoolean(args[0])) {
path.value.arguments = [
j.objectExpression([property(j, 'includeSelectorTag', args[0])])
]
return
}

if (name !== 'getCookies' && name !== 'getCookie') {
return
}

const filter = cookieNameFilter(j, args[0])
if (!filter) {
return
}

if (name === 'getCookie') {
renameMember(callee, 'getCookies')
}
args[0] = filter
})

return root.toSource()
}

function memberName (callee) {
if (!callee || !callee.property || callee.computed && !isString(callee.property)) {
return
}
if (callee.property.type === 'Identifier') {
return callee.property.name
}
if (isString(callee.property)) {
return callee.property.value
}
}

function renameMember (callee, name) {
if (callee.property.type === 'Identifier') {
callee.property.name = name
return
}
callee.property.value = name
if ('extra' in callee.property && callee.property.extra) {
callee.property.extra.raw = `'${name}'`
}
}

function property (j, key, value) {
return j.property('init', j.identifier(key), value)
}

function isBoolean (node) {
return Boolean(node) && (
node.type === 'BooleanLiteral' ||
(node.type === 'Literal' && typeof node.value === 'boolean')
)
}

function isString (node) {
return Boolean(node) && (
node.type === 'StringLiteral' ||
(node.type === 'Literal' && typeof node.value === 'string')
)
}

function isUndefined (node) {
return (node.type === 'Identifier' && node.name === 'undefined') ||
(node.type === 'UnaryExpression' && node.operator === 'void')
}

function cookieNameFilter (j, node) {
if (isString(node)) {
return j.objectExpression([property(j, 'name', node)])
}

if (
node &&
node.type === 'ArrayExpression' &&
node.elements.length === 1 &&
isString(node.elements[0])
) {
return j.objectExpression([property(j, 'name', node.elements[0])])
}
}
Loading