UNPKG

spectron-test

Version:

Easily test your Electron apps using ChromeDriver and WebdriverIO.

276 lines (236 loc) 8.39 kB
var Accessibility = require('./accessibility') var Api = require('./api') var ChromeDriver = require('./chrome-driver') var DevNull = require('dev-null') var fs = require('fs') var path = require('path') var WebDriver = require('webdriverio') function Application (options) { options = options || {} this.host = options.host || '127.0.0.1' this.port = parseInt(options.port, 10) || 9515 this.quitTimeout = parseInt(options.quitTimeout, 10) || 1000 this.startTimeout = parseInt(options.startTimeout, 10) || 5000 this.waitTimeout = parseInt(options.waitTimeout, 10) || 5000 this.connectionRetryCount = parseInt(options.connectionRetryCount, 10) || 10 this.connectionRetryTimeout = parseInt(options.connectionRetryTimeout, 10) || 30000 this.nodePath = options.nodePath || process.execPath this.path = options.path this.args = options.args || [] this.chromeDriverArgs = options.chromeDriverArgs || [] this.env = options.env || {} this.workingDirectory = options.cwd || process.cwd() this.debuggerAddress = options.debuggerAddress this.chromeDriverLogPath = options.chromeDriverLogPath this.webdriverLogPath = options.webdriverLogPath this.webdriverOptions = options.webdriverOptions || {} this.requireName = options.requireName || 'require' this.api = new Api(this, this.requireName) this.setupPromiseness() } Application.prototype.setupPromiseness = function () { var self = this self.transferPromiseness = function (target, promise) { self.api.transferPromiseness(target, promise) } } Application.prototype.start = function () { var self = this return self.exists() .then(function () { return self.startChromeDriver() }) .then(function () { return self.createClient() }) .then(function () { return self.api.initialize() }) .then(function () { return self.client.timeouts('script', self.waitTimeout) }) .then(function () { self.running = true }) .then(function () { return self }) } Application.prototype.stop = function () { var self = this if (!self.isRunning()) return Promise.reject(Error('Application not running')) return new Promise(function (resolve, reject) { var endClient = function () { setTimeout(function () { self.client.end().then(function () { self.chromeDriver.stop() self.running = false resolve(self) }, reject) }, self.quitTimeout) } if (self.api.nodeIntegration) { self.client.windowByIndex(0).electron.remote.app.quit().then(endClient, reject) } else { self.client.windowByIndex(0).execute(function () { window.close() }).then(endClient, reject) } }) } Application.prototype.restart = function () { var self = this return self.stop().then(function () { return self.start() }) } Application.prototype.isRunning = function () { return this.running } Application.prototype.getSettings = function () { return { host: this.host, port: this.port, quitTimeout: this.quitTimeout, startTimeout: this.startTimeout, waitTimeout: this.waitTimeout, connectionRetryCount: this.connectionRetryCount, connectionRetryTimeout: this.connectionRetryTimeout, nodePath: this.nodePath, path: this.path, args: this.args, chromeDriverArgs: this.chromeDriverArgs, env: this.env, workingDirectory: this.workingDirectory, debuggerAddress: this.debuggerAddress, chromeDriverLogPath: this.chromeDriverLogPath, webdriverLogPath: this.webdriverLogPath, webdriverOptions: this.webdriverOptions, requireName: this.requireName } } Application.prototype.exists = function () { var self = this return new Promise(function (resolve, reject) { // Binary path is ignored by ChromeDriver if debuggerAddress is set if (self.debuggerAddress) return resolve() if (typeof self.path !== 'string') { return reject(Error('Application path must be a string')) } fs.stat(self.path, function (error, stat) { if (error) return reject(error) if (stat.isFile()) return resolve() reject(Error('Application path specified is not a file: ' + self.path)) }) }) } Application.prototype.startChromeDriver = function () { this.chromeDriver = new ChromeDriver(this.host, this.port, this.nodePath, this.startTimeout, this.workingDirectory, this.chromeDriverLogPath) return this.chromeDriver.start() } Application.prototype.createClient = function () { var self = this return new Promise(function (resolve, reject) { var args = [] args.push('spectron-path=' + self.path) self.args.forEach(function (arg, index) { args.push('spectron-arg' + index + '=' + arg) }) for (var name in self.env) { args.push('spectron-env-' + name + '=' + self.env[name]) } self.chromeDriverArgs.forEach(function (arg) { args.push(arg) }) var isWin = process.platform === 'win32' var launcherPath = path.join(__dirname, isWin ? 'launcher.bat' : 'launcher.js') if (process.env.APPVEYOR) { args.push('no-sandbox') } var options = { host: self.host, port: self.port, waitforTimeout: self.waitTimeout, connectionRetryCount: self.connectionRetryCount, connectionRetryTimeout: self.connectionRetryTimeout, desiredCapabilities: { browserName: 'electron', chromeOptions: { binary: launcherPath, args: args, debuggerAddress: self.debuggerAddress, windowTypes: ['app', 'webview'] } }, logOutput: DevNull() } if (self.webdriverLogPath) { options.logOutput = self.webdriverLogPath options.logLevel = 'verbose' } Object.assign(options, self.webdriverOptions) self.client = WebDriver.remote(options) self.addCommands() self.initializeClient(resolve, reject) }) } Application.prototype.initializeClient = function (resolve, reject) { var maxTries = 10 var tries = 0 var self = this var init = function () { tries++ self.client.init().then(resolve, function (error) { if (tries >= maxTries) { error.message = 'Client initialization failed after ' + tries + ' attempts: ' error.message += error.type + ' ' + error.message reject(error) } else { global.setTimeout(init, 250) } }) } init() } Application.prototype.addCommands = function () { this.client.addCommand('waitUntilTextExists', function (selector, text, timeout) { return this.waitUntil(function () { return this.isExisting(selector).then(function (exists) { if (!exists) return false return this.getText(selector).then(function (selectorText) { return Array.isArray(selectorText) ? selectorText.some(s => s.includes(text)) : selectorText.includes(text) }) }) }, timeout).then(function () { }, function (error) { error.message = 'waitUntilTextExists ' + error.message throw error }) }) this.client.addCommand('waitUntilWindowLoaded', function (timeout) { return this.waitUntil(function () { return this.webContents.isLoading().then(function (loading) { return !loading }) }, timeout).then(function () { }, function (error) { error.message = 'waitUntilWindowLoaded ' + error.message throw error }) }) this.client.addCommand('getWindowCount', function () { return this.windowHandles().then(getResponseValue).then(function (handles) { return handles.length }) }) this.client.addCommand('windowByIndex', function (index) { return this.windowHandles().then(getResponseValue).then(function (handles) { return this.window(handles[index]) }) }) this.client.addCommand('getSelectedText', function () { return this.execute(function () { return window.getSelection().toString() }).then(getResponseValue) }) this.client.addCommand('getRenderProcessLogs', function () { return this.log('browser').then(getResponseValue) }) var self = this this.client.addCommand('getMainProcessLogs', function () { var logs = self.chromeDriver.getLogs() self.chromeDriver.clearLogs() return logs }) Accessibility.addCommand(this.client, this.requireName) } var getResponseValue = function (response) { return response.value } module.exports = Application