BrowserHandling.py 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925
  1. import os
  2. from selenium.webdriver.common.by import By
  3. from selenium.common.exceptions import *
  4. from selenium.webdriver.common import keys
  5. from baangt.base import GlobalConstants as GC
  6. from baangt.base.Timing.Timing import Timing
  7. from baangt.TestSteps import Exceptions
  8. from baangt.base.DownloadFolderMonitoring import DownloadFolderMonitoring
  9. from baangt.base.BrowserHandling.WebdriverFunctions import WebdriverFunctions as webDrv
  10. from baangt.base.BrowserHandling.BrowserHelperFunction import BrowserDriverData
  11. from baangt.base.BrowserHandling.BrowserHelperFunction import BrowserHelperFunction as helper
  12. from baangt.base.Utils import utils
  13. from baangt.base.ProxyRotate import ProxyRotate
  14. from http import HTTPStatus
  15. import uuid
  16. import time
  17. import logging
  18. from pathlib import Path
  19. #import json
  20. import sys
  21. import platform
  22. #import ctypes
  23. import requests
  24. from baangt.base.PathManagement import ManagedPaths
  25. from baangt.base.RuntimeStatistics import Statistic
  26. import psutil
  27. import signal
  28. logger = logging.getLogger("pyC")
  29. class BrowserDriver:
  30. """
  31. The main class for baangt-Elements to interact with a browser.
  32. Main Methods:
  33. - createNewBrowser: Create one instance of a Browser
  34. - findBy*-Methods: e.g. findByAndClick
  35. - URL-Methods: To navigate to an URL
  36. - handleIframe and handleWindow: To navigate between Windows (=tabs) and Iframes
  37. - javaScript: to pass JS directly to the browser
  38. - takeScreenshot: yes, that.
  39. """
  40. def __init__(self, timing=None, screenshotPath=None, statistics=None):
  41. self.iFrame = None
  42. self.element = None
  43. self.browserData = BrowserDriverData(locatorType=None, locator=None,
  44. driver=webDrv.BROWSER_DRIVERS[GC.BROWSER_FIREFOX])
  45. self.slowExecution = False
  46. self.slowExecutionTimeoutInSeconds = 1
  47. self.downloadFolder = None
  48. self.downloadFolderMonitoring = None
  49. self.randomProxy = None
  50. self.zoomFactorDesired = None # Desired zoom factor for this page
  51. self.browserName = None
  52. # Reference to Selenium "HTML" in order to track page changes. It is set on every interaction with the page
  53. self.html = None
  54. self.managedPaths = ManagedPaths()
  55. self.statistics = Statistic()
  56. if timing:
  57. self.timing = timing
  58. self.takeTime = timing.takeTime
  59. else:
  60. self.timing = Timing()
  61. self.takeTime = self.timing.takeTime
  62. if not screenshotPath or screenshotPath == "":
  63. self.screenshotPath = self.managedPaths.getOrSetScreenshotsPath()
  64. else:
  65. self.screenshotPath = screenshotPath
  66. def sleep(self, seconds):
  67. time.sleep(seconds)
  68. def createNewBrowser(self, mobileType=None, mobileApp = None, desired_app = None, mobile_app_setting = None,
  69. browserName=GC.BROWSER_FIREFOX,
  70. desiredCapabilities={}, randomProxy=None, **kwargs):
  71. """
  72. Will find the specified executables of the desired browser and start it with the given capabilities.
  73. @param browserName: one of GC_BROWSER_*-Browsernames, e.g. GC_BROWSER_FIREFOX
  74. @param desiredCapabilities: DICT of desiredCapabilities for this browser
  75. @param kwargs: Currently (Jan2020) not used
  76. """
  77. self.takeTime("Browser Start")
  78. self.randomProxy = randomProxy
  79. self.browserName = browserName
  80. self.browserProcessID = []
  81. lCurPath = Path(self.managedPaths.getOrSetDriverPath())
  82. if browserName in webDrv.BROWSER_DRIVERS:
  83. browserProxy = kwargs.get('browserProxy')
  84. browserInstance = kwargs.get('browserInstance', 'unknown')
  85. if utils.anything2Boolean(mobileType):
  86. self.browserData.driver = self._mobileConnectAppium(browserName, desired_app, mobileApp, mobile_app_setting)
  87. elif GC.BROWSER_FIREFOX == browserName:
  88. self.browserData.driver = self._browserFirefoxRun(browserName, lCurPath, browserProxy, randomProxy, desiredCapabilities)
  89. helper.browserHelper_startBrowsermobProxy(browserName=browserName, browserInstance=browserInstance, browserProxy=browserProxy)
  90. self.browserProcessID.append(self.browserData.driver.capabilities.get("moz:processID"))
  91. elif GC.BROWSER_CHROME == browserName:
  92. self.browserData.driver = self._browserChromeRun(browserName, lCurPath, browserProxy, randomProxy, desiredCapabilities)
  93. helper.browserHelper_startBrowsermobProxy(browserName=browserName, browserInstance=browserInstance, browserProxy=browserProxy)
  94. try:
  95. port = self.browserData.driver.capabilities['goog:chromeOptions']["debuggerAddress"].split(":")[1]
  96. fp = os.popen(f"lsof -nP -iTCP:{port} | grep LISTEN")
  97. self.browserProcessID.append(int(fp.readlines()[-1].split()[1]))
  98. except Exception as ex:
  99. logger.info(ex)
  100. elif GC.BROWSER_EDGE == browserName:
  101. self.browserData.driver = webDrv.BROWSER_DRIVERS[browserName](executable_path = helper.browserHelper_findBrowserDriverPaths(GC.EDGE_DRIVER))
  102. elif GC.BROWSER_SAFARI == browserName:
  103. # SAFARI doesn't provide any options, but desired_capabilities.
  104. # Executable_path = the standard safaridriver path.
  105. if len(desiredCapabilities) == 0:
  106. desiredCapabilities = {}
  107. self.browserData.driver = webDrv.BROWSER_DRIVERS[browserName](desired_capabilities=desiredCapabilities)
  108. elif GC.BROWSER_REMOTE == browserName:
  109. self.browserData.driver = webDrv.BROWSER_DRIVERS[browserName](options = webDrv.webdriver_createBrowserOptions(browserName=browserName,
  110. desiredCapabilities=desiredCapabilities),
  111. command_executor=GC.REMOTE_EXECUTE_URL,
  112. desired_capabilities=desiredCapabilities)
  113. else:
  114. logger.critical(f"Browsername not found: {browserName}. Cancelling test run")
  115. raise SystemError(f"Browsername not found: {browserName}. Cancelling test run")
  116. elif GC.BROWSER_REMOTE_V4 == browserName:
  117. desired_capabilities, seleniumGridIp, seleniumGridPort = helper.browserHelper_setSettingsRemoteV4(desiredCapabilities)
  118. if desired_capabilities['browserName'] == 'firefox':
  119. browserExecutable = helper.browserHelper_getBrowserExecutable(GC.BROWSER_FIREFOX)
  120. self._downloadDriverCheck(browserExecutable, lCurPath, GC.BROWSER_FIREFOX)
  121. elif desired_capabilities['browserName'] == 'chrome':
  122. browserExecutable = helper.browserHelper_getBrowserExecutable(GC.BROWSER_CHROME)
  123. self._downloadDriverCheck(browserExecutable, lCurPath, GC.BROWSER_CHROME)
  124. serverUrl = 'http://' + seleniumGridIp + ':' + seleniumGridPort
  125. self.browserData.driver = webDrv.BROWSER_DRIVERS[GC.BROWSER_REMOTE](command_executor=serverUrl, desired_capabilities=desiredCapabilities)
  126. else:
  127. raise SystemExit("Browsername unknown")
  128. if self.downloadFolder:
  129. self.downloadFolderMonitoring = DownloadFolderMonitoring(self.downloadFolder)
  130. self.takeTime("Browser Start")
  131. def _downloadDriverCheck(self, executable, lCurPath, browserName):
  132. lCurPath = lCurPath.joinpath(executable)
  133. if not (os.path.isfile(str(lCurPath))):
  134. self.downloadDriver(browserName)
  135. def _browserChromeRun(self, browserName, lCurPath, browserProxy, randomProxy, desiredCapabilities):
  136. executable = helper.browserHelper_getBrowserExecutable(browserName)
  137. self._downloadDriverCheck(executable, lCurPath, browserName)
  138. lOptions = webDrv.webdriver_createBrowserOptions(browserName=browserName,
  139. desiredCapabilities=desiredCapabilities,
  140. browserMobProxy=browserProxy,
  141. randomProxy=randomProxy)
  142. self.downloadFolder=webDrv.getDownloadFolderFromChromeOptions(options=lOptions)
  143. return webDrv.BROWSER_DRIVERS[browserName](
  144. chrome_options = lOptions,
  145. executable_path = helper.browserHelper_findBrowserDriverPaths(executable),
  146. service_log_path = os.path.join(self.managedPaths.getLogfilePath(), 'chromedriver.log')
  147. )
  148. def _browserFirefoxRun(self, browserName, lCurPath, browserProxy, randomProxy, desiredCapabilities):
  149. executable = helper.browserHelper_getBrowserExecutable(browserName)
  150. self._downloadDriverCheck(executable, lCurPath, browserName)
  151. profile = webDrv.webdriver_setFirefoxProfile(browserProxy, randomProxy)
  152. self.downloadFolder = webDrv.getDownloadFolderFromProfile(profile)
  153. logger.debug(f"Firefox Profile as follows:{profile.userPrefs}")
  154. return webDrv.BROWSER_DRIVERS[browserName](
  155. options = webDrv.webdriver_createBrowserOptions(browserName=browserName, desiredCapabilities=desiredCapabilities),
  156. executable_path = helper.browserHelper_findBrowserDriverPaths(executable),
  157. firefox_profile = profile,
  158. service_log_path = os.path.join(self.managedPaths.getLogfilePath(), 'geckodriver.log')
  159. )
  160. @staticmethod
  161. def _mobileConnectAppium(browserName, desired_app, mobileApp, mobile_app_setting):
  162. validSettings = False
  163. desired_cap = desired_app
  164. if desired_app[GC.MOBILE_PLATFORM_NAME] == "Android":
  165. validSettings = True
  166. if utils.anything2Boolean(mobileApp):
  167. desired_cap['app'] = mobile_app_setting[GC.MOBILE_APP_URL]
  168. desired_cap['appPackage'] = mobile_app_setting[GC.MOBILE_APP_PACKAGE]
  169. desired_cap['appActivity'] = mobile_app_setting[GC.MOBILE_APP_ACTIVITY]
  170. else:
  171. desired_cap['browserName'] = browserName
  172. desired_cap['chromedriverExecutable'] = mobile_app_setting[GC.MOBILE_APP_BROWSER_PATH]
  173. desired_cap['noReset'] = False
  174. elif desired_app[GC.MOBILE_PLATFORM_NAME] == "iOS":
  175. validSettings = True
  176. if utils.anything2Boolean(mobileApp):
  177. desired_cap['automationName'] = 'XCUITest'
  178. desired_cap['app'] = mobile_app_setting[GC.MOBILE_APP_URL]
  179. else:
  180. desired_cap['browserName'] = 'safari'
  181. if validSettings:
  182. return webDrv.BROWSER_DRIVERS[GC.BROWSER_APPIUM](GC.REMOTE_EXECUTE_URL, desired_cap)
  183. else:
  184. return None
  185. def closeBrowser(self):
  186. self.statistics.update_teststep()
  187. try:
  188. if self.browserData.driver:
  189. try:
  190. if len(self.browserProcessID) > 0:
  191. for bpid in self.browserProcessID:
  192. os.kill(bpid, signal.SIGINT)
  193. except:
  194. pass
  195. self.browserData.driver.close()
  196. self.browserData.driver.quit()
  197. except Exception as ex:
  198. logger.info(ex)
  199. pass # If the driver is already dead, it's fine.
  200. self.browserData.driver = None
  201. def refresh(self):
  202. self.browserData.driver.execute_script("window.location.reload()")
  203. def takeScreenshot(self, screenShotPath=None):
  204. driver = self.browserData.driver
  205. # Filename must have ".png" inside
  206. lFile = str(uuid.uuid4()) + ".png"
  207. if screenShotPath:
  208. lFile = Path(screenShotPath).joinpath(lFile)
  209. else:
  210. lFile = Path(self.screenshotPath).joinpath(lFile)
  211. try:
  212. lFile = str(lFile)
  213. driver.save_screenshot(lFile)
  214. helper.browserHelper_log(logging.DEBUG, f"Stored Screenshot: {lFile}", self.browserData)
  215. except Exception as e:
  216. helper.browserHelper_log(logging.INFO, f"Screenshot not possible. Error: {e}", self.browserData)
  217. lFile = None
  218. return lFile
  219. def handleIframe(self, iframe=None):
  220. """
  221. Give an IFRAME and it will try to go into.
  222. If you're inside an iframe it will go out of the iframe
  223. """
  224. self.statistics.update_teststep()
  225. if iframe:
  226. self.browserData.locatorType="XPATH"
  227. self.browserData.locator=iframe
  228. helper.browserHelper_log(logging.DEBUG, "Going into Iframe: ", self.browserData, **{"iframe": iframe})
  229. # frame_to_be_availble_and_switch_to_it doesn't work.
  230. mustEnd = time.time() + 30
  231. while time.time() < mustEnd:
  232. try:
  233. self.browserData.driver.switch_to.default_content()
  234. self.iFrame = self.browserData.driver.switch_to.frame(iframe)
  235. break
  236. except WebDriverException as e:
  237. helper.browserHelper_log(logging.DEBUG, f"IFrame {iframe} not there yet - waiting 1 second", self.browserData)
  238. time.sleep(1)
  239. if time.time() > mustEnd:
  240. raise TimeoutError
  241. elif self.iFrame:
  242. helper.browserHelper_log(logging.DEBUG, f"Leaving Iframe: {self.iFrame}", self.browserData)
  243. self.browserData.driver.switch_to.default_content()
  244. self.iFrame = None
  245. else:
  246. # TODO add exception, this code should never be reached
  247. pass
  248. def handleWindow(self, windowNumber=None, function=None, timeout=20):
  249. """
  250. Interations with Windows (=BrowserTabs).
  251. @param windowNumber: Number of the windowHandle inside this browser session (0 = startwindow(=Tab), 1=Next window
  252. @param function: "CLOSE", "CLOSEALL"
  253. """
  254. self.statistics.update_teststep()
  255. if function:
  256. if "close" == function.lower():
  257. self.browserData.driver.close()
  258. self.browserData.driver.switch_to.window(self.browserData.driver.window_handles[0])
  259. elif "closeall" in function.lower():
  260. exceptHandles = function.lower().replace("closeall", "")
  261. exceptHandles = exceptHandles.replace("-", "")
  262. # WindowHandles based on 0.. Value "let 2 windows open" means to close everything except 0 and 1:
  263. exceptHandles = int(exceptHandles.strip()) - 1
  264. try:
  265. len(self.browserData.driver.window_handles)
  266. except BaseException as e:
  267. logger.error(f"Tried to get amount of windows. Threw error {e}. Most probably browser crashed")
  268. raise Exceptions.baangtTestStepException(f"Tried to get amount of windows. "
  269. f"Threw error {e}. Most probably browser crashed")
  270. for windowHandle in self.browserData.driver.window_handles[-1:exceptHandles:-1]:
  271. try:
  272. self.browserData.driver.switch_to.window(windowHandle)
  273. self.browserData.driver.close()
  274. except NoSuchWindowException as e:
  275. # If the window is already closed, it's fine. Don't do anything
  276. pass
  277. try:
  278. self.browserData.driver.switch_to.window(self.browserData.driver.window_handles[exceptHandles])
  279. except IndexError as e:
  280. raise Exceptions.baangtTestStepException(f"Seems like the browser crashed. Main-Window lost")
  281. else:
  282. # TODO Wrong function, add exception
  283. pass
  284. else:
  285. success = False
  286. duration = 0
  287. while not success and duration < timeout:
  288. try:
  289. self.browserData.driver.switch_to.window(self.browserData.driver.window_handles[windowNumber])
  290. success = True
  291. continue
  292. except Exception as e:
  293. logger.debug(f"Tried to switch to Window {windowNumber} but it's not there yet")
  294. time.sleep(1)
  295. duration += 1
  296. if not success:
  297. raise Exceptions.baangtTestStepException(f"Window {windowNumber} doesn't exist after timeout {timeout}")
  298. def findByAndWaitForValue(self, id=None, css=None, xpath=None, class_name=None, iframe=None, timeout=20, optional=False):
  299. """
  300. @param id: ID of the element
  301. @param css: CSS-Locator
  302. @param xpath: XPATH-Locator
  303. @param class_name: Class-Name
  304. @param iframe: Iframe to use (use only if changed. If you set an iframe before, you don't need to set it again!)
  305. @param timeout: Timeout in Seconds before raising an error or returning back (depending on "optional")
  306. @param optional: If set to "True" and the operation can not be executed, just a log entry is written but no error raised
  307. @return: the text of the element, if element was found
  308. """
  309. self.statistics.update_teststep()
  310. self.element = None
  311. returnValue = None
  312. start = time.time()
  313. duration = 0
  314. retry = True
  315. while retry and duration < timeout:
  316. self.element, self.html = self.findBy(id=id, css=css, xpath=xpath, class_name=class_name, iframe=iframe, timeout=timeout / 3,
  317. optional=optional)
  318. time.sleep(0.5)
  319. duration = time.time() - start
  320. if self.element:
  321. try:
  322. if len(self.element.text) > 0:
  323. returnValue = self.element.text.strip()
  324. elif self.element.tag_name == 'input':
  325. # element is of type <input />
  326. returnValue = self.element.get_property('value').strip()
  327. except Exception as e:
  328. logger.debug(f"Exception during findByAndWaitForValue, but continuing {str(e)}, "
  329. f"Locator: {self.browserData.locatorType} = {self.browserData.locator}")
  330. else:
  331. logger.info(f"Couldn't find value for element {self.browserData.locatorType}:{self.browserData.locator}")
  332. if returnValue and len(returnValue.strip()) > 0:
  333. return returnValue
  334. return returnValue
  335. def findByAndSetText(self, id=None, css=None, xpath=None, class_name=None, value=None, iframe=None,
  336. timeout=60, optional=False):
  337. """
  338. Please see documentation in findBy and __doSomething
  339. """
  340. self.element, self.html = self.findBy(id=id, css=css, xpath=xpath, class_name=class_name, iframe=iframe, timeout=timeout, optional=optional)
  341. if not self.element:
  342. return False
  343. return webDrv.webdriver_doSomething(GC.CMD_SETTEXT, self.element, value=value, timeout=timeout, optional=optional, browserData = self.browserData)
  344. def slowExecutionToggle(self, newSlowExecutionWaitTimeInSeconds=None):
  345. """
  346. SlowExecution can be set in globals or by the teststep. It's intended use is debugging or showcasing a testcases
  347. functionality.
  348. @param newSlowExecutionWaitTimeInSeconds: Optional. If set, it will change the default value of WaitTime, when SlowExecution is active
  349. @return: Returns the state of sloeExecution toggle after toggling was done.
  350. """
  351. if newSlowExecutionWaitTimeInSeconds:
  352. self.slowExecutionTimeoutInSeconds = newSlowExecutionWaitTimeInSeconds
  353. return not self.slowExecution
  354. def findByAndSetTextIf(self, id=None, css=None, xpath=None, class_name=None, value=None, iframe=None,
  355. timeout=60, optional=False):
  356. """
  357. Helper function to not have to write:
  358. If <condition>:
  359. findByAndSetText(locator)
  360. instead use:
  361. findByAndSetTextIf(locator, value).
  362. If value is evaluated into "True" the Text is set.
  363. """
  364. if self._isValidKeyValue(value):
  365. return self.findByAndSetText(id=id, css=css, xpath=xpath, class_name=class_name, value=value, iframe=iframe, timeout=timeout, optional=optional)
  366. else:
  367. return False
  368. def findByAndSetTextValidated(self, id=None, css=None, xpath=None, class_name=None, value=None, iframe=None, timeout=60, retries=5):
  369. """
  370. This is a method not recommended to be used regularly. Sometimes (especially with Angular Frontends) it gets
  371. pretty hard to set a value into a field. Chrome, but also FF will show the value, but the DOM will not have it.
  372. Ths Method should be your last ressort. Here we try <retries> time to set a value. Then we read the element again
  373. and compare value to what we'd expect. If value is different and we're less than <retries>-Times, we'll try again.
  374. """
  375. tries = 0
  376. self.element, self.html = self.findBy(id=id, css=css, xpath=xpath, class_name=class_name, iframe=iframe, timeout=timeout)
  377. while self.element.text != value and self.element.get_property("value") != value and tries < retries:
  378. helper.browserHelper_log(logging.DEBUG, f"Verified trying of SetText - iteration {tries} of {retries}", self.browserData)
  379. self.findByAndForceText(id=id, css=css, xpath=xpath, class_name=class_name, iframe=iframe,
  380. value=value, timeout=timeout)
  381. self.element, self.html = self.findBy(id=id, css=css, xpath=xpath, class_name=class_name, iframe=iframe, timeout=timeout)
  382. tries += 1
  383. def submit(self):
  384. """
  385. Used for forms to call the standard submit-function (similar to pressing "Enter" in Dialogue)
  386. @return:
  387. """
  388. self.statistics.update_teststep()
  389. self.element.submit()
  390. def findByAndClick(self, id=None, css=None, xpath=None, class_name=None, iframe=None, timeout=20, optional=False):
  391. """
  392. Execute a Click on an element identified by it's locator.
  393. @return wasSuccessful says, whether the element was found.
  394. """
  395. self.element, self.html = self.findBy(id=id, css=css, xpath=xpath, class_name=class_name, iframe=iframe,
  396. timeout=timeout, optional=optional)
  397. if not self.element:
  398. logger.debug("findBy didn't work in findByAndClick")
  399. return False
  400. else:
  401. return webDrv.webdriver_doSomething(GC.CMD_CLICK, self.element, timeout=timeout, optional=optional, browserData = self.browserData)
  402. def confirmAlertIfAny(self):
  403. self.statistics.update_teststep()
  404. try:
  405. self.browserData.driver.switch_to().alert().accept()
  406. except Exception as e:
  407. pass
  408. @staticmethod
  409. def _isValidKeyValue(value):
  410. isValid = False
  411. if not value:
  412. pass
  413. elif len(str(value)) == 0 or str(value) == "0":
  414. pass
  415. else:
  416. isValid = True
  417. return isValid
  418. def findByAndClickIf(self, id=None, css=None, xpath=None, class_name=None, iframe=None, timeout=60,
  419. value=None, optional=False):
  420. """
  421. Convenience method to not have to write:
  422. if <condition>:
  423. findByAndClick(locator)
  424. instead write:
  425. findByAndClickIf(locator, value).
  426. If value is evaluated to "True", the click-event is executed.
  427. """
  428. if self._isValidKeyValue(value):
  429. return self.findByAndClick(id=id, css=css, xpath=xpath, class_name=class_name, iframe=iframe,
  430. timeout=timeout, optional=optional)
  431. else:
  432. return False
  433. def findByAndForceText(self, id=None, css=None, xpath=None, class_name=None, value=None,
  434. iframe=None, timeout=60, optional=False):
  435. """
  436. Convenience Method. Please see documentation in findBy and __doSomething.
  437. """
  438. self.element, self.html = self.findBy(id=id, css=css, xpath=xpath, class_name=class_name, iframe=iframe,
  439. timeout=timeout, optional=optional)
  440. if not self.element:
  441. return False
  442. return webDrv.webdriver_doSomething(GC.CMD_FORCETEXT, self.element, value=value, timeout=timeout, optional=optional, browserData = self.browserData)
  443. def findByAndForceViaJS(self, id=None, css=None, xpath:str=None, class_name=None, value=None,
  444. iframe=None, timeout=60, optional=False):
  445. """
  446. Identifies the object via JS and set's the desired value via JS
  447. """
  448. # element, html = self.findBy(id=id ,css=css, xpath=xpath, class_name=class_name, iframe=iframe,
  449. # timeout=timeout, optional=optional)
  450. # didn't work to give the element to JavaScript-method
  451. xpath = xpath.replace('"', "'")
  452. xpath = xpath.replace("'", "\\'")
  453. lJSText = "\n".join(
  454. [f"var zzbaangt = document.evaluate('{xpath}', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);",
  455. "if (zzbaangt.snapshotLength > 0) { ",
  456. f"zzbaangt[0].value='{value}';",
  457. "};",
  458. f""
  459. ]
  460. )
  461. logger.debug(f"Setting element using JS-Text: {lJSText}")
  462. # lJSText = f"arguments[0].value='{value}';" --> Didn't work with Element.
  463. self.javaScript(lJSText)
  464. def setBrowserWindowSize(self, browserWindowSize: str):
  465. """
  466. Resized the browser Window to a fixed size
  467. :param browserWindowSize: String with Widht/Height or Width;Height or Width,height or width x height
  468. If you want also with leading --
  469. :return: False, if browser wasn't reset,
  470. size-Dict when resize worked.
  471. """
  472. self.statistics.update_teststep()
  473. lIntBrowserWindowSize = browserWindowSize.replace("-","").strip()
  474. lIntBrowserWindowSize = lIntBrowserWindowSize.replace(";", "/")
  475. lIntBrowserWindowSize = lIntBrowserWindowSize.replace(",", "/")
  476. lIntBrowserWindowSize = lIntBrowserWindowSize.replace("x", "/")
  477. lIntBrowserWindowSize = lIntBrowserWindowSize.replace("*", "/")
  478. validSize = False
  479. try:
  480. width = int(lIntBrowserWindowSize.split("/")[0])
  481. height = int(lIntBrowserWindowSize.split("/")[1])
  482. except KeyError as e:
  483. logger.warning(f"Called with wrong setting: {browserWindowSize}. Won't resize browser "
  484. f"Can't determine Width/Height.")
  485. except ValueError as e:
  486. logger.warning(f"Called with wrong setting: {browserWindowSize}. Won't resize browser "
  487. f"Something seems not numeric before conversion: {lIntBrowserWindowSize}")
  488. try:
  489. if width == 0 or height == 0:
  490. logger.warning(f"Called with wrong setting: {browserWindowSize}. Won't resize browser. Can't be 0")
  491. else:
  492. validSize = True
  493. except:
  494. pass
  495. if validSize:
  496. self.browserData.driver.set_window_size(width, height)
  497. size = self.browserData.driver.get_window_size()
  498. logger.debug(f"Resized browser window to width want/is: {width}/{size['width']}, "
  499. f"height want/is: {height}/{size['height']}")
  500. else:
  501. size = False
  502. return size
  503. def findNewFiles(self):
  504. """
  505. Returns a list of new files from downloadFolderMonitoring since the last call
  506. :return: List of Files since last call
  507. """
  508. self.statistics.update_teststep()
  509. l_list = self.downloadFolderMonitoring.getNewFiles()
  510. return l_list
  511. @staticmethod
  512. def _setLocator(id, css, xpath, class_name, browserData):
  513. browserData.locatorType = None
  514. browserData.locator = None
  515. if xpath:
  516. browserData.locatorType = By.XPATH
  517. browserData.locator = xpath
  518. elif css:
  519. browserData.locatorType = By.CSS_SELECTOR
  520. browserData.locator = css
  521. elif class_name:
  522. browserData.locatorType = By.CLASS_NAME
  523. browserData.locator = class_name
  524. elif id:
  525. browserData.locatorType = By.ID
  526. browserData.locator = id
  527. return browserData
  528. def findBy(self, id=None, css=None, xpath=None, class_name=None, iframe=None, timeout=60, loggingOn=True, optional=False):
  529. """
  530. chose any single locator (ID, CSS, XPATH, CLASS_NAME) to identify an element within the page. if slowExectuion
  531. is set, we'll pause for slowExecutionTimeoutInSeconds.
  532. @param id: ID of the element
  533. @param css: CSS-Locator
  534. @param xpath: XPATH
  535. @param class_name: Class-Name
  536. @param iframe: Name of an Iframe. Use only, if you didn't set the Iframe previously already!
  537. @param timeout: How many seconds shall we try/retry, default = 60 Seconds
  538. @param loggingOn: Shall this request be logged? Default = Yes
  539. @param optional: If set to true and within Timeout we can't find the element, we just return this info. If set to False (=default), an Exception is raised
  540. @return: True if element was located, False if element couldn't be found.
  541. """
  542. self.statistics.update_teststep()
  543. if self.slowExecution:
  544. time.sleep(self.slowExecutionTimeoutInSeconds)
  545. if iframe:
  546. self.handleIframe(iframe)
  547. # Set class variables for potential logging of problems.
  548. self.browserData = self._setLocator(id, css, xpath, class_name, self.browserData)
  549. if loggingOn:
  550. logger.debug(f"Locating Element {self.browserData.locatorType} = {self.browserData.locator}")
  551. self.element, self.html = webDrv.webdriver_tryAndRetry(self.browserData, timeout=timeout, optional=optional)
  552. if not self.element and not optional:
  553. raise Exceptions.baangtTestStepException(f"Element {self.browserData.locatorType} = {self.browserData.locator} could not be found "
  554. f"within timeout of {timeout}")
  555. return self.element, self.html
  556. def getURL(self):
  557. """
  558. @return: the current URL/URI of the current Tab of the current Browser
  559. """
  560. self.statistics.update_teststep()
  561. return self.browserData.driver.current_url
  562. def findWaitNotVisible(self, css=None, xpath=None, id=None, timeout=90, optional=False):
  563. """
  564. You'd use this method when you wait for an element to disappear, for instance Angular Spinner or a popup
  565. to disapear before you continue with your script in the main screen.
  566. """
  567. self.statistics.update_teststep()
  568. logger.debug(f"Waiting for Element to disappear: XPATH:{xpath}, timeout: {timeout}")
  569. time.sleep(0.5)
  570. stillHere = True
  571. elapsed = 0
  572. begin = time.time()
  573. while stillHere and elapsed < timeout:
  574. try:
  575. if xpath:
  576. self.element = self.browserData.driver.find_element_by_xpath(xpath)
  577. elif id:
  578. self.element = self.browserData.driver.find_element_by_id(id)
  579. elif css:
  580. self.element = self.browserData.driver.find_element_by_css_selector(css)
  581. time.sleep(0.2)
  582. elapsed = time.time() - begin
  583. except Exception as e:
  584. # Element gone - exit
  585. stillHere = False
  586. helper.browserHelper_log(logging.DEBUG, f"Element was gone after {format(elapsed, '.2f')} seconds", self.browserData)
  587. if not stillHere:
  588. raise Exceptions.baangtTestStepException(
  589. f"Element still here after {timeout} seconds. Locator: xpath={xpath}, id={id}")
  590. return stillHere
  591. def checkLinks(self):
  592. """
  593. For the current page we'll check all links and return result in format
  594. <status_code> <link_that_was_checked>
  595. :return: List of checked links
  596. """
  597. self.statistics.update_teststep()
  598. lResult = []
  599. links = self.browserData.driver.find_elements_by_css_selector("a")
  600. logger.debug(f"Checking links on page {self.browserData.driver.current_url}")
  601. for link in links:
  602. lHref = link.get_attribute("href")
  603. if not lHref:
  604. continue
  605. if lHref.startswith("mailto"):
  606. pass
  607. else:
  608. try:
  609. r = requests.head(lHref)
  610. lResult.append([r.status_code, lHref])
  611. logger.debug(f"Result was: {r.status_code}, {lHref}")
  612. except requests.exceptions.InvalidURL as e:
  613. lResult.append([HTTPStatus.INTERNAL_SERVER_ERROR, f"Invalid URL: {lHref}"])
  614. except requests.exceptions.ConnectionError as e:
  615. lResult.append([HTTPStatus.INTERNAL_SERVER_ERROR, f"HTTP connection error: {lHref}"])
  616. except requests.exceptions.MissingSchema as e:
  617. lResult.append([HTTPStatus.INTERNAL_SERVER_ERROR, f"Missing Schema - invalid URL: {lHref}"])
  618. return lResult
  619. def waitForElementChangeAfterButtonClick(self, timeout=5):
  620. """
  621. Wait for a stale element (in a good way). Stale means, that the object has changed.
  622. old element is in self.element
  623. old locator is in self.browserData.locatorType and self.browserData.locator
  624. :param timeout:
  625. :return:
  626. """
  627. self.statistics.update_teststep()
  628. lOldElement = self.element.id
  629. isValid = False
  630. lStartOfWaiting = time.time()
  631. elapsed = 0
  632. logger.debug("Starting")
  633. xpath, css, id = utils.setLocatorFromLocatorType(self.browserData.locatorType, self.browserData.locator)
  634. while not isValid and elapsed < timeout:
  635. self.element, self.html = self.findBy(xpath=xpath, css=css, id=id, timeout=0.5, optional=True)
  636. if not self.element:
  637. # Wonderful. Element is gone
  638. logger.debug("Old object is not in the page any longer, save to continue")
  639. isValid = True
  640. if self.element.id != lOldElement:
  641. logger.debug("Old element is stale, save to continue")
  642. isValid = True
  643. time.sleep(0.2)
  644. elapsed = time.time() - lStartOfWaiting
  645. if not isValid:
  646. # TimeOut Return false
  647. logger.debug("Old element equal to new element after timeout. Staleness not detected using this method")
  648. return isValid
  649. def waitForPageLoadAfterButtonClick(self, timeout=5):
  650. """
  651. Problem: If the same XPATH/CSS/ID exists on both pages (the current one, where a button is clicked
  652. and the next one, where we now want to interact, then it happens very often, that the element
  653. is stale (because it was bound to the current page BEFORE the page-load happened.
  654. Solution: Wait deliberately until current self.element is stale.
  655. :param timout: Yeah, you guessed it. The timeout
  656. :return: True = New page loaded, False = The element didn't get stale within timeout
  657. """
  658. # Performance in 5 parallel Runs dropped from 06:50 to 07:51. That's 1 Minute slower
  659. # 60 Seconds or 10% time lost.
  660. # For now let it as it is. If users report that as a problem, revisit the subject and
  661. # e.g. find another way to understand, whether we're still on the same page or not.
  662. self.statistics.update_teststep()
  663. if not self.html:
  664. sys.exit("Something is very wrong! self.html didn't exist when waitForPageLoadAfterButtonClick was called")
  665. lStartOfWaiting = time.time()
  666. elapsed = 0
  667. logger.debug("Starting")
  668. while elapsed < timeout:
  669. lHTML = self.browserData.driver.find_element_by_tag_name("html")
  670. if lHTML != self.html:
  671. logger.debug("Page was reloaded")
  672. return True
  673. time.sleep(0.2)
  674. elapsed = time.time() - lStartOfWaiting
  675. logger.debug("No Page reload detected by this method")
  676. return False # There was no changed HTML
  677. def goToUrl(self, url):
  678. self.statistics.update_teststep()
  679. helper.browserHelper_log(logging.INFO, f'GoToUrl:{url}', self.browserData)
  680. try:
  681. if self.browserName==GC.BROWSER_FIREFOX:
  682. self.browserData.driver.set_context("content")
  683. self.browserData.driver.get(url)
  684. self.setZoomFactor()
  685. except WebDriverException as e:
  686. # Use noScreenshot-Parameter as otherwise we'll try on a dead browser to create a screenshot
  687. helper.browserHelper_log(logging.ERROR, f"Webpage {url} not reached. Error was: {e}", self.browserData, cbTakeScreenshot=self.takeScreenshot)
  688. helper.browserHelper_setProxyError(self.randomProxy)
  689. raise Exceptions.baangtTestStepException
  690. except Exception as e:
  691. # Use noScreenshot-Parameter as otherwise we'll try on a dead browser to create a screenshot
  692. helper.browserHelper_log(logging.ERROR, f"Webpage {url} throws error {e}", self.browserData, cbTakeScreenshot=self.takeScreenshot)
  693. helper.browserHelper_setProxyError(self.randomProxy)
  694. raise Exceptions.baangtTestStepException(url, e)
  695. def goBack(self):
  696. """
  697. Method to go 1 step back in current tab's browse history
  698. @return:
  699. """
  700. self.statistics.update_teststep()
  701. try:
  702. self.javaScript("window.history.go(-1)")
  703. except Exception as e:
  704. helper.browserHelper_log(logging.WARNING, f"Tried to go back in history, didn't work with error {e}", self.browserData)
  705. def javaScript(self, jsText, *args):
  706. """Execute a given JavaScript in the current Session"""
  707. self.statistics.update_teststep()
  708. self.browserData.driver.execute_script(jsText, *args)
  709. def _zoomFirefox(self, lZoomKey, lHitKeyTimes):
  710. try:
  711. lWindow = self.browserData.driver.find_element_by_tag_name("html")
  712. # Reset the browser window to 100%:
  713. if platform.system().lower() == "darwin":
  714. lWindow.send_keys(keys.Keys.META + "0")
  715. else:
  716. lWindow.send_keys(keys.Keys.CONTROL + "0")
  717. # Now set to desired zoom factor:
  718. for counter in range(lHitKeyTimes):
  719. if platform.system().lower() == "darwin":
  720. lWindow.send_keys(keys.Keys.META + lZoomKey)
  721. else:
  722. lWindow.send_keys(keys.Keys.CONTROL + lZoomKey)
  723. logger.debug(f"Adjusted zoom factor of browserwindow to {self.zoomFactorDesired}")
  724. except Exception as e:
  725. logger.debug(f"Tried to adjust zoom factor and failed: {e}")
  726. finally:
  727. self.browserData.driver.set_context("content")
  728. def setZoomFactor(self, lZoomFactor=None):
  729. """
  730. Will try to set the browser's zoom factor.
  731. :param lZoomFactor: set with a value. Otherwise existing value will be used (if previously set)
  732. :return:
  733. """
  734. isZoomed = False
  735. if self.zoomFactorDesired and lZoomFactor:
  736. self.zoomFactorDesired = int(lZoomFactor)
  737. if self.browserName == GC.BROWSER_CHROME:
  738. logger.critical(f"Zoom in Chrome doesn't work. Continuing without zoom")
  739. return False
  740. x = self.getURL()
  741. if x[0:5] == "http:": # He loaded already something. Too late for us
  742. logger.debug("CHROME: Got called to change Zoom level - but already URL loaded. Too late.")
  743. else:
  744. self.browserData.driver.get("chrome://settings/")
  745. self.browserData.driver.execute_script(f"chrome.settingsPrivate.setDefaultZoom({self.zoomFactorDesired/100});")
  746. logger.debug(f"CHROME: Set default zoom using JS-Method to {self.zoomFactorDesired/100}")
  747. isZoomed = True
  748. elif self.browserName == GC.BROWSER_FIREFOX:
  749. self.browserData.driver.set_context("chrome")
  750. lZoomKey = "+" if self.zoomFactorDesired > 100 else "-"
  751. # E.g. current = 100. Desired = 67%: 100-67 = 33. 33/10 = 3.3 int(3.3) = 3 --> he'll hit 3 times CTRL+"-"
  752. lDifference = abs(100 - self.zoomFactorDesired)
  753. lHitKeyTimes = int(lDifference/10)
  754. self._zoomFirefox(lZoomKey, lHitKeyTimes)
  755. isZoomed = True
  756. else:
  757. # statement not matched
  758. pass
  759. else:
  760. # statement not matched
  761. pass
  762. return isZoomed
  763. @staticmethod
  764. def downloadDriver(browserName):
  765. managedPaths = ManagedPaths()
  766. path = Path(managedPaths.getOrSetDriverPath())
  767. logger.debug(f"Trying to download browserDriver for {browserName} into {path}")
  768. path.mkdir(parents=True, exist_ok=True)
  769. if browserName == GC.BROWSER_FIREFOX:
  770. url, isTarFile = helper.browserHelper_getFirefoxFileUrl()
  771. if isTarFile:
  772. helper.browserHelper_extractTarDriverFile(url, path, GC.GECKO_DRIVER)
  773. else:
  774. helper.browserHelper_unzipDriverFile(url, path, GC.GECKO_DRIVER)
  775. elif browserName == GC.BROWSER_CHROME:
  776. url = helper.browserHelper_getChromeFileUrl()
  777. helper.browserHelper_unzipDriverFile(url, path, GC.CHROME_DRIVER)
  778. else:
  779. logger.critical(f"Please download driver for {browserName} manually into folder /browserDrivers")