Spaces:
Running
Running
File size: 6,454 Bytes
f2bee8a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
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',
'clickBlocksCategory',
'elementIsVisible',
'findByText',
'textToXpath',
'findByXpath',
'textExists',
'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")]',
contextMenu: '*[starts-with(@class,"react-contextmenu")]'
};
}
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)
));
}
textToXpath (text, scope) {
return `//body//${scope || '*'}//*[contains(text(), '${text}')]`;
}
findByText (text, scope) {
return this.findByXpath(this.textToXpath(text, scope));
}
textExists (text, scope) {
return this.driver.findElements(By.xpath(this.textToXpath(text, scope)))
.then(elements => elements.length > 0);
}
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());
}
async clickBlocksCategory (categoryText) {
// The toolbox is destroyed and recreated several times, so avoid clicking on a nonexistent element and erroring
// out. First we wait for the block pane itself to appear, then wait 100ms for the toolbox to finish refreshing,
// then finally click the toolbox text.
await this.findByXpath('//div[contains(@class, "blocks_blocks")]');
await this.driver.sleep(100);
await this.clickText(categoryText, 'div[contains(@class, "blocks_blocks")]');
await this.driver.sleep(500); // Wait for scroll to finish
}
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;
|