integration/js/utils/WebdriverSauceLabs.js (291 lines of code) (raw):
const {Builder, Capabilities, logging} = require('selenium-webdriver');
const safari = require('../node_modules/selenium-webdriver/safari');
const axios = require('axios');
const Base64 = require('js-base64').Base64;
const { AppPage, MeetingReadinessCheckerPage, MessagingSessionPage, TestAppPage } = require('../pages');
const SAUCE_LAB_DOMAIN = 'us-west-1.saucelabs.com';
const getPlatformName = capabilities => {
const { browserName, version, platform } = capabilities;
switch (platform) {
case 'MAC':
if (browserName === 'safari') {
return version === 'latest' ? 'macOS 12' : 'macOS 10.13';
}
return 'macOS 10.14';
case 'WINDOWS':
return 'Windows 10';
case 'LINUX':
return 'Linux Beta';
case 'IOS':
return 'iOS';
case 'ANDROID':
return 'Android';
default:
return '';
}
};
const getFirefoxCapabilities = (capabilities) => {
return {
platformName: getPlatformName(capabilities),
browserName: 'Firefox',
browserVersion: capabilities.version,
resolution: '1920x1080',
'moz:firefoxOptions': {
args: [
"-start-debugger-server",
"9222"
],
prefs: {
'media.navigator.streams.fake': true,
'media.navigator.permission.disabled': true,
'media.peerconnection.video.h264_enabled': true,
'media.webrtc.hw.h264.enabled': true,
'media.webrtc.platformencoder': true,
'devtools.chrome.enabled': true,
'devtools.debugger.prompt-connection': false,
'devtools.debugger.remote-enabled': true
},
},
'loggingPrefs': {
performance: 'ALL',
browser: 'ALL',
driver: 'ALL'
},
'sauce:options': getSauceLabsConfig(capabilities)
};
};
const getSafariCapabilities = capabilities => {
let cap = new safari.Options();
cap.setTechnologyPreview(true);
var prefs = new logging.Preferences();
prefs.setLevel(logging.Type.BROWSER, logging.Level.ALL);
prefs.setLevel(logging.Type.DRIVER, logging.Level.ALL);
prefs.setLevel(logging.Type.CLIENT, logging.Level.ALL);
prefs.setLevel(logging.Type.SERVER, logging.Level.ALL);
cap.setLoggingPrefs(prefs);
cap.set('platformName', getPlatformName(capabilities));
cap.setBrowserVersion(capabilities.version);
cap.set('sauce:options', getSauceLabsConfig(capabilities));
return cap
};
const getChromeOptions = capabilities => {
let chromeOptions = {};
if (capabilities.useFakeMedia) {
chromeOptions['args'] = ['--use-fake-device-for-media-stream', '--use-fake-ui-for-media-stream'];
if (capabilities.video) {
chromeOptions['args'].push('--use-file-for-fake-video-capture=' + fetchMediaPath(capabilities.video, capabilities.browserName));
}
if (capabilities.audio) {
chromeOptions['args'].push('--use-file-for-fake-audio-capture=' + fetchMediaPath(capabilities.audio, capabilities.browserName));
}
}
/**
* Recently, SauceLabs loads the web page in test and runs into "Your connection is not private" error.
* Content share test is also failing with WebSocket connection failed issues which SauceLabs says may
* be related.
*
* Per SauceLabs suggestion add '--ignore-certificate-errors' to all tests and
* few explicit ones for content share test as most errors are on MAC platform.
*/
chromeOptions.args.push('--ignore-certificate-errors');
if (capabilities.platform.toUpperCase() === 'MAC' && capabilities.name.includes('ContentShare')) {
const args = [
"start-maximized",
"disable-infobars",
"ignore-gpu-blacklist",
"test-type",
"disable-gpu"
];
chromeOptions.args = [...chromeOptions.args, ...args];
}
return chromeOptions;
}
const fetchMediaPath = function(media, browserName) {
let extension;
if (media.type === 'Video') {
if (browserName === 'chrome') {
extension = '.y4m';
} else {
extension = '.mp4';
}
} else {
extension = '.wav';
}
return media.directory + media.filename + extension;
}
const getPrerunScript = (capabilities) =>{
const name = capabilities.name;
const blurName = "Background Blur Test";
const repName = "Background Replacement Test";
return (name.includes(blurName) || name.includes(repName)) ? process.env.PRE_RUN_SCRIPT_URL : "";
}
const getChromeCapabilities = capabilities => {
let cap = Capabilities.chrome();
var prefs = new logging.Preferences();
prefs.setLevel(logging.Type.BROWSER, logging.Level.ALL);
prefs.setLevel(logging.Type.DRIVER, logging.Level.ALL);
prefs.setLevel(logging.Type.CLIENT, logging.Level.ALL);
prefs.setLevel(logging.Type.SERVER, logging.Level.ALL);
cap.set('platformName', getPlatformName(capabilities));
cap.setLoggingPrefs(prefs);
cap.setBrowserVersion(capabilities.version);
cap.set('goog:chromeOptions', getChromeOptions(capabilities));
cap.set('resolution', '1920x1080');
cap.set('sauce:options', getSauceLabsConfig(capabilities));
return cap;
};
const getSauceLabsConfig = (capabilities) => {
const prerunScript = getPrerunScript(capabilities);
return {
name: capabilities.name,
tags: [capabilities.name],
seleniumVersion: '3.141.59',
...(!((capabilities.name).includes('ContentShare')) && {
screenResolution: '1280x960',
}),
tunnelIdentifier: process.env.JOB_ID,
...((capabilities.platform.toUpperCase() !== 'LINUX' &&
!((capabilities.name).includes('ContentShare'))) && {
extendedDebugging: true
}),
prerun : prerunScript
}
};
const getSafariIOSConfig = (capabilities) => {
return {
platformName: capabilities.platform,
platformVersion: capabilities.version,
deviceOrientation: 'portrait',
autoAcceptAlerts: "true",
name: capabilities.name,
};
};
const getChromeAndroidConfig = capabilities => {
return {
platformName: capabilities.platform,
platformVersion: capabilities.version,
deviceOrientation: 'portrait',
chromeOptions: {
'args': ['use-fake-device-for-media-stream', 'use-fake-ui-for-media-stream'],
'w3c': false
},
name: capabilities.name
};
};
const getSauceLabsUrl = (domain) => {
return (
'https://' +
process.env.SAUCE_USERNAME +
':' +
process.env.SAUCE_ACCESS_KEY +
`@ondemand.${domain}:443/wd/hub`
);
};
class SaucelabsSession {
static async createSession(capabilities, appName) {
let cap = {};
if (capabilities.browserName === 'chrome') {
if (capabilities.platform === 'ANDROID') {
cap = getChromeAndroidConfig(capabilities);
} else {
cap = getChromeCapabilities(capabilities);
}
} else if (capabilities.browserName === 'firefox') {
cap = getFirefoxCapabilities(capabilities);
} else {
if (capabilities.platform === 'IOS') {
cap = getSafariIOSConfig(capabilities);
} else {
cap = getSafariCapabilities(capabilities);
}
}
const domain = SAUCE_LAB_DOMAIN;
const driver = await new Builder()
.usingServer(getSauceLabsUrl(domain))
.withCapabilities(cap)
.forBrowser(capabilities.browserName)
.build();
// Selenium is supposed to have a default timeout of 30 seconds, but on IOS Safari
// it is undefined. This will override the timeout in this case or any similar cases.
const defaultTimeout = 30000;
let timeouts = await driver.manage().getTimeouts()
if (timeouts.script == undefined || timeouts.script < defaultTimeout) {
await driver.manage().setTimeouts({script: defaultTimeout})
}
return new SaucelabsSession(driver, domain, appName);
}
constructor(inDriver, domain, appName) {
this.driver = inDriver;
this.domain = domain;
this.appName = appName;
}
async init() {
await this.getSessionId();
this.name = "";
this.logger = (message) => {
const prefix = this.name === "" ? "" : `[${this.name} App] `;
console.log(`${prefix}${message}`)
};
this.getAppPage();
}
async getSessionId() {
if (this.sessionId === undefined) {
// This is needed to obtain the job ID from mobile tests and only works on mobile tests
const cap = await this.driver.getCapabilities();
const url = cap.get('testobject_test_report_api_url')
if(url !== undefined)
{
this.sessionId = url.substring(url.lastIndexOf('/')+1);
}
else
{
const session = await this.driver.getSession();
this.sessionId = session.getId();
}
}
return this.sessionId
}
async getDeviceName() {
if (this.deviceName === undefined) {
const session = await this.driver.getSession();
this.deviceName = session.getCapability('testobject_device_name');
}
return this.deviceName;
}
async getMobileTestRunURL() {
if (this.mobileTestRunURL === undefined) {
const session = await this.driver.getSession();
this.mobileTestRunURL = session.getCapability('testobject_test_report_url');
}
return this.mobileTestRunURL;
}
setSessionName(inName) {
this.name = inName;
}
getAppPage() {
if (this.page === undefined) {
switch (this.appName) {
case 'meetingReadinessChecker':
this.page = new MeetingReadinessCheckerPage(this.driver, this.logger);
break;
case 'messagingSession':
this.page = new MessagingSessionPage(this.driver, this.logger);
break;
case 'testApp':
this.page = new TestAppPage(this.driver, this.logger);
break;
default:
this.page = new AppPage(this.driver, this.logger);
break;
}
}
}
async updateTestResults(passed) {
const sessionId = await this.getSessionId();
console.log(`Publishing test results to saucelabs for session: ${sessionId} status: ${passed ? 'passed' : 'failed'}`);
this.driver.executeScript(`sauce:job-result=${passed}`)
};
async printRunDetails() {
const sessionId = await this.getSessionId();
const prefix = this.name === "" ? "" : `[${this.name} App] `;
console.log(`${prefix}Saucelabs run details :`);
console.log(JSON.stringify({
run_details: {
sessionId
}
}));
}
async quit() {
await this.driver.quit();
}
}
module.exports.SaucelabsSession = SaucelabsSession;