themummy
Version:
UNSTABLE. codecoverage married with integration testing. currently a work in progress
329 lines (296 loc) • 8.58 kB
JavaScript
const chromeDebug = require('chrome-remote-interface')
const chromeLauncher = require('chrome-launcher')
const path = require('path')
const fs = require('fs-extra')
const _ = require('lodash')
// jonos keys
// m stands for methods
// mc stands for call
// s stands for store
// ss stands for store.set
// sg stands for store.get
// sl stands for store lock
// slg stands for store lock get
// sls stands for store lock set
const m = {}
const s = {}
const sl = {}
const sls = (fnName, varName, val) => {
// fnName or fnNames to lock requests to
}
const ss = (apath, value) => {
_.set(s, apath, value)
}
const sg = (apath) => {
return _.get(s, apath)
}
const ms = (strPath, fn) => {
if (typeof fn !== 'function') {
throw new Error(`m is only a store of methods`)
}
if (_.get(m, strPath)) {
throw new Error(`m name already taken`)
}
_.set(m, strPath, fn)
}
const mg = (strPath) => {
const fn = _.get(m, strPath)
if (typeof fn !== 'function') {
throw new Error(`m at "${strPath}" is not a function`)
}
return fn
}
const mc = (strPath, ...args) => {
console.timeEnd(' ')
const fn = mg(strPath)
console.log(`m: ${strPath}`)
console.time(' ')
return fn(...args)
}
ms('setup', () => {
ss('out path', path.resolve('./cov'))
ss('snaps path', path.join(sg('out path'), 'snaps'))
ss('scripts path', path.join(sg('out path'), 'scripts'))
ss('scripts', {})
})
ms('chrome launch', () => {
return chromeLauncher.launch({
startingUrl: 'about:blank',
chromePath: '/bin/chromium',
port: 1111
}).catch(err=>{
console.log(err)
})
})
ms('chrometools setup', (client, tools) => {
return Promise.all([
_.map(tools, (tool) => { return client[tool].enable() })
])
})
ms('chrometools save script', async (scriptId, Debugger) => {
try {
const {scriptSource} = await Debugger.getScriptSource({scriptId})
await fs.outputFile(path.resolve(sg('scripts path'), `${scriptId}.js`), scriptSource)
} catch (err) {
console.log(err)
}
})
ms('string ends with .js', (name) => {
const l = name.length
return name.substr(l - 3, 3) === '.js'
})
ms('string omit ext', (n = 3) => {
return (name) => {
const l = name.length
return name.substr(0, l - n)
}
})
ms('write scriptUrls', async () => {
let scriptfiles = await fs.readdir(sg('scripts path'))
scriptfiles = _.filter(scriptfiles, mg('string ends with .js'))
scriptfiles = _.map(scriptfiles, mc('string omit ext', 3))
const scriptIndexObj = _.cloneDeep(sg('scripts'))
const scriptIndexArr = _.map(scriptIndexObj, (value) => { return value })
_.remove(scriptIndexArr, (script) => {
return !_.includes(scriptfiles, script.scriptId)
})
const index = _.reduce(scriptIndexArr, (o, script) => {
_.set(script, 'scriptId', Number(script.scriptId))
return _.set(o, script.scriptId, script)
}, {})
await fs.outputJSON(
path.resolve(sg('out path'), 'index-scripts-url.json'),
index,
{spaces: 2}
)
})
ms('coverage setup', async (client) => {
await mc('page goto', client, 'about:blank')
await Promise.all([
client.Profiler.start(),
client.Profiler.startPreciseCoverage()
])
})
ms('coverage snapsave', (() => {
let i = 0
return async (snap) => {
ss(`coverage.snaps[${i}]`, snap)
await fs.outputJSON(`${sg('snaps path')}/${i}`, snap, {spaces: 2})
i++
}
})())
ms('coverage snapshot', async (client, bRetartProfiler = true) => {
let {result: snap} = await client.Profiler.takePreciseCoverage()
snap = _.filter(snap, (o) => { return o.url !== '' }),
// snap = _.map(snap, fn => {
// fn.functions = fn.functions.filter(fn => {
// return (fn.ranges.length === 1 && fn.ranges[0].count > 0) || fn.ranges.length > 1
// })
// return fn
// })
await mc('coverage snapsave', snap)
const saveabelScriptIds = _.reduce(snap, (arr, file) => {
const script = sg(['scripts', file.scriptId])
if (script !== undefined) {
return arr
}
ss(['scripts', file.scriptId], _.pick(file, ['scriptId', 'url']))
arr.push(file.scriptId)
return arr
}, [])
await Promise.all(_.map(saveabelScriptIds, (id) => {
return mc('chrometools save script', id, client.Debugger)
}))
if (bRetartProfiler) {
await client.Profiler.startPreciseCoverage()
}
})
ms('rm cov, scripts', () => {
fs.remove(sg('out path'))
// return Promise.all([
// fs.remove(sg('snaps path')),
// fs.remove(sg('scripts path'))
// ])
})
ms('page hookload once', (client, fn) => {
client.Page.loadEventFired(_.once(() => {
mc(fn, client)
}))
})
ms('page goto', (client, url) => {
return client.Page.navigate({url})
})
ms('page firstload', async (client) => {
try {
await mc('user script', client)
await client.close()
} catch (err) {
console.error(err)
client.close()
}
})
ms('wait s', (n) => {
return new Promise((resolve, reject) => {
setTimeout(resolve, n * 1000)
})
})
ms('user script', async (client) => {
await mc('coverage snapshot', client)
await mc('click', client, '.nav > div:nth-child(2)')
await mc('wait s', 1)
await mc('coverage snapshot', client)
await mc('click', client, '#webdev > div > div:nth-child(2)')
await mc('wait s', 20)
await mc('coverage snapshot', client, false)
await mc('end')
})
ms('click xy', async (client, {x, y, button = 'left', clickCount = 1}) => {
const options = { x, y, button, clickCount }
options.type = 'mousePressed'
await client.Input.dispatchMouseEvent(options)
options.type = 'mouseReleased'
await client.Input.dispatchMouseEvent(options)
})
ms('click', async (client, selector) => {
const el = await mc('querySelector', client, selector)
const rect = await mc('el to rect', client, el)
// todo error if no rect
// todo error if size is too small
let x = []
let y = []
_.each(rect.model.padding, (n, i) => {
const tmp = (i % 2 === 0) ? x : y
tmp.push(n)
})
x = mc('arr average', x)
y = mc('arr average', y)
await mc('click xy', client, {x, y})
})
ms('arr average', (arr) => {
const sum = _.reduce(arr, (total, n) => {
return n + total
}, 0)
return sum / arr.length
})
ms('querySelector', async (client, selector) => {
const {DOM} = client
const doc = await DOM.getDocument()
const el = await DOM.querySelector({
selector,
nodeId: doc.root.nodeId
})
return el
})
ms('el to rect', async (client, el) => {
const {DOM} = client
const rect = await DOM.getBoxModel({nodeId: el.nodeId})
return rect
})
ms('write indexs', async () => {
let fileIndex = {}
let snapIndex = {}
const snapsdir = sg('snaps path')
const snapfilenames = await fs.readdir(snapsdir)
await Promise.all(_.map(snapfilenames, async (filename) => {
const snap = await fs.readJSON(path.resolve(snapsdir, filename))
_.each(snap, (coverage) => {
// scirpt ref snaps
if (fileIndex[coverage.scriptId] === undefined) {
fileIndex[coverage.scriptId] = []
}
fileIndex[coverage.scriptId].push(Number(filename))
// snap ref scripts
if (snapIndex[filename] === undefined) {
snapIndex[filename] = []
}
snapIndex[filename].push(Number(coverage.scriptId))
})
}))
snapIndex = mc('sort index', snapIndex)
fileIndex = mc('sort index', fileIndex)
await Promise.all([
fs.writeJSON(path.resolve(sg('out path'), 'index-snap-scripts.json'), snapIndex, {spaces: 2}),
fs.writeJSON(path.resolve(sg('out path'), 'index-script-snaps.json'), fileIndex, {spaces: 2})
])
})
ms('sort index', (index) => {
return _.chain(_.keys(index))
.sortBy()
.reduce((o, ikey) => {
return _.set(o, ikey, _.sortBy(index[ikey]))
}, {})
.value()
})
ms('main', async (client) => {
try {
await mc('setup')
await mc('rm cov, scripts')
await mc('chrometools setup', client, ['Network', 'Page', 'Profiler', 'Debugger', 'Runtime', 'DOM'])
await mc('coverage setup', client)
await mc('page hookload once', client, 'page firstload')
await mc('page goto', client, 'https://blogjono.com')
} catch (err) {
console.error(err)
client.close(() => {
process.exit()
})
}
})
ms('start', async () => {
const chrome = await mc('chrome launch')
ss('chrome', chrome)
const chromedebug = chromeDebug({port: 1111}, mg('main'))
chromedebug.on('error', (err) => {
// cannot connect to the remote endpoint
console.error(err)
debugger
})
})
ms('end', async() => {
await Promise.all([
mc('write scriptUrls'),
mc('write indexs'),
sg('chrome').kill()
])
})
mc('start')