WebdriverFunctions.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. import os
  2. from selenium import webdriver
  3. from appium import webdriver as Appiumwebdriver
  4. from selenium.webdriver.support import expected_conditions as ec
  5. from selenium.webdriver.common.by import By
  6. from selenium.webdriver.support.ui import WebDriverWait
  7. from selenium.webdriver.chrome.options import Options as ChromeOptions
  8. from selenium.webdriver.firefox.options import Options as ffOptions
  9. from selenium.common.exceptions import *
  10. from selenium.webdriver.common import keys
  11. from baangt.base import GlobalConstants as GC
  12. from baangt.TestSteps import Exceptions
  13. from baangt.base.Utils import utils
  14. from baangt.base.BrowserHandling.BrowserHelperFunction import BrowserDriverData
  15. from baangt.base.BrowserHandling.BrowserHelperFunction import BrowserHelperFunction as helper
  16. import time
  17. import logging
  18. from pathlib import Path
  19. import json
  20. import sys
  21. logger = logging.getLogger("pyC")
  22. class WebdriverFunctions:
  23. """
  24. The webdriverclass for BrowserHandling to interact with selenium webdriver.
  25. """
  26. BROWSER_DRIVERS = {
  27. GC.BROWSER_FIREFOX: webdriver.Firefox,
  28. GC.BROWSER_CHROME: webdriver.Chrome,
  29. GC.BROWSER_SAFARI: webdriver.Safari,
  30. GC.BROWSER_EDGE: webdriver.Edge,
  31. GC.BROWSER_REMOTE: webdriver.Remote,
  32. GC.BROWSER_APPIUM : Appiumwebdriver.Remote
  33. }
  34. @staticmethod
  35. def getDownloadFolderFromProfile(profile: webdriver.FirefoxProfile):
  36. return profile.__getattribute__("default_preferences")["browser.download.dir"]
  37. @staticmethod
  38. def webdriver_setFirefoxProfile(browserProxy, randomProxy=None):
  39. profile = webdriver.FirefoxProfile()
  40. if browserProxy:
  41. profile.set_proxy(browserProxy.selenium_proxy())
  42. if randomProxy:
  43. """
  44. from selenium import webdriver
  45. return webdriver.Proxy({
  46. "httpProxy": self.proxy,
  47. "sslProxy": self.proxy,
  48. })
  49. """
  50. # We shall use a random Proxy from the list:
  51. PROXY = f"{randomProxy['ip']}:{randomProxy['port']}"
  52. logger.info(f"Using Proxy-Server: {PROXY}")
  53. ffProxy = webdriver.Proxy( {
  54. "httpProxy":PROXY,
  55. "ftpProxy":PROXY,
  56. "sslProxy":PROXY,
  57. "noProxy":None,
  58. "proxy_type":"MANUAL",
  59. "proxyType":"MANUAL",
  60. "class":"org.openqa.selenium.Proxy",
  61. "autodetect":False
  62. })
  63. profile.set_proxy(ffProxy)
  64. profile.set_preference("browser.download.folderList", 2)
  65. profile.set_preference("browser.helperApps.alwaysAsk.force", False)
  66. profile.set_preference("browser.download.manager.showWhenStarting", False)
  67. profile.set_preference("browser.download.manager.showAlertOnComplete", False)
  68. profile.set_preference('browser.helperApps.neverAsk.saveToDisk',
  69. 'application/octet-stream,application/pdf,application/x-pdf,application/vnd.pdf,application/zip,application/octet-stream,application/x-zip-compressed,multipart/x-zip,application/x-rar-compressed, application/octet-stream,application/msword,application/vnd.ms-word.document.macroEnabled.12,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/pdf,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/rtf,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,application/vnd.ms-word.document.macroEnabled.12,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/xls,application/msword,text/csv,application/vnd.ms-excel.sheet.binary.macroEnabled.12,text/plain,text/csv/xls/xlsb,application/csv,application/download,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/octet-stream')
  70. profile.set_preference('browser.helperApps.neverAsk.openFile',
  71. 'application/octet-stream,application/pdf,application/x-pdf,application/vnd.pdf,application/zip,application/octet-stream,application/x-zip-compressed,multipart/x-zip,application/x-rar-compressed, application/octet-stream,application/msword,application/vnd.ms-word.document.macroEnabled.12,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/pdf,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/rtf,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,application/vnd.ms-word.document.macroEnabled.12,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/xls,application/msword,text/csv,application/vnd.ms-excel.sheet.binary.macroEnabled.12,text/plain,text/csv/xls/xlsb,application/csv,application/download,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/octet-stream')
  72. profile.set_preference("browser.download.dir", helper.browserHelper_setBrowserDownloadDirRandom())
  73. profile.set_preference("browser.download.manager.useWindow", False)
  74. profile.set_preference("browser.download.manager.focusWhenStarting", False)
  75. profile.set_preference("browser.download.manager.showAlertOnComplete", False)
  76. profile.set_preference("browser.download.manager.closeWhenDone", True)
  77. profile.set_preference("pdfjs.enabledCache.state", False)
  78. profile.set_preference("pdfjs.disabled", True) # This is nowhere on the Internet! But that did the trick!
  79. # Set volume to 0
  80. profile.set_preference("media.volume_scale", "0.0")
  81. return profile
  82. @staticmethod
  83. def webdriver_createBrowserOptions(browserName, desiredCapabilities, browserMobProxy=None, randomProxy=None):
  84. """
  85. Translates desired capabilities from the Testrun (or globals) into specific BrowserOptions for the
  86. currently active browser
  87. @param browserName: any of the GC.BROWSER*
  88. @param desiredCapabilities: Settings from TestRun or globals
  89. @param browserMobProxy: Proxy-Server IP+Port of internal BrowserMobProxy.
  90. @param randomProxy: Proxy-Server IP+Port of random external Proxy
  91. @return: the proper BrowserOptions for the currently active browser.
  92. """
  93. # Default Download Directory for Attachment downloads
  94. if browserName == GC.BROWSER_CHROME:
  95. lOptions = ChromeOptions()
  96. prefs = {"plugins.plugins_disabled" : ["Chrome PDF Viewer"],
  97. "plugins.always_open_pdf_externally": True,
  98. "profile.default_content_settings.popups": 0,
  99. "download.default_directory": helper.browserHelper_setBrowserDownloadDirRandom(), # IMPORTANT - ENDING SLASH V IMPORTANT
  100. "directory_upgrade": True}
  101. lOptions.add_experimental_option("prefs", prefs)
  102. # Set Proxy for Chrome. First RandomProxy (External), if set. If not, then internal Browsermob
  103. if randomProxy:
  104. lOptions.add_argument(f"--proxy-server={randomProxy['ip']}:{randomProxy['port']}")
  105. elif browserMobProxy:
  106. lOptions.add_argument('--proxy-server={0}'.format(browserMobProxy.proxy))
  107. elif browserName == GC.BROWSER_FIREFOX:
  108. lOptions = ffOptions()
  109. else:
  110. lOptions = None
  111. if desiredCapabilities and lOptions:
  112. # sometimes instead of DICT comes a string with DICT-Format
  113. if isinstance(desiredCapabilities, str) and "{" in desiredCapabilities and "}" in desiredCapabilities:
  114. desiredCapabilities = json.loads(desiredCapabilities.replace("'", '"'))
  115. if isinstance(desiredCapabilities, dict):
  116. if desiredCapabilities.get(GC.BROWSER_MODE_HEADLESS):
  117. logger.debug("Starting in Headless mode")
  118. lOptions.headless = True
  119. else:
  120. # statement not matched
  121. pass
  122. else:
  123. # statement not matched
  124. pass
  125. else:
  126. # statement not matched
  127. pass
  128. return lOptions
  129. @staticmethod
  130. def getDownloadFolderFromChromeOptions(options: ChromeOptions):
  131. return options.__getattribute__("experimental_options")["prefs"]["download.default_directory"]
  132. @staticmethod
  133. def webdriver_getCurrentHTMLReference(driver):
  134. """
  135. Get a reference of the current HTML-Tag into self.html. We need that for stale check
  136. "waitForPageLoadAfterButtonClick"
  137. :return:
  138. """
  139. RETRY_COUNTER_LIMIT = 5
  140. e = None
  141. html = None
  142. retryCount = 0
  143. while retryCount < RETRY_COUNTER_LIMIT and not html:
  144. try:
  145. html = driver.find_element_by_tag_name('html') # This is for waitForPageLoadAfterButton
  146. except NoSuchElementException as e:
  147. logger.debug(f"had a NoSuchElementException: {e}")
  148. except NoSuchWindowException as e:
  149. logger.debug(f"had a noSuchWindowException: {e}")
  150. except WebDriverException as e:
  151. logger.debug(f"had a WebDriverException: {e}")
  152. except BaseException as e:
  153. logger.warning(f"had an unknown exception (should be checked): {e}")
  154. retryCount += 1
  155. time.sleep(0.5)
  156. if retryCount >= RETRY_COUNTER_LIMIT:
  157. raise Exceptions.baangtTestStepException(f"Couldn't locate HTML element in Page. "
  158. f"No idea what's going on. This was the last error"
  159. f" (check logs for more): {e}")
  160. return html
  161. @staticmethod
  162. def webdriver_tryAndRetry(browserData, timeout=20, optional=False):
  163. """
  164. In: Locator
  165. Out: Boolean whether the element was found or not.
  166. Also sets the self.element for further use by other Methods (for instance to setText or read existing value)
  167. The method is resistant to common timing problems (can't work 100% of the time but will remove at least 80%
  168. of your pain compared to directly calling Selenium Methods).
  169. """
  170. REQUEST_TIMEOUT_MINIMUM = 1.5
  171. REQUEST_POLL_FREQUENCY = 0.5
  172. element = None
  173. html = None
  174. begin = time.time()
  175. elapsed = 0
  176. if timeout < REQUEST_TIMEOUT_MINIMUM:
  177. pollFrequency = timeout / 3
  178. else:
  179. pollFrequency = REQUEST_POLL_FREQUENCY
  180. internalTimeout = timeout / 5
  181. lLoopCount = 0
  182. try:
  183. html = WebdriverFunctions.webdriver_getCurrentHTMLReference(browserData.driver)
  184. except BaseException as e:
  185. raise Exceptions.baangtTestStepException(f"__getCurrentHTMLReference was not successful: {e}")
  186. while not element and elapsed < timeout:
  187. lLoopCount += 1
  188. try:
  189. driverWait = WebDriverWait(browserData.driver, timeout=internalTimeout, poll_frequency=pollFrequency)
  190. if By.ID == browserData.locatorType or By.CSS_SELECTOR == browserData.locatorType:
  191. element = driverWait.until(ec.visibility_of_element_located((browserData.locatorType, browserData.locator)))
  192. elif By.CLASS_NAME == browserData.locatorType:
  193. element = browserData.driver.find_element_by_class_name(browserData.locator)
  194. elif By.XPATH == browserData.locatorType:
  195. # visibility of element sometimes not true, but still clickable. If we tried already
  196. # 2 times with visibility, let's give it one more try with Presence of element
  197. if lLoopCount > 1:
  198. logger.debug(f"Tried 2 times to find visible element, now trying presence "
  199. f"of element instead, XPATH = {browserData.locator}")
  200. element = driverWait.until(ec.presence_of_element_located((browserData.locatorType, browserData.locator)))
  201. else:
  202. element = driverWait.until(ec.visibility_of_element_located((browserData.locatorType, browserData.locator)))
  203. except StaleElementReferenceException as e:
  204. logger.debug("Stale Element Exception - retrying " + str(e))
  205. time.sleep(pollFrequency)
  206. except ElementClickInterceptedException as e:
  207. logger.debug("ElementClickIntercepted - retrying " + str(e))
  208. time.sleep(pollFrequency)
  209. except TimeoutException as e:
  210. logger.debug("TimoutException - retrying " + str(e))
  211. time.sleep(pollFrequency)
  212. except NoSuchElementException as e:
  213. logger.debug("Retrying Webdriver Exception: " + str(e))
  214. time.sleep(pollFrequency)
  215. except InvalidSessionIdException as e:
  216. logger.debug("WebDriver Exception - terminating testrun: " + str(e))
  217. raise Exceptions.baangtTestStepException
  218. except NoSuchWindowException as e:
  219. helper.browserHelper_log(logging.CRITICAL, "WebDriver Exception - terminating testrun: " + str(e), browserData)
  220. raise Exceptions.baangtTestStepException
  221. except ElementNotInteractableException as e:
  222. logger.debug("Most probably timeout exception - retrying: " + str(e))
  223. time.sleep(pollFrequency)
  224. except WebDriverException as e:
  225. helper.browserHelper_log(logging.ERROR, "Retrying WebDriver Exception: " + str(e), browserData)
  226. time.sleep(2)
  227. elapsed = time.time() - begin
  228. return element, html
  229. @staticmethod
  230. def webdriver_doSomething(command, element, value=None, timeout=20, optional=False, browserData=None):
  231. """
  232. Will interact in an element (that was found before by findBy-Method and stored in self.element) as defined by
  233. ``command``.
  234. Command can be "SETTEXT" (GC.CMD_SETTEXT), "CLICK" (GC.CMD_CLICK), "FORCETEXT" (GC.CMD_FORCETEXT).
  235. Similarly to __try_and_retry the method is pretty robust when it comes to error handling of timing issues.
  236. """
  237. NUMBER_OF_SEND_KEY_BACKSPACE = 10
  238. COUNTER_LIMIT_RETRY = 2
  239. COUNTER_LIMIT_ELEMENT_REF = 4
  240. COUNTER_LIMIT_ELEMENT_NOT_INTERACT = 5
  241. didWork = False
  242. elapsed = 0
  243. counter = 0
  244. begin = time.time()
  245. while not didWork and elapsed < timeout:
  246. counter += 1
  247. logger.debug(f"__doSomething {command} with {value}")
  248. try:
  249. if command.upper() == GC.CMD_SETTEXT:
  250. if not value:
  251. value = ""
  252. element.send_keys(value)
  253. elif command.upper() == GC.CMD_CLICK:
  254. element.click()
  255. elif command.upper() == GC.CMD_FORCETEXT:
  256. element.clear()
  257. element.click()
  258. # element.send_keys(keys.Keys.CONTROL+"A")
  259. # for i in range(0, NUMBER_OF_SEND_KEY_BACKSPACE):
  260. # element.send_keys(keys.Keys.BACKSPACE)
  261. element.send_keys(value)
  262. element.send_keys(keys.Keys.TAB)
  263. didWork = True
  264. except ElementClickInterceptedException as e:
  265. logger.debug("doSomething: Element intercepted - retry")
  266. time.sleep(0.2)
  267. except StaleElementReferenceException as e:
  268. logger.debug(f"doSomething: Element stale - retry {browserData.locatorType} {browserData.locator}")
  269. # If the element is stale after 2 times, try to re-locate the element
  270. if counter < COUNTER_LIMIT_RETRY:
  271. time.sleep(0.2)
  272. elif counter < COUNTER_LIMIT_ELEMENT_REF:
  273. begin, element = WebdriverFunctions.webdriver_refindElementAfterError(browserData, timeout,
  274. optional=optional)
  275. else:
  276. raise Exceptions.baangtTestStepException(e)
  277. except NoSuchElementException as e:
  278. logger.debug("doSomething: Element not there yet - retry")
  279. time.sleep(0.5)
  280. except InvalidSessionIdException as e:
  281. helper.browserHelper_log(logging.ERROR, f"Invalid Session ID Exception caught - aborting... {e} ", browserData)
  282. raise Exceptions.baangtTestStepException(e)
  283. except ElementNotInteractableException as e:
  284. if counter < COUNTER_LIMIT_RETRY:
  285. logger.debug(f"Element not interactable {browserData.locatorType} {browserData.locator}, retrying")
  286. time.sleep(0.2)
  287. elif counter < COUNTER_LIMIT_ELEMENT_NOT_INTERACT:
  288. logger.debug(f"Element not interactable {browserData.locatorType} {browserData.locator}, re-finding element")
  289. begin, element = WebdriverFunctions.webdriver_refindElementAfterError(browserData, timeout,
  290. optional=optional)
  291. else:
  292. helper.browserHelper_log(logging.ERROR, f"Element not interactable {e}", browserData)
  293. raise Exceptions.baangtTestStepException(e)
  294. except NoSuchWindowException as e:
  295. raise Exceptions.baangtTestStepException(e)
  296. elapsed = time.time() - begin
  297. if not didWork:
  298. if optional:
  299. logger.debug(
  300. f"Action not possible after {timeout} s, Locator: {browserData.locatorType}: {browserData.locator}, but flag 'optional' is set")
  301. else:
  302. raise Exceptions.baangtTestStepException(f"Action not possible after {timeout} s, Locator: {browserData.locatorType}: {browserData.locator}")
  303. else:
  304. # Function successful
  305. pass
  306. return didWork
  307. @staticmethod
  308. def webdriver_refindElementAfterError(browserData, timeout, optional=None):
  309. if not optional:
  310. optional = False
  311. element, _ = WebdriverFunctions.webdriver_tryAndRetry(browserData, timeout=timeout / 2, optional=True)
  312. if element:
  313. logger.debug(f"Re-Found element {browserData.locatorType}: {browserData.locator}, will retry ")
  314. begin = time.time()
  315. else:
  316. if not optional:
  317. raise Exceptions.baangtTestStepException(
  318. f"Element {browserData.locatorType} {browserData.locator} couldn't be found. "
  319. f"Tried to re-find it, but not element was not found")
  320. else:
  321. return None, None
  322. return begin, element