All files / wdio-devtools-service/src commands.js

98% Statements 49/50
93.75% Branches 15/16
93.75% Functions 15/16
97.92% Lines 47/48

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155                1x       9x 9x 9x 9x         9x 81x 72x         9x 1x 1x 1x               3x 1x     2x 1x     1x 1x   1x       1x                 1x 1x             1x 1x       1x             1x 1x       1x                   2x 1x     1x 1x                         2x 1x     1x 1x 1x         1x 1x 1x 1x       1x 1x                           1x 1x 1x 1x      
import 'core-js/modules/web.url'
import logger from '@wdio/logger'
 
import NetworkHandler from './handler/network'
 
import { DEFAULT_TRACING_CATEGORIES } from './constants'
import { readIOStream, sumByKey } from './utils'
 
const log = logger('@wdio/devtools-service:CommandHandler')
 
export default class CommandHandler {
    constructor (client, browser) {
        this.client = client
        this.browser = browser
        this.isTracing = false
        this.networkHandler = new NetworkHandler(client)
 
        /**
         * register browser commands
         */
        const commands = Object.getOwnPropertyNames(Object.getPrototypeOf(this)).filter(
            fnName => fnName !== 'constructor' && !fnName.startsWith('_'))
        commands.forEach(fnName => this.browser.addCommand(fnName, ::this[fnName]))
 
        /**
         * propagate CDP events to the browser event listener
         */
        this.client.on('event', (event) => {
            const method = event.method || 'event'
            log.debug(`cdp event: ${method} with params ${JSON.stringify(event.params)}`)
            this.browser.emit(method, event.params)
        })
    }
 
    /**
     * allow to easily access the CDP from the browser object
     */
    cdp (domain, command, args = {}) {
        if (!this.client[domain]) {
            throw new Error(`Domain "${domain}" doesn't exist in the Chrome DevTools protocol`)
        }
 
        if (!this.client[domain][command]) {
            throw new Error(`The "${domain}" domain doesn't have a method called "${command}"`)
        }
 
        log.info(`Send command "${domain}.${command}" with args: ${JSON.stringify(args)}`)
        return new Promise((resolve, reject) => this.client[domain][command](args, (err, result) => {
            /* istanbul ignore if */
            if (err) {
                return reject(new Error(`Chrome DevTools Error: ${result.message}`))
            }
 
            return resolve(result)
        }))
    }
 
    /**
     * helper method to receive Chrome remote debugging connection data to
     * e.g. use external tools like lighthouse
     */
    cdpConnection () {
        const { host, port } = this.client
        return { host, port }
    }
 
    /**
     * get nodeId to use for other commands
     */
    async getNodeId (selector) {
        const document = await this.cdp('DOM', 'getDocument')
        const { nodeId } = await this.cdp(
            'DOM', 'querySelector',
            { nodeId: document.root.nodeId, selector }
        )
        return nodeId
    }
 
    /**
     * get nodeIds to use for other commands
     */
    async getNodeIds (selector) {
        const document = await this.cdp('DOM', 'getDocument')
        const { nodeIds } = await this.cdp(
            'DOM', 'querySelectorAll',
            { nodeId: document.root.nodeId, selector }
        )
        return nodeIds
    }
 
    /**
     * start tracing the browser
     *
     * @param  {string[]} [categories=DEFAULT_TRACING_CATEGORIES]  categories to trace for
     * @param  {Number}   [samplingFrequency=10000]                sampling frequency
     */
    startTracing (categories = DEFAULT_TRACING_CATEGORIES, samplingFrequency = 10000) {
        if (this.isTracing) {
            throw new Error('browser is already being traced')
        }
 
        this.isTracing = true
        return this.cdp('Tracing', 'start', {
            categories: categories.join(','),
            transferMode: 'ReturnAsStream',
            options: `sampling-frequency=${samplingFrequency}` // 1000 is default and too slow.
        })
    }
 
    /**
     * stop tracing the browser
     *
     * @return {Number}  tracing id to use for other commands
     */
    async endTracing () {
        if (!this.isTracing) {
            throw new Error('No tracing was initiated, call `browser.startTracing()` first')
        }
 
        this.cdp('Tracing', 'end')
        const stream = await new Promise((resolve, reject) => {
            const timeout = setTimeout(
                /* istanbul ignore next */
                () => reject('Did not receive a Tracing.tracingComplete event'),
                5000)
 
            this.browser.once('Tracing.tracingComplete', ({ stream }) => {
                clearTimeout(timeout)
                resolve(stream)
                this.isTracing = false
            })
        })
 
        this.traceEvents = await readIOStream(::this.cdp, stream)
        return stream
    }
 
    /**
     * get raw trace logs
     */
    getTraceLogs () {
        return this.traceEvents
    }
 
    /**
     * get page weight from last page load
     */
    getPageWeight () {
        const pageWeight = sumByKey(Object.values(this.networkHandler.requestTypes), 'size')
        const transferred = sumByKey(Object.values(this.networkHandler.requestTypes), 'encoded')
        const requestCount = sumByKey(Object.values(this.networkHandler.requestTypes), 'count')
        return { pageWeight, transferred, requestCount, details: this.networkHandler.requestTypes }
    }
}