jest.setTimeout(30000); // eslint-disable-line no-undef import bindAll from 'lodash.bindall'; import 'chromedriver'; // register path import webdriver from 'selenium-webdriver'; const {By, until, Button} = webdriver; const USE_HEADLESS = process.env.USE_HEADLESS !== 'no'; // The main reason for this timeout is so that we can control the timeout message and report details; // if we hit the Jasmine default timeout then we get a terse message that we can't control. // The Jasmine default timeout is 30 seconds so make sure this is lower. const DEFAULT_TIMEOUT_MILLISECONDS = 20 * 1000; class SeleniumHelper { constructor () { bindAll(this, [ 'clickText', 'clickButton', 'clickXpath', 'elementIsVisible', 'findByText', 'findByXpath', 'getDriver', 'getSauceDriver', 'getLogs', 'loadUri', 'rightClickText' ]); this.Key = webdriver.Key; // map Key constants, for sending special keys } elementIsVisible (element, timeoutMessage = 'elementIsVisible timed out') { return this.driver.wait(until.elementIsVisible(element), DEFAULT_TIMEOUT_MILLISECONDS, timeoutMessage); } get scope () { // List of useful xpath scopes for finding elements return { blocksTab: "*[@id='react-tabs-1']", costumesTab: "*[@id='react-tabs-3']", modal: '*[@class="ReactModalPortal"]', reportedValue: '*[@class="blocklyDropDownContent"]', soundsTab: "*[@id='react-tabs-5']", spriteTile: '*[starts-with(@class,"react-contextmenu-wrapper")]', monitors: '*[starts-with(@class,"stage_monitor-wrapper")]' }; } getDriver () { const chromeCapabilities = webdriver.Capabilities.chrome(); const args = []; if (USE_HEADLESS) { args.push('--headless'); } // Stub getUserMedia to always not allow access args.push('--use-fake-ui-for-media-stream=deny'); // Suppress complaints about AudioContext starting before a user gesture // This is especially important on Windows, where Selenium directs JS console messages to stdout args.push('--autoplay-policy=no-user-gesture-required'); chromeCapabilities.set('chromeOptions', {args}); chromeCapabilities.setLoggingPrefs({ performance: 'ALL' }); this.driver = new webdriver.Builder() .forBrowser('chrome') .withCapabilities(chromeCapabilities) .build(); return this.driver; } getSauceDriver (username, accessKey, configs) { this.driver = new webdriver.Builder() .withCapabilities({ browserName: configs.browserName, platform: configs.platform, version: configs.version, username: username, accessKey: accessKey }) .usingServer(`http://${username}:${accessKey }@ondemand.saucelabs.com:80/wd/hub`) .build(); return this.driver; } findByXpath (xpath, timeoutMessage = `findByXpath timed out for path: ${xpath}`) { return this.driver.wait(until.elementLocated(By.xpath(xpath)), DEFAULT_TIMEOUT_MILLISECONDS, timeoutMessage) .then(el => ( this.driver.wait(el.isDisplayed(), DEFAULT_TIMEOUT_MILLISECONDS, `${xpath} is not visible`) .then(() => el) )); } findByText (text, scope) { return this.findByXpath(`//body//${scope || '*'}//*[contains(text(), '${text}')]`); } loadUri (uri) { const WINDOW_WIDTH = 1024; const WINDOW_HEIGHT = 768; return this.driver .get(`file://${uri}`) .then(() => ( this.driver.executeScript('window.onbeforeunload = undefined;') )) .then(() => ( this.driver.manage() .window() .setSize(WINDOW_WIDTH, WINDOW_HEIGHT) )); } clickXpath (xpath) { return this.findByXpath(xpath).then(el => el.click()); } clickText (text, scope) { return this.findByText(text, scope).then(el => el.click()); } rightClickText (text, scope) { return this.findByText(text, scope).then(el => this.driver.actions() .click(el, Button.RIGHT) .perform()); } clickButton (text) { return this.clickXpath(`//button//*[contains(text(), '${text}')]`); } getLogs (whitelist) { if (!whitelist) { // Default whitelist whitelist = [ 'The play() request was interrupted by a call to pause()' ]; } return this.driver.manage() .logs() .get('browser') .then(entries => entries.filter(entry => { const message = entry.message; for (let i = 0; i < whitelist.length; i++) { if (message.indexOf(whitelist[i]) !== -1) { return false; } else if (entry.level !== 'SEVERE') { return false; } } return true; })); } } export default SeleniumHelper;