spectron
Version:
Easily test your Electron apps using ChromeDriver and WebdriverIO.
308 lines (269 loc) • 8.43 kB
JavaScript
const Accessibility = require('./accessibility');
const Api = require('./api');
const ChromeDriver = require('./chrome-driver');
const DevNull = require('dev-null');
const fs = require('fs');
const path = require('path');
const 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 () {
const self = this;
self.transferPromiseness = function (target, promise) {
self.api.transferPromiseness(target, promise);
};
};
Application.prototype.start = function () {
const 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.setTimeouts(
self.waitTimeout,
self.waitTimeout,
self.waitTimeout
);
})
.then(function () {
self.running = true;
})
.then(function () {
return self;
});
};
Application.prototype.stop = function () {
const self = this;
if (!self.isRunning()) {
return Promise.reject(Error('Application not running'));
}
return new Promise(function (resolve, reject) {
const endClient = function () {
setTimeout(function () {
self.chromeDriver.stop();
self.running = false;
resolve(self);
}, self.quitTimeout);
};
if (self.api.nodeIntegration) {
self.client.electron.remote.app.quit().then(endClient, reject);
} else {
self.client
.windowByIndex(0)
.then(function () {
self.client.closeWindow();
})
.then(endClient, reject);
}
});
};
Application.prototype.restart = function () {
const 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 () {
const 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 () {
const self = this;
return new Promise(function (resolve, reject) {
const args = [];
args.push('spectron-path=' + self.path);
self.args.forEach(function (arg, index) {
args.push('spectron-arg' + index + '=' + arg);
});
for (const name in self.env) {
if (Object.prototype.hasOwnProperty.call(self.env, name)) {
args.push('spectron-env-' + name + '=' + self.env[name]);
}
}
self.chromeDriverArgs.forEach(function (arg) {
args.push(arg);
});
const isWin = process.platform === 'win32';
const launcherPath = path.join(
__dirname,
isWin ? 'launcher.bat' : 'launcher.js'
);
if (process.env.APPVEYOR) {
args.push('no-sandbox');
}
const options = {
hostname: self.host,
port: self.port,
waitforTimeout: self.waitTimeout,
connectionRetryCount: self.connectionRetryCount,
connectionRetryTimeout: self.connectionRetryTimeout,
logLevel: 'silent',
capabilities: {
'goog:chromeOptions': {
binary: launcherPath,
args: args,
debuggerAddress: self.debuggerAddress,
windowTypes: ['app', 'webview']
}
},
logOutput: DevNull()
};
if (self.webdriverLogPath) {
options.outputDir = self.webdriverLogPath;
options.logLevel = 'trace';
}
Object.assign(options, self.webdriverOptions);
self.client = WebDriver.remote(options).then(function (remote) {
self.client = remote;
self.addCommands();
resolve();
}, reject);
});
};
Application.prototype.addCommands = function () {
this.client.addCommand(
'waitUntilTextExists',
function (selector, text, timeout) {
const self = this;
return self
.waitUntil(async function () {
const elem = await self.$(selector);
const exists = await elem.isExisting();
if (!exists) {
return false;
}
const selectorText = await elem.getText();
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) {
const self = this;
return self
.waitUntil(function () {
return self.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.getWindowHandles().then(function (handles) {
return handles.length;
});
});
this.client.addCommand('windowByIndex', function (index) {
const self = this;
return self.getWindowHandles().then(function (handles) {
return self.switchToWindow(handles[index]);
});
});
this.client.addCommand('getSelectedText', function () {
return this.execute(function () {
return window.getSelection().toString();
});
});
this.client.addCommand('getRenderProcessLogs', function () {
return this.getLogs('browser');
});
const self = this;
this.client.addCommand('getMainProcessLogs', function () {
const logs = self.chromeDriver.getLogs();
self.chromeDriver.clearLogs();
return logs;
});
Accessibility.addCommand(this.client, this.requireName);
};
module.exports = Application;