blob: 838a49611cec209ee87b994777f670b7a5cc64fa [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
'use strict';
const APPIUM_SERVER_HOST = 'localhost';
const APPIUM_SERVER_PORT = 4723;
const WEBVIEW_WAIT_TIMEOUT = 5000;
const IMPLICIT_WAIT_TIMEOUT = 10000;
const ASYNC_SCRIPT_TIMEOUT = 60000;
const fs = require('fs');
const path = require('path');
const wd = global.WD || require('wd');
const { utilities } = require('../../utils');
module.exports.getDriver = function (platform) {
let normalizedPlatform;
let automationName;
let driverConfig = {};
let serverConfig = {};
let driver;
switch (platform.toLowerCase()) {
case utilities.ANDROID:
normalizedPlatform = 'Android';
automationName = 'Appium';
break;
case utilities.IOS:
normalizedPlatform = 'iOS';
automationName = 'XCUITest';
break;
default:
throw `Unknown Platform: ${platform}`;
}
global.WD.configureHttp({
timeout: utilities.WD_TIMEOUT,
retryDelay: utilities.WD_RETRY_DELAY,
retries: utilities.WD_RETRIES
});
if (global.USE_SAUCE) {
serverConfig = {
host: global.SAUCE_SERVER_HOST,
port: global.SAUCE_SERVER_PORT
};
driverConfig = global.SAUCE_CAPS;
driver = global.WD.promiseChainRemote(serverConfig.host, serverConfig.port, global.SAUCE_USER, global.SAUCE_KEY);
} else {
serverConfig = {
host: APPIUM_SERVER_HOST,
port: APPIUM_SERVER_PORT
};
driverConfig = {
browserName: '',
platformName: normalizedPlatform,
platformVersion: global.PLATFORM_VERSION || '',
deviceName: global.DEVICE_NAME || '',
app: global.PACKAGE_PATH,
autoAcceptAlerts: true,
automationName
};
if (global.UDID) {
driverConfig.udid = global.UDID;
}
driver = global.WD.promiseChainRemote(serverConfig);
}
module.exports.configureLogging(driver);
const spamDots = setInterval(function () {
process.stdout.write('.');
}, 1000);
return driver
.init(driverConfig)
.setImplicitWaitTimeout(IMPLICIT_WAIT_TIMEOUT)
.then(function () {
clearInterval(spamDots);
process.stdout.write('\n');
}, function (error) {
clearInterval(spamDots);
process.stdout.write('\n');
throw (error);
});
};
module.exports.getWD = function () {
return wd;
};
module.exports.getWebviewContext = function (driver, retries) {
if (typeof retries === 'undefined') {
retries = 2;
}
return driver
.sleep(WEBVIEW_WAIT_TIMEOUT)
.contexts()
.then(function (contexts) {
// take the last webview context
contexts.reverse();
for (let i = 0; i < contexts.length; i++) {
if (contexts[i].indexOf('WEBVIEW') >= 0) return contexts[i];
}
// no webview context, the app is still loading
return driver
.sleep(1000)
.then(function () {
if (retries > 0) {
console.log('No webview context. Retries remaining: ' + retries);
return module.exports.getWebviewContext(driver, retries - 1);
}
throw 'Couldn\'t get webview context.';
});
});
};
module.exports.waitForDeviceReady = function (driver) {
return driver
.setAsyncScriptTimeout(ASYNC_SCRIPT_TIMEOUT)
.executeAsync(function (cb) {
document.addEventListener('deviceready', function () {
cb();
}, false);
}, []);
};
module.exports.injectLibraries = function (driver) {
const q = fs.readFileSync(path.join(__dirname, 'lib', 'q.min.js'), 'utf8');
return driver
.execute(q)
.execute(function () {
navigator._appiumPromises = {};
}, []);
};
module.exports.configureLogging = function (driver) {
if (!global.VERBOSE) return;
driver.on('status', function (info) {
console.log(info);
});
driver.on('command', function (meth, path, data) {
console.log(' > ' + meth, path, data || '');
});
driver.on('http', function (meth, path, data) {
console.log(' > ' + meth, path, data || '');
});
};
module.exports.tapElementByXPath = function (xpath, driver) {
return driver
.waitForElementByXPath(xpath, 30000)
.getLocation()
.then(function (loc) {
if (loc.x <= 0) loc.x = 1;
if (loc.y <= 0) loc.y = 1;
loc.x = Math.floor(loc.x + 1);
loc.y = Math.floor(loc.y + 1);
const wd = module.exports.getWD();
const tapElement = new wd.TouchAction();
tapElement.tap(loc);
return driver.performTouchAction(tapElement);
});
};
module.exports.pollForEvents = function (driver, platform, skipBuster, windowOffset, retries) {
const isAndroid = platform === utilities.ANDROID;
const isBrowser = platform === utilities.BROWSER;
const isIOS = platform === utilities.IOS;
if (retries === undefined || retries === null) retries = 2;
if (!windowOffset) windowOffset = 0;
// polling for new events
return driver
.sleep(0)
.then(function () {
if (skipBuster) return driver;
return driver.bustAlert(platform);
})
.then(function () {
if (isIOS || isBrowser) return driver;
// for some reason inappbrowser tests tend to leave an active window on android
// so for the polling to work correctly we need to
// switch back to the window where the cache is located
return driver
.windowHandles()
.then(function (windowHandles) {
if (windowOffset >= windowHandles.length) {
throw new Error('Cannot find a window with the event cache.');
}
return driver.window(windowHandles[windowOffset]);
});
})
.execute(function () {
// wrong window
if (typeof window._jasmineParamedicProxyCache === 'undefined') return null;
// get the results and clean up the cache
const result = window._jasmineParamedicProxyCache;
window._jasmineParamedicProxyCache = [];
return JSON.stringify(result);
}, [])
.then(function (result) {
if (result) {
result = JSON.parse(result);
}
// found
if (Object.prototype.toString.call(result) === '[object Array]') return result;
// not found
if (isBrowser && retries > 0) {
// the odds are that we're hitting "bad gateway" error on Sauce, refreshing the page should fix it
return driver
.get('http://localhost:8000/cdvtests/index.html')
.then(function () {
return module.exports.pollForEvents(driver, platform, skipBuster, windowOffset, retries - 1);
});
}
if (!isAndroid) {
throw new Error('Cannot get the event cache: it doesn\'t exist in the app. Got this instead: ' + result);
}
// no luck finding the event cache in this window, let's try next
return module.exports.pollForEvents(driver, platform, skipBuster, windowOffset + 1);
});
};
module.exports.bustAlert = function (driver, platform) {
let previousContext;
return driver
.currentContext()
.then(function (context) {
if (context !== 'NATIVE_APP') {
previousContext = context;
}
return driver;
})
.context('NATIVE_APP')
.then(function () {
// iOS
if (platform === 'ios') {
return driver.acceptAlert()
.then(function alertDismissed () { }, function noAlert () { });
}
// Android
return driver
.elementByXPath('//android.widget.Button[translate(@text, "alow", "ALOW")="ALLOW"]')
.click()
.fail(function noAlert () { });
})
.then(function () {
return previousContext ? driver.context(previousContext) : driver;
});
};
module.exports.addFillerImage = function (driver) {
const bitmap = fs.readFileSync(path.join(__dirname, '..', 'cordova_logo_thumb.jpg'));
// @todo 'new Buffer()' was deprecated since v6.0.0. Use 'Buffer.alloc()' or 'Buffer.from()' instead
const base64str = new Buffer(bitmap).toString('base64'); // eslint-disable-line
return driver.executeAsync(function (b64str, cb) {
try {
window.imageSaver.saveBase64Image({
data: b64str
}, function (fpath) {
cb(fpath);
}, function (err) {
cb('ERROR: ' + err); // eslint-disable-line
});
} catch (err) {
cb('ERROR: ' + err.message); // eslint-disable-line
}
}, [base64str]);
};
module.exports.deleteFillerImage = function (driver, testImagePath) {
if (!testImagePath) return driver;
return driver.executeAsync(function (testImagePath, cb) {
if (window.imageSaver) {
window.imageSaver.removeImage({
data: testImagePath
}, function () {
cb();
}, function (err) {
cb('ERROR: ' + err); // eslint-disable-line
});
} else {
cb();
}
}, [testImagePath]);
};
wd.addPromiseChainMethod('getWebviewContext', function (retries) {
return module.exports.getWebviewContext(this, retries);
});
wd.addPromiseChainMethod('waitForDeviceReady', function () {
return module.exports.waitForDeviceReady(this);
});
wd.addPromiseChainMethod('injectLibraries', function () {
return module.exports.injectLibraries(this);
});
wd.addPromiseChainMethod('tapElementByXPath', function (xpath) {
return module.exports.tapElementByXPath(xpath, this);
});
wd.addPromiseChainMethod('pollForEvents', function (platform, skipBuster) {
return module.exports.pollForEvents(this, platform, skipBuster);
});
wd.addPromiseChainMethod('addFillerImage', function () {
return module.exports.addFillerImage(this);
});
wd.addPromiseChainMethod('deleteFillerImage', function (testImagePath) {
return module.exports.deleteFillerImage(this, testImagePath);
});
wd.addPromiseChainMethod('bustAlert', function (platform) {
return module.exports.bustAlert(this, platform);
});