Browser/element prototype performance improvements (#4229)
* element prototype performance improvements * webdriverio: fix getElements performance issue * wdio-runner: mock fs in test * webdriverio: mock fs in commands tests * webdriverio: loadCommands function * webdriverio: use import in commands tests * webdriverio: remove readdirSync * webdriverio: discard changes in some tests * webdriverio: utils - remove async * tests: add sample test used for performance measurement
This commit is contained in:
parent
5db7dade9d
commit
7d21566511
15 changed files with 165 additions and 29 deletions
|
|
@ -1,5 +1,4 @@
|
|||
import logger from '@wdio/logger'
|
||||
import merge from 'lodash.merge'
|
||||
import { validateConfig } from '@wdio/config'
|
||||
|
||||
import webdriverMonad from './monad'
|
||||
|
|
@ -62,7 +61,7 @@ export default class WebDriver {
|
|||
const environment = environmentDetector(params)
|
||||
const environmentPrototype = getEnvironmentVars(environment)
|
||||
const protocolCommands = getPrototype(environment)
|
||||
const prototype = merge(protocolCommands, environmentPrototype, userPrototype)
|
||||
const prototype = { ...protocolCommands, ...environmentPrototype, ...userPrototype }
|
||||
const monad = webdriverMonad(params, modifier, prototype)
|
||||
return monad(response.value.sessionId || response.sessionId, commandWrapper)
|
||||
}
|
||||
|
|
@ -85,7 +84,7 @@ export default class WebDriver {
|
|||
|
||||
const environmentPrototype = getEnvironmentVars(options)
|
||||
const protocolCommands = getPrototype(options)
|
||||
const prototype = merge(protocolCommands, environmentPrototype, userPrototype)
|
||||
const prototype = { ...protocolCommands, ...environmentPrototype, ...userPrototype }
|
||||
const monad = webdriverMonad(options, modifier, prototype)
|
||||
return monad(options.sessionId, commandWrapper)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import url from 'url'
|
|||
import http from 'http'
|
||||
import path from 'path'
|
||||
import https from 'https'
|
||||
import merge from 'lodash.merge'
|
||||
import request from 'request'
|
||||
import EventEmitter from 'events'
|
||||
|
||||
|
|
@ -38,7 +37,7 @@ export default class WebDriverRequest extends EventEmitter {
|
|||
}
|
||||
|
||||
makeRequest (options, sessionId) {
|
||||
const fullRequestOptions = merge({}, this.defaultOptions, this._createOptions(options, sessionId))
|
||||
const fullRequestOptions = Object.assign({}, this.defaultOptions, this._createOptions(options, sessionId))
|
||||
this.emit('request', fullRequestOptions)
|
||||
return this._request(fullRequestOptions, options.connectionRetryCount)
|
||||
}
|
||||
|
|
@ -55,7 +54,7 @@ export default class WebDriverRequest extends EventEmitter {
|
|||
*/
|
||||
if (this.body && (Object.keys(this.body).length || this.method === 'POST')) {
|
||||
requestOptions.body = this.body
|
||||
requestOptions.headers = merge({}, requestOptions.headers, {
|
||||
requestOptions.headers = Object.assign({}, requestOptions.headers, {
|
||||
'Content-Length': Buffer.byteLength(JSON.stringify(requestOptions.body), 'UTF-8')
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,9 +61,9 @@
|
|||
"archiver": "^3.0.0",
|
||||
"css-value": "^0.0.1",
|
||||
"grapheme-splitter": "^1.0.2",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.isobject": "^3.0.2",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.merge": "^4.6.1",
|
||||
"lodash.zip": "^4.2.0",
|
||||
"resq": "^1.5.0",
|
||||
"rgb2hex": "^0.1.0",
|
||||
|
|
|
|||
25
packages/webdriverio/src/commands/browser.js
Normal file
25
packages/webdriverio/src/commands/browser.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
export const $$ = require('./browser/$$').default
|
||||
export const $ = require('./browser/$').default
|
||||
export const call = require('./browser/call').default
|
||||
export const debug = require('./browser/debug').default
|
||||
export const deleteCookies = require('./browser/deleteCookies').default
|
||||
export const execute = require('./browser/execute').default
|
||||
export const executeAsync = require('./browser/executeAsync').default
|
||||
export const getCookies = require('./browser/getCookies').default
|
||||
export const getWindowSize = require('./browser/getWindowSize').default
|
||||
export const keys = require('./browser/keys').default
|
||||
export const newWindow = require('./browser/newWindow').default
|
||||
export const pause = require('./browser/pause').default
|
||||
export const react$$ = require('./browser/react$$').default
|
||||
export const react$ = require('./browser/react$').default
|
||||
export const reloadSession = require('./browser/reloadSession').default
|
||||
export const saveRecordingScreen = require('./browser/saveRecordingScreen').default
|
||||
export const saveScreenshot = require('./browser/saveScreenshot').default
|
||||
export const setCookies = require('./browser/setCookies').default
|
||||
export const setTimeout = require('./browser/setTimeout').default
|
||||
export const setWindowSize = require('./browser/setWindowSize').default
|
||||
export const switchWindow = require('./browser/switchWindow').default
|
||||
export const touchAction = require('./browser/touchAction').default
|
||||
export const uploadFile = require('./browser/uploadFile').default
|
||||
export const url = require('./browser/url').default
|
||||
export const waitUntil = require('./browser/waitUntil').default
|
||||
38
packages/webdriverio/src/commands/element.js
Normal file
38
packages/webdriverio/src/commands/element.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
export const $$ = require('./element/$$').default
|
||||
export const $ = require('./element/$').default
|
||||
export const addValue = require('./element/addValue').default
|
||||
export const clearValue = require('./element/clearValue').default
|
||||
export const click = require('./element/click').default
|
||||
export const doubleClick = require('./element/doubleClick').default
|
||||
export const dragAndDrop = require('./element/dragAndDrop').default
|
||||
export const getAttribute = require('./element/getAttribute').default
|
||||
export const getCSSProperty = require('./element/getCSSProperty').default
|
||||
export const getHTML = require('./element/getHTML').default
|
||||
export const getLocation = require('./element/getLocation').default
|
||||
export const getProperty = require('./element/getProperty').default
|
||||
export const getSize = require('./element/getSize').default
|
||||
export const getTagName = require('./element/getTagName').default
|
||||
export const getText = require('./element/getText').default
|
||||
export const getValue = require('./element/getValue').default
|
||||
export const isDisplayed = require('./element/isDisplayed').default
|
||||
export const isDisplayedInViewport = require('./element/isDisplayedInViewport').default
|
||||
export const isEnabled = require('./element/isEnabled').default
|
||||
export const isExisting = require('./element/isExisting').default
|
||||
export const isFocused = require('./element/isFocused').default
|
||||
export const isSelected = require('./element/isSelected').default
|
||||
export const moveTo = require('./element/moveTo').default
|
||||
export const react$$ = require('./element/react$$').default
|
||||
export const react$ = require('./element/react$').default
|
||||
export const saveScreenshot = require('./element/saveScreenshot').default
|
||||
export const scrollIntoView = require('./element/scrollIntoView').default
|
||||
export const selectByAttribute = require('./element/selectByAttribute').default
|
||||
export const selectByIndex = require('./element/selectByIndex').default
|
||||
export const selectByVisibleText = require('./element/selectByVisibleText').default
|
||||
export const setValue = require('./element/setValue').default
|
||||
export const shadow$$ = require('./element/shadow$$').default
|
||||
export const shadow$ = require('./element/shadow$').default
|
||||
export const touchAction = require('./element/touchAction').default
|
||||
export const waitForDisplayed = require('./element/waitForDisplayed').default
|
||||
export const waitForEnabled = require('./element/waitForEnabled').default
|
||||
export const waitForExist = require('./element/waitForExist').default
|
||||
export const waitUntil = require('./element/waitUntil').default
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import zip from 'lodash.zip'
|
||||
import merge from 'lodash.merge'
|
||||
import clone from 'lodash.clonedeep'
|
||||
import { webdriverMonad } from 'webdriver'
|
||||
import { wrapCommand } from '@wdio/config'
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ export default class MultiRemote {
|
|||
* ```
|
||||
*/
|
||||
static elementWrapper (instances, result, propertiesObject) {
|
||||
const prototype = merge({}, propertiesObject, getPrototype('element'), { scope: 'element' })
|
||||
const prototype = { ...propertiesObject, ...clone(getPrototype('element')), scope: 'element' }
|
||||
|
||||
const element = webdriverMonad({}, (client) => {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -10,16 +10,21 @@ import { URL } from 'url'
|
|||
import { ELEMENT_KEY, UNICODE_CHARACTERS } from './constants'
|
||||
import { findStrategy } from './utils/findStrategy'
|
||||
|
||||
const browserCommands = require('./commands/browser')
|
||||
const elementCommands = require('./commands/element')
|
||||
|
||||
const log = logger('webdriverio')
|
||||
const INVALID_SELECTOR_ERROR = 'selector needs to be typeof `string` or `function`'
|
||||
|
||||
const scopes = {
|
||||
browser: browserCommands,
|
||||
element: elementCommands
|
||||
}
|
||||
|
||||
const applyScopePrototype = (prototype, scope) => {
|
||||
const dir = path.resolve(__dirname, 'commands', scope)
|
||||
const files = fs.readdirSync(dir)
|
||||
for (let filename of files) {
|
||||
const commandName = path.basename(filename, path.extname(filename))
|
||||
prototype[commandName] = { value: require(path.join(dir, commandName)).default }
|
||||
}
|
||||
Object.entries(scopes[scope]).forEach(([commandName, command]) => {
|
||||
prototype[commandName] = { value: command }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { webdriverMonad } from 'webdriver'
|
||||
import { wrapCommand, runFnInFiberContext } from '@wdio/config'
|
||||
import merge from 'lodash.merge'
|
||||
import clone from 'lodash.clonedeep'
|
||||
|
||||
import { getBrowserObject, getPrototype as getWDIOPrototype, getElementFromResponse } from '../utils'
|
||||
import { elementErrorHandler } from '../middlewares'
|
||||
|
|
@ -14,7 +14,7 @@ import { ELEMENT_KEY } from '../constants'
|
|||
*/
|
||||
export const getElement = function findElement (selector, res) {
|
||||
const browser = getBrowserObject(this)
|
||||
const prototype = merge({}, browser.__propertiesObject__, getWDIOPrototype('element'), { scope: 'element' })
|
||||
const prototype = { ...clone(browser.__propertiesObject__), ...getWDIOPrototype('element'), scope: 'element' }
|
||||
|
||||
const element = webdriverMonad(this.options, (client) => {
|
||||
const elementId = getElementFromResponse(res)
|
||||
|
|
@ -61,7 +61,7 @@ export const getElement = function findElement (selector, res) {
|
|||
*/
|
||||
export const getElements = function getElements (selector, res) {
|
||||
const browser = getBrowserObject(this)
|
||||
const prototype = merge({}, browser.__propertiesObject__, getWDIOPrototype('element'), { scope: 'element' })
|
||||
const partialPrototype = { ...getWDIOPrototype('element'), scope: 'element' }
|
||||
|
||||
const elements = res.map((res, i) => {
|
||||
const element = webdriverMonad(this.options, (client) => {
|
||||
|
|
@ -90,7 +90,7 @@ export const getElements = function getElements (selector, res) {
|
|||
client.index = i
|
||||
client.emit = ::this.emit
|
||||
return client
|
||||
}, prototype)
|
||||
}, { ...clone(browser.__propertiesObject__), ...partialPrototype })
|
||||
|
||||
const elementInstance = element(this.sessionId, elementErrorHandler(wrapCommand))
|
||||
|
||||
|
|
|
|||
14
packages/webdriverio/tests/commands/browser.test.js
Normal file
14
packages/webdriverio/tests/commands/browser.test.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { getPrototype } from '../../src/utils'
|
||||
|
||||
const scope = 'browser'
|
||||
const dir = path.resolve(__dirname, '../..', 'src', 'commands', scope)
|
||||
const files = fs
|
||||
.readdirSync(dir)
|
||||
.map(f => path.basename(f, path.extname(f)))
|
||||
|
||||
test(scope + ' commands list', () => {
|
||||
const prototype = Object.keys(getPrototype(scope))
|
||||
expect(prototype).toEqual(files)
|
||||
})
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import fs from 'fs'
|
||||
import request from 'request'
|
||||
import { remote } from '../../../src'
|
||||
import * as utils from '../../../src/utils'
|
||||
|
||||
jest.mock('fs')
|
||||
|
||||
describe('saveRecordingScreen', () => {
|
||||
jest.mock('fs')
|
||||
const fs = require('fs').default
|
||||
let browser, getAbsoluteFilepathSpy, assertDirectoryExistsSpy, writeFileSyncSpy
|
||||
|
||||
beforeEach(async () => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
|
||||
import fs from 'fs'
|
||||
import request from 'request'
|
||||
import { remote } from '../../../src'
|
||||
import * as utils from '../../../src/utils'
|
||||
|
||||
jest.mock('fs')
|
||||
|
||||
describe('saveScreenshot', () => {
|
||||
jest.mock('fs')
|
||||
const fs = require('fs').default
|
||||
let browser, getAbsoluteFilepathSpy, assertDirectoryExistsSpy, writeFileSyncSpy
|
||||
|
||||
beforeEach(async () => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import path from 'path'
|
||||
import archiver from 'archiver'
|
||||
|
||||
import { remote } from '../../../src'
|
||||
|
||||
describe('uploadFile', () => {
|
||||
jest.mock('fs')
|
||||
jest.mock('fs')
|
||||
|
||||
describe('uploadFile', () => {
|
||||
it('should throw if browser does not support it', async function () {
|
||||
const browser = await remote({
|
||||
baseUrl: 'http://webdriver.io',
|
||||
|
|
|
|||
14
packages/webdriverio/tests/commands/element.test.js
Normal file
14
packages/webdriverio/tests/commands/element.test.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { getPrototype } from '../../src/utils'
|
||||
|
||||
const scope = 'element'
|
||||
const dir = path.resolve(__dirname, '../..', 'src', 'commands', scope)
|
||||
const files = fs
|
||||
.readdirSync(dir)
|
||||
.map(f => path.basename(f, path.extname(f)))
|
||||
|
||||
test(scope + ' commands list', () => {
|
||||
const prototype = Object.keys(getPrototype(scope))
|
||||
expect(prototype).toEqual(files)
|
||||
})
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import fs from 'fs'
|
||||
import request from 'request'
|
||||
import { remote } from '../../../src'
|
||||
import * as utils from '../../../src/utils'
|
||||
|
||||
jest.mock('fs')
|
||||
|
||||
describe('saveScreenshot', () => {
|
||||
jest.mock('fs')
|
||||
const fs = require('fs').default
|
||||
let getAbsoluteFilepathSpy, assertDirectoryExistsSpy, writeFileSyncSpy
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
41
tests/performance/sample.js
Normal file
41
tests/performance/sample.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* This is just an example, test is not running anywhere.
|
||||
*/
|
||||
|
||||
describe('perf', () => {
|
||||
before(() => {
|
||||
browser.url('http://adam.goucher.ca/parkcalc/')
|
||||
$('body').waitForExist()
|
||||
browser.pause(2000)
|
||||
})
|
||||
|
||||
it('getting $$[idx] performance', () => {
|
||||
const elems = browser.$$('*')
|
||||
|
||||
const start = Date.now()
|
||||
elems[10].$('not-existing')
|
||||
console.log(Date.now() - start)
|
||||
|
||||
// tests on my local machine
|
||||
// before: 42150 ms, 2048 calls
|
||||
// after: 50 ms, 2 calls
|
||||
//
|
||||
// Duration decreased by 99.9%
|
||||
// (before - after) / after * 100 = 99.88%
|
||||
})
|
||||
|
||||
it('getting $ performance', () => {
|
||||
const start = Date.now()
|
||||
for (let i = 0; i < 100; i++) {
|
||||
$('body')
|
||||
}
|
||||
console.log(Date.now() - start)
|
||||
|
||||
// tests on my local machine
|
||||
// before: 1200
|
||||
// after: 1100
|
||||
//
|
||||
// Duration decreased by 8%
|
||||
// (before - after) / after * 100 = 8%
|
||||
})
|
||||
})
|
||||
Loading…
Reference in a new issue