spectron-test
Version:
Easily test your Electron apps using ChromeDriver and WebdriverIO.
510 lines (427 loc) • 14.6 kB
JavaScript
var apiCache = {}
function Api (app, requireName) {
this.app = app
this.requireName = requireName
}
Api.prototype.initialize = function () {
return this.load().then(this.addApiCommands.bind(this))
}
Api.prototype.addApiCommands = function (api) {
if (!this.nodeIntegration) return
this.addRenderProcessApis(api.electron)
this.addMainProcessApis(api.electron.remote)
this.addBrowserWindowApis(api.browserWindow)
this.addWebContentsApis(api.webContents)
this.addProcessApis(api.rendererProcess)
this.api = {
browserWindow: api.browserWindow,
electron: api.electron,
rendererProcess: api.rendererProcess,
webContents: api.webContents
}
this.addClientProperties()
}
Api.prototype.load = function () {
var self = this
return this.isNodeIntegrationEnabled().then(function (nodeIntegration) {
self.nodeIntegration = nodeIntegration
if (!nodeIntegration) {
return {
electron: { remote: {} },
browserWindow: {},
webContents: {},
rendererProcess: {}
}
}
return self.getVersion().then(function (version) {
var api = apiCache[version]
if (api) return api
return self.loadApi().then(function (api) {
if (version) apiCache[version] = api
return api
})
})
})
}
Api.prototype.isNodeIntegrationEnabled = function () {
return this.app.client.execute(function (requireName) {
return typeof window[requireName] === 'function'
}, this.requireName).then(getResponseValue)
}
Api.prototype.getVersion = function () {
return this.app.client.execute(function (requireName) {
var process = window[requireName]('process')
if (process != null && process.versions != null) {
return process.versions.electron
}
}, this.requireName).then(getResponseValue)
}
Api.prototype.loadApi = function () {
return this.app.client.execute(function (requireName) {
if (typeof window[requireName] !== 'function') {
throw new Error('Could not find global require method with name: ' + requireName)
}
var electron = window[requireName]('electron')
var process = window[requireName]('process')
var api = {
browserWindow: {},
electron: {},
rendererProcess: {},
webContents: {}
}
function ignoreModule (moduleName) {
switch (moduleName) {
case 'CallbacksRegistry':
case 'deprecate':
case 'deprecations':
case 'hideInternalModules':
case 'Tray':
return true
case 'inAppPurchase':
return process.platform !== 'darwin'
}
return false
}
function isRemoteFunction (name) {
switch (name) {
case 'BrowserWindow':
case 'Menu':
case 'MenuItem':
return false
}
return typeof electron.remote[name] === 'function'
}
function ignoreApi (apiName) {
switch (apiName) {
case 'prototype':
return true
default:
return apiName[0] === '_'
}
}
function addModule (parent, parentName, name, api) {
api[name] = {}
for (var key in parent[name]) {
if (ignoreApi(key)) continue
api[name][key] = parentName + '.' + name + '.' + key
}
}
function addRenderProcessModules () {
Object.getOwnPropertyNames(electron).forEach(function (key) {
if (ignoreModule(key)) return
if (key === 'remote') return
addModule(electron, 'electron', key, api.electron)
})
}
function addMainProcessModules () {
api.electron.remote = {}
Object.getOwnPropertyNames(electron.remote).forEach(function (key) {
if (ignoreModule(key)) return
if (isRemoteFunction(key)) {
api.electron.remote[key] = 'electron.remote.' + key
} else {
addModule(electron.remote, 'electron.remote', key, api.electron.remote)
}
})
addModule(electron.remote, 'electron.remote', 'process', api.electron.remote)
}
function addBrowserWindow () {
var currentWindow = electron.remote.getCurrentWindow()
for (var name in currentWindow) {
if (ignoreApi(name)) continue
var value = currentWindow[name]
if (typeof value === 'function') {
api.browserWindow[name] = 'browserWindow.' + name
}
}
}
function addWebContents () {
var webContents = electron.remote.getCurrentWebContents()
for (var name in webContents) {
if (ignoreApi(name)) continue
var value = webContents[name]
if (typeof value === 'function') {
api.webContents[name] = 'webContents.' + name
}
}
}
function addProcess () {
if (typeof process !== 'object') return
for (var name in process) {
if (ignoreApi(name)) continue
api.rendererProcess[name] = 'process.' + name
}
}
addRenderProcessModules()
addMainProcessModules()
addBrowserWindow()
addWebContents()
addProcess()
return api
}, this.requireName).then(getResponseValue)
}
Api.prototype.addClientProperty = function (name) {
var self = this
var clientPrototype = Object.getPrototypeOf(self.app.client)
Object.defineProperty(clientPrototype, name, {
get: function () {
var client = this
return transformObject(self.api[name], {}, function (value) {
return client[value].bind(client)
})
}
})
}
Api.prototype.addClientProperties = function () {
this.addClientProperty('electron')
this.addClientProperty('browserWindow')
this.addClientProperty('webContents')
this.addClientProperty('rendererProcess')
Object.defineProperty(Object.getPrototypeOf(this.app.client), 'mainProcess', {
get: function () {
return this.electron.remote.process
}
})
}
Api.prototype.addRenderProcessApis = function (api) {
var app = this.app
var self = this
var electron = {}
app.electron = electron
Object.keys(api).forEach(function (moduleName) {
if (moduleName === 'remote') return
electron[moduleName] = {}
var moduleApi = api[moduleName]
Object.keys(moduleApi).forEach(function (key) {
var commandName = moduleApi[key]
app.client.addCommand(commandName, function () {
var args = Array.prototype.slice.call(arguments)
return this.execute(callRenderApi, moduleName, key, args, self.requireName).then(getResponseValue)
})
electron[moduleName][key] = function () {
return app.client[commandName].apply(app.client, arguments)
}
})
})
}
Api.prototype.addMainProcessApis = function (api) {
var app = this.app
var self = this
var remote = {}
app.electron.remote = remote
Object.keys(api).filter(function (propertyName) {
return typeof api[propertyName] === 'string'
}).forEach(function (name) {
var commandName = api[name]
app.client.addCommand(commandName, function () {
var args = Array.prototype.slice.call(arguments)
return this.execute(callMainApi, null, name, args, self.requireName).then(getResponseValue)
})
remote[name] = function () {
return app.client[commandName].apply(app.client, arguments)
}
})
Object.keys(api).filter(function (moduleName) {
return typeof api[moduleName] === 'object'
}).forEach(function (moduleName) {
remote[moduleName] = {}
var moduleApi = api[moduleName]
Object.keys(moduleApi).forEach(function (key) {
var commandName = moduleApi[key]
app.client.addCommand(commandName, function () {
var args = Array.prototype.slice.call(arguments)
return this.execute(callMainApi, moduleName, key, args, self.requireName).then(getResponseValue)
})
remote[moduleName][key] = function () {
return app.client[commandName].apply(app.client, arguments)
}
})
})
}
Api.prototype.addBrowserWindowApis = function (api) {
var app = this.app
var self = this
app.browserWindow = {}
const asyncApis = {
'browserWindow.capturePage': true
}
Object.keys(api).filter(function (name) {
return !asyncApis.hasOwnProperty(api[name])
}).forEach(function (name) {
var commandName = api[name]
app.client.addCommand(commandName, function () {
var args = Array.prototype.slice.call(arguments)
return this.execute(callBrowserWindowApi, name, args, self.requireName).then(getResponseValue)
})
app.browserWindow[name] = function () {
return app.client[commandName].apply(app.client, arguments)
}
})
this.addCapturePageSupport()
}
Api.prototype.addCapturePageSupport = function () {
var app = this.app
var self = this
app.client.addCommand('browserWindow.capturePage', function (rect) {
return this.executeAsync(function (rect, requireName, done) {
var args = []
if (rect != null) args.push(rect)
args.push(function (image) {
if (image != null) {
done(image.toPNG().toString('base64'))
} else {
done(image)
}
})
var browserWindow = window[requireName]('electron').remote.getCurrentWindow()
browserWindow.capturePage.apply(browserWindow, args)
}, rect, self.requireName).then(getResponseValue).then(function (image) {
return Buffer.from(image, 'base64')
})
})
app.browserWindow.capturePage = function () {
return app.client['browserWindow.capturePage'].apply(app.client, arguments)
}
}
Api.prototype.addWebContentsApis = function (api) {
var app = this.app
var self = this
app.webContents = {}
const asyncApis = {
'webContents.savePage': true,
'webContents.executeJavaScript': true
}
Object.keys(api).filter(function (name) {
return !asyncApis.hasOwnProperty(api[name])
}).forEach(function (name) {
var commandName = api[name]
app.client.addCommand(commandName, function () {
var args = Array.prototype.slice.call(arguments)
return this.execute(callWebContentsApi, name, args, self.requireName).then(getResponseValue)
})
app.webContents[name] = function () {
return app.client[commandName].apply(app.client, arguments)
}
})
this.addSavePageSupport()
this.addExecuteJavaScriptSupport()
}
Api.prototype.addSavePageSupport = function () {
var app = this.app
var self = this
app.client.addCommand('webContents.savePage', function (fullPath, saveType) {
return this.executeAsync(function (fullPath, saveType, requireName, done) {
var webContents = window[requireName]('electron').remote.getCurrentWebContents()
webContents.savePage(fullPath, saveType, done)
}, fullPath, saveType, self.requireName).then(getResponseValue).then(function (rawError) {
if (rawError) {
var error = new Error(rawError.message)
if (rawError.name) error.name = rawError.name
throw error
}
})
})
app.webContents.savePage = function () {
return app.client['webContents.savePage'].apply(app.client, arguments)
}
}
Api.prototype.addExecuteJavaScriptSupport = function () {
const app = this.app
const self = this
app.client.addCommand('webContents.executeJavaScript', function (code, useGesture) {
return this.executeAsync(function (code, useGesture, requireName, done) {
const webContents = window[requireName]('electron').remote.getCurrentWebContents()
webContents.executeJavaScript(code, useGesture, done)
}, code, useGesture, self.requireName).then(getResponseValue)
})
app.webContents.executeJavaScript = function () {
return app.client['webContents.executeJavaScript'].apply(app.client, arguments)
}
}
Api.prototype.addProcessApis = function (api) {
var app = this.app
app.rendererProcess = {}
Object.keys(api).forEach(function (name) {
var commandName = api[name]
app.client.addCommand(commandName, function () {
var args = Array.prototype.slice.call(arguments)
return this.execute(callProcessApi, name, args).then(getResponseValue)
})
app.rendererProcess[name] = function () {
return app.client[commandName].apply(app.client, arguments)
}
})
app.mainProcess = app.electron.remote.process
}
Api.prototype.transferPromiseness = function (target, promise) {
this.app.client.transferPromiseness(target, promise)
if (!this.nodeIntegration) return
var addProperties = function (source, target, moduleName) {
var sourceModule = source[moduleName]
if (!sourceModule) return
target[moduleName] = transformObject(sourceModule, {}, function (value, parent) {
return value.bind(parent)
})
}
addProperties(promise, target, 'webContents')
addProperties(promise, target, 'browserWindow')
addProperties(promise, target, 'electron')
addProperties(promise, target, 'mainProcess')
addProperties(promise, target, 'rendererProcess')
}
Api.prototype.logApi = function () {
var fs = require('fs')
var path = require('path')
var json = JSON.stringify(this.api, null, 2)
fs.writeFileSync(path.join(__dirname, 'api.json'), json)
}
function transformObject (input, output, callback) {
Object.keys(input).forEach(function (name) {
var value = input[name]
if (typeof value === 'object') {
output[name] = {}
transformObject(value, output[name], callback)
} else {
output[name] = callback(value, input)
}
})
return output
}
function callRenderApi (moduleName, api, args, requireName) {
var module = window[requireName]('electron')[moduleName]
if (typeof module[api] === 'function') {
return module[api].apply(module, args)
} else {
return module[api]
}
}
function callMainApi (moduleName, api, args, requireName) {
var module = window[requireName]('electron').remote
if (moduleName) {
module = module[moduleName]
}
if (typeof module[api] === 'function') {
return module[api].apply(module, args)
} else {
return module[api]
}
}
function callWebContentsApi (name, args, requireName) {
var webContents = window[requireName]('electron').remote.getCurrentWebContents()
return webContents[name].apply(webContents, args)
}
function callBrowserWindowApi (name, args, requireName) {
var browserWindow = window[requireName]('electron').remote.getCurrentWindow()
return browserWindow[name].apply(browserWindow, args)
}
function callProcessApi (name, args) {
if (typeof process[name] === 'function') {
return process[name].apply(process, args)
} else {
return process[name]
}
}
function getResponseValue (response) {
return response.value
}
module.exports = Api