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:
Mykola Grybyk 2019-07-23 11:08:17 +02:00 committed by Christian Bromann
parent 5db7dade9d
commit 7d21566511
15 changed files with 165 additions and 29 deletions

View file

@ -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)
}

View file

@ -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')
})
}

View file

@ -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",

View 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

View 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

View file

@ -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) => {
/**

View file

@ -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 }
})
}
/**

View file

@ -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))

View 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)
})

View file

@ -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 () => {

View file

@ -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 () => {

View file

@ -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',

View 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)
})

View file

@ -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(() => {

View 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%
})
})