nightwatch
Version:
Easy to use Node.js based end-to-end testing solution for web applications using the W3C WebDriver API.
334 lines (279 loc) • 10.3 kB
JavaScript
const {execSync} = require('child_process');
const Logger = require('./logger');
const untildify = require('untildify');
const path = require('path');
const semver = require('semver');
/**
* Function to require mobile-helper
*/
function requireMobileHelper() {
try {
return require('@nightwatch/mobile-helper');
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
err.message = `@nightwatch/mobile-helper needs to be installed as a project dependency. You can install @nightwatch/mobile-helper from NPM using:\n\n ${Logger.colors.light_green('npm i @nightwatch/mobile-helper --save-dev')}`;
} else if (!semver.satisfies(process.version, '>=14.0.0')) {
err.message = 'You are using Node ' + process.version + ', but @nightwatch/mobile-helper requires Node >= v14.0.0.\nPlease upgrade your Node version.';
}
err.showTrace = false;
err.displayed = false;
throw err;
}
}
/**
* check if target is Android
* @param {Object} desiredCapabilities
* @returns {Boolean}
*/
function isAndroid(desiredCapabilities = {}){
const {platformName} = desiredCapabilities;
if (platformName && platformName.toLowerCase() === 'android') {
return true;
}
const options = desiredCapabilities['goog:chromeOptions'] || desiredCapabilities['moz:firefoxOptions'];
if (options && options.androidPackage) {
return true;
}
return false;
};
/**
* check if target is iOS Device
* @param {Object} desiredCapabilities
* @returns {Boolean}
*/
function isIos(desiredCapabilities = {}) {
const {platformName} = desiredCapabilities;
if (platformName && platformName.toLowerCase() === 'ios') {
return true;
}
return false;
}
/**
* check if target is Simulator
* @param {Object} desiredCapabilities
* @returns {Boolean}
*/
function isSimulator(desiredCapabilities){
if (isIos(desiredCapabilities) && desiredCapabilities['safari:useSimulator'] === true) {
return true;
}
return false;
};
/**
* check if target is Real iOS Device
* @param {Object} desiredCapabilities
* @returns {Boolean}
*/
function isRealIos(desiredCapabilities) {
if (isIos(desiredCapabilities) && desiredCapabilities['safari:useSimulator'] !== true) {
return true;
}
return false;
};
/**
* check if the target is a mobile platform
* @param {Object} desiredCapabilities
* @returns {Boolean}
*/
function isMobile(desiredCapabilities){
if (isIos(desiredCapabilities) || isAndroid(desiredCapabilities)) {
return true;
}
return false;
};
/**
* Check if Real iOS device UDID is correct
* @param {String} udid
* @returns {String}
*/
function iosRealDeviceUDID(udid){
if (udid.length > 25) {
return udid;
}
if (udid.length < 24) {
throw new Error('Incorrect UDID provided for real iOS device');
}
return `${udid.substring(0, 8)}-${udid.substring(9, 25)}`;
};
/**
* Function to kill iOS Simulator
* @param {String} udid
*/
function killSimulator(udid) {
const cmd = `xcrun simctl shutdown ${udid}`;
try {
execSync(cmd, {
stdio: 'pipe'
});
} catch (e) {
Logger.err(e);
}
}
/**
* Function to set and return the ANDROID_HOME
* @returns {String}
*/
function getSdkRootFromEnv() {
const androidHome = process.env.ANDROID_HOME;
if (androidHome) {
return process.env.ANDROID_HOME = path.resolve(untildify(androidHome));
}
throw new AndroidHomeError(androidHome);
}
class AndroidHomeError extends Error {
constructor(androidHome) {
super();
this.message = 'ANDROID_HOME environment variable is NOT set or is NOT a valid path!';
this.name = 'AndroidHomeError';
this.help = [
`To setup Android requirements, run: ${Logger.colors.cyan('npx @nightwatch/mobile-helper android')}`,
`For Android help, run: ${Logger.colors.cyan('npx @nightwatch/mobile-helper android --help \n')}`
];
this.stack = false;
this.androidHome = androidHome;
}
}
class RealIosDeviceIdError extends Error {
constructor() {
super();
this.name = 'RealIosDeviceIdError';
this.message = 'Real Device ID is neither configured nor passed';
this.help = [
`Pass ${Logger.colors.green('deviceId')} in the command (for e.g : ${Logger.colors.cyan('--deviceId 00008030-00024C2C3453402E')})`,
`Or pass ${Logger.colors.green('safari:deviceUDID')} capability in config`,
`To verify the deviceId run, ${Logger.colors.cyan('system_profiler SPUSBDataType | sed -n \'/iPhone/,/Serial/p\' | grep \'Serial Number:\' | awk -F \': \' \'{print $2}')}`,
`For more help, run: ${Logger.colors.cyan('npx @nightwatch/mobile-helper ios')}\n`
];
this.stack = false;
}
}
function getBinaryLocation(binaryName) {
const {getBinaryLocation, getPlatformName} = requireMobileHelper();
return getBinaryLocation(getSdkRootFromEnv(), getPlatformName(), binaryName, true);
}
class AndroidBinaryError extends Error {
constructor(message, binaryName) {
super();
this.message = message;
this.stack = false;
this.binaryName = binaryName;
}
get help() {
let help;
const binaryLocation = getBinaryLocation(this.binaryName);
if (binaryLocation === '') {
help = [
`${Logger.colors.cyan(this.binaryName)} binary not found. Run command ${Logger.colors.cyan('npx @nightwatch/mobile-helper android')} to setup the missing requirements.`
];
} else if (this.binaryName === 'emulator') {
help = [
`Run ${Logger.colors.cyan('npx @nightwatch/mobile-helper android')} and re-run the test.`,
`If it still doesn't work, start the emulator by yourself by running ${Logger.colors.cyan(binaryLocation + ' @' + this.avd)} and then run the test.`
];
} else {
help = [
`Run ${Logger.colors.cyan('npx @nightwatch/mobile-helper android')} and re-run the test.`
];
}
return help;
}
}
class AndroidConnectionError extends Error {
constructor({message, detailedErr, extraDetail}) {
super();
this.message = message;
this.extraHelp = [detailedErr, extraDetail + '\n'];
this.stack = false;
}
get help() {
let binaryLocation = getBinaryLocation('adb');
let help;
if (binaryLocation === '') {
help = [
`${Logger.colors.cyan('adb')} binary not found. Run command ${Logger.colors.cyan('npx @nightwatch/mobile-helper android')} to setup the missing requirements.`
];
} else {
if (binaryLocation === 'PATH') {
binaryLocation = 'adb';
}
if (this.message.includes('Failed to run adb command')) {
help = [
`Run command: ${Logger.colors.cyan(binaryLocation + ' start-server')}`,
`If still doesn't work, run "${Logger.colors.green('npx @nightwatch/mobile-helper android')}"`
];
}
if (this.message.includes('no devices online')) {
help = [
`If testing on real-device, check if device is connected with USB debugging turned on and ${Logger.colors.cyan(binaryLocation + ' devices')} should list the connected device.`,
`If testing on emulator, check the Nightwatch configuration to make sure ${Logger.colors.cyan('real_mobile')} is set to ${Logger.colors.cyan('false')} and ${Logger.colors.cyan('avd')} to the name of AVD to launch and test on.`
];
}
}
return [...help, ...this.extraHelp];
}
}
class IosSessionNotCreatedError extends Error {
constructor({message, name}, desiredCapabilities) {
super();
this.message = message;
this.name = name;
this.stack = false;
this.desiredCapabilities = desiredCapabilities;
}
get help() {
let help;
if (this.message.includes('session timed out')) {
// 'The session timed out while connecting to a Safari instance.'
help = [
'Re-run the test command',
`If it doesn't work, try running: ${Logger.colors.cyan('npx @nightwatch/mobile-helper ios')}`
];
} else if (this.message.includes('not find any session hosts') || this.message.includes('Some devices were found')) {
// Could not find any session hosts that match the requested capabilities
// or some devices were found, but could not be used
if (isSimulator(this.desiredCapabilities)) {
help = [
`Run command to get device list: ${Logger.colors.cyan('xcrun simctl list devices')}`,
`Update the ${Logger.colors.cyan('safari:platformVersion')} and/or ${Logger.colors.cyan('safari:platforName')} in Nightwatch configuration accordingly`,
`If it doesn't work, try running: ${Logger.colors.cyan('npx @nightwatch/mobile-helper ios')}`
];
} else {
help = [
`Make sure you have passed correct ${Logger.colors.green('deviceId')} in the command (for e.g : ${Logger.colors.cyan('--deviceId 00008030-00024C2C3453402E')})`,
`Or pass ${Logger.colors.green('safari:deviceUDID')} capability in config`,
`To verify the deviceId run, ${Logger.colors.cyan('system_profiler SPUSBDataType | sed -n \'/iPhone/,/Serial/p\' | grep \'Serial Number:\' | awk -F \': \' \'{print $2}')}`,
`For more help, run: ${Logger.colors.cyan('npx @nightwatch/mobile-helper ios')}\n`
];
}
} else if (this.desiredCapabilities['safari:deviceUDID']) {
help = [
`Verify the UDID of the device set in Nightwatch configuration (look for ${Logger.colors.cyan('safari:deviceUDID')} capability) or pass the correct UDID using ${Logger.colors.cyan('--deviceId')} flag in the test command.`,
'Re-run the test command',
`If it doesn't work, try running: ${Logger.colors.cyan('npx @nightwatch/mobile-helper ios')}`
];
if (isRealIos(this.desiredCapabilities)) {
help = [
`Check device connection by running command: ${Logger.colors.cyan('system_profiler SPUSBDataType | sed -n \'/iPhone/,/Serial/p\' | grep \'Serial Number:\' | awk -F \': \' \'{print $2}')}`,
...help
];
}
}
return help;
}
}
module.exports = {
isMobile,
isIos,
isRealIos,
isSimulator,
isAndroid,
iosRealDeviceUDID,
killSimulator,
getSdkRootFromEnv,
RealIosDeviceIdError,
AndroidConnectionError,
IosSessionNotCreatedError,
AndroidBinaryError,
AndroidHomeError,
requireMobileHelper
};