selenium-helper.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. jest.setTimeout(30000); // eslint-disable-line no-undef
  2. import bindAll from 'lodash.bindall';
  3. import 'chromedriver'; // register path
  4. import webdriver from 'selenium-webdriver';
  5. const {By, until, Button} = webdriver;
  6. const USE_HEADLESS = process.env.USE_HEADLESS !== 'no';
  7. // The main reason for this timeout is so that we can control the timeout message and report details;
  8. // if we hit the Jasmine default timeout then we get a terse message that we can't control.
  9. // The Jasmine default timeout is 30 seconds so make sure this is lower.
  10. const DEFAULT_TIMEOUT_MILLISECONDS = 20 * 1000;
  11. class SeleniumHelper {
  12. constructor () {
  13. bindAll(this, [
  14. 'clickText',
  15. 'clickButton',
  16. 'clickXpath',
  17. 'elementIsVisible',
  18. 'findByText',
  19. 'findByXpath',
  20. 'getDriver',
  21. 'getSauceDriver',
  22. 'getLogs',
  23. 'loadUri',
  24. 'rightClickText'
  25. ]);
  26. this.Key = webdriver.Key; // map Key constants, for sending special keys
  27. }
  28. elementIsVisible (element, timeoutMessage = 'elementIsVisible timed out') {
  29. return this.driver.wait(until.elementIsVisible(element), DEFAULT_TIMEOUT_MILLISECONDS, timeoutMessage);
  30. }
  31. get scope () {
  32. // List of useful xpath scopes for finding elements
  33. return {
  34. blocksTab: "*[@id='react-tabs-1']",
  35. costumesTab: "*[@id='react-tabs-3']",
  36. modal: '*[@class="ReactModalPortal"]',
  37. reportedValue: '*[@class="blocklyDropDownContent"]',
  38. soundsTab: "*[@id='react-tabs-5']",
  39. spriteTile: '*[starts-with(@class,"react-contextmenu-wrapper")]',
  40. monitors: '*[starts-with(@class,"stage_monitor-wrapper")]'
  41. };
  42. }
  43. getDriver () {
  44. const chromeCapabilities = webdriver.Capabilities.chrome();
  45. const args = [];
  46. if (USE_HEADLESS) {
  47. args.push('--headless');
  48. }
  49. // Stub getUserMedia to always not allow access
  50. args.push('--use-fake-ui-for-media-stream=deny');
  51. // Suppress complaints about AudioContext starting before a user gesture
  52. // This is especially important on Windows, where Selenium directs JS console messages to stdout
  53. args.push('--autoplay-policy=no-user-gesture-required');
  54. chromeCapabilities.set('chromeOptions', {args});
  55. chromeCapabilities.setLoggingPrefs({
  56. performance: 'ALL'
  57. });
  58. this.driver = new webdriver.Builder()
  59. .forBrowser('chrome')
  60. .withCapabilities(chromeCapabilities)
  61. .build();
  62. return this.driver;
  63. }
  64. getSauceDriver (username, accessKey, configs) {
  65. this.driver = new webdriver.Builder()
  66. .withCapabilities({
  67. browserName: configs.browserName,
  68. platform: configs.platform,
  69. version: configs.version,
  70. username: username,
  71. accessKey: accessKey
  72. })
  73. .usingServer(`http://${username}:${accessKey
  74. }@ondemand.saucelabs.com:80/wd/hub`)
  75. .build();
  76. return this.driver;
  77. }
  78. findByXpath (xpath, timeoutMessage = `findByXpath timed out for path: ${xpath}`) {
  79. return this.driver.wait(until.elementLocated(By.xpath(xpath)), DEFAULT_TIMEOUT_MILLISECONDS, timeoutMessage)
  80. .then(el => (
  81. this.driver.wait(el.isDisplayed(), DEFAULT_TIMEOUT_MILLISECONDS, `${xpath} is not visible`)
  82. .then(() => el)
  83. ));
  84. }
  85. findByText (text, scope) {
  86. return this.findByXpath(`//body//${scope || '*'}//*[contains(text(), '${text}')]`);
  87. }
  88. loadUri (uri) {
  89. const WINDOW_WIDTH = 1024;
  90. const WINDOW_HEIGHT = 768;
  91. return this.driver
  92. .get(`file://${uri}`)
  93. .then(() => (
  94. this.driver.executeScript('window.onbeforeunload = undefined;')
  95. ))
  96. .then(() => (
  97. this.driver.manage()
  98. .window()
  99. .setSize(WINDOW_WIDTH, WINDOW_HEIGHT)
  100. ));
  101. }
  102. clickXpath (xpath) {
  103. return this.findByXpath(xpath).then(el => el.click());
  104. }
  105. clickText (text, scope) {
  106. return this.findByText(text, scope).then(el => el.click());
  107. }
  108. rightClickText (text, scope) {
  109. return this.findByText(text, scope).then(el => this.driver.actions()
  110. .click(el, Button.RIGHT)
  111. .perform());
  112. }
  113. clickButton (text) {
  114. return this.clickXpath(`//button//*[contains(text(), '${text}')]`);
  115. }
  116. getLogs (whitelist) {
  117. if (!whitelist) {
  118. // Default whitelist
  119. whitelist = [
  120. 'The play() request was interrupted by a call to pause()'
  121. ];
  122. }
  123. return this.driver.manage()
  124. .logs()
  125. .get('browser')
  126. .then(entries => entries.filter(entry => {
  127. const message = entry.message;
  128. for (let i = 0; i < whitelist.length; i++) {
  129. if (message.indexOf(whitelist[i]) !== -1) {
  130. return false;
  131. } else if (entry.level !== 'SEVERE') {
  132. return false;
  133. }
  134. }
  135. return true;
  136. }));
  137. }
  138. }
  139. export default SeleniumHelper;