PageRenderTime 69ms CodeModel.GetById 40ms RepoModel.GetById 0ms app.codeStats 0ms

/pytest_mozwebqa/pytest_mozwebqa.py

https://github.com/retornam/pytest-mozwebqa
Python | 330 lines | 292 code | 31 blank | 7 comment | 49 complexity | fa68a2cbd1a00289a3d734e11cd59339 MD5 | raw file
  1. #!/usr/bin/env python
  2. # This Source Code Form is subject to the terms of the Mozilla Public
  3. # License, v. 2.0. If a copy of the MPL was not distributed with this
  4. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  5. import py
  6. import re
  7. import ConfigParser
  8. import requests
  9. import credentials
  10. __version__ = '1.1'
  11. def pytest_configure(config):
  12. if not hasattr(config, 'slaveinput'):
  13. config.addinivalue_line(
  14. 'markers', 'nondestructive: mark the test as nondestructive. ' \
  15. 'Tests are assumed to be destructive unless this marker is ' \
  16. 'present. This reduces the risk of running destructive tests ' \
  17. 'accidentally.')
  18. if config.option.webqa_report_path:
  19. from html_report import HTMLReport
  20. config._html = HTMLReport(config)
  21. config.pluginmanager.register(config._html)
  22. if not config.option.run_destructive:
  23. if config.option.markexpr:
  24. config.option.markexpr = 'nondestructive and (%s)' % config.option.markexpr
  25. else:
  26. config.option.markexpr = 'nondestructive'
  27. def pytest_unconfigure(config):
  28. html = getattr(config, '_html', None)
  29. if html:
  30. del config._html
  31. config.pluginmanager.unregister(html)
  32. def pytest_sessionstart(session):
  33. if session.config.option.base_url and not (session.config.option.skip_url_check or session.config.option.collectonly):
  34. r = requests.get(session.config.option.base_url, verify=False)
  35. assert r.status_code == 200, 'Base URL did not return status code 200. (URL: %s, Response: %s)' % (session.config.option.base_url, r.status_code)
  36. def pytest_runtest_setup(item):
  37. item.debug = {
  38. 'urls': [],
  39. 'screenshots': [],
  40. 'html': [],
  41. 'logs': [],
  42. 'network_traffic': []}
  43. TestSetup.base_url = item.config.option.base_url
  44. # consider this environment sensitive if the base url or any redirection
  45. # history matches the regular expression
  46. sensitive = False
  47. if TestSetup.base_url and not item.config.option.skip_url_check:
  48. r = requests.get(TestSetup.base_url, verify=False)
  49. urls = [h.url for h in r.history] + [r.url]
  50. matches = [re.search(item.config.option.sensitive_url, u) for u in urls]
  51. sensitive = any(matches)
  52. destructive = 'nondestructive' not in item.keywords
  53. if (sensitive and destructive):
  54. first_match = matches[next(i for i, match in enumerate(matches) if match)]
  55. # skip the test with an appropriate message
  56. py.test.skip('This test is destructive and the target URL is ' \
  57. 'considered a sensitive environment. If this test is ' \
  58. 'not destructive, add the \'nondestructive\' marker to ' \
  59. 'it. Sensitive URL: %s' % first_match.string)
  60. if item.config.option.sauce_labs_credentials_file:
  61. item.sauce_labs_credentials = credentials.read(item.config.option.sauce_labs_credentials_file)
  62. if item.config.option.credentials_file:
  63. TestSetup.credentials = credentials.read(item.config.option.credentials_file)
  64. test_id = '.'.join(split_class_and_test_names(item.nodeid))
  65. if 'skip_selenium' not in item.keywords:
  66. if hasattr(item, 'sauce_labs_credentials'):
  67. from sauce_labs import Client
  68. TestSetup.selenium_client = Client(
  69. test_id,
  70. item.config.option,
  71. item.keywords,
  72. item.sauce_labs_credentials)
  73. else:
  74. from selenium_client import Client
  75. TestSetup.selenium_client = Client(
  76. test_id,
  77. item.config.option)
  78. TestSetup.selenium_client.start()
  79. item.session_id = TestSetup.selenium_client.session_id
  80. TestSetup.selenium = TestSetup.selenium_client.selenium
  81. TestSetup.timeout = TestSetup.selenium_client.timeout
  82. TestSetup.default_implicit_wait = TestSetup.selenium_client.default_implicit_wait
  83. else:
  84. TestSetup.timeout = item.config.option.webqatimeout
  85. TestSetup.selenium = None
  86. def pytest_runtest_teardown(item):
  87. if hasattr(TestSetup, 'selenium') and TestSetup.selenium and 'skip_selenium' not in item.keywords:
  88. TestSetup.selenium_client.stop()
  89. def pytest_runtest_makereport(__multicall__, item, call):
  90. report = __multicall__.execute()
  91. if report.when == 'call':
  92. report.session_id = getattr(item, 'session_id', None)
  93. if hasattr(TestSetup, 'selenium') and TestSetup.selenium and not 'skip_selenium' in item.keywords:
  94. if report.skipped and 'xfail' in report.keywords or report.failed and 'xfail' not in report.keywords:
  95. url = TestSetup.selenium_client.url
  96. url and item.debug['urls'].append(url)
  97. screenshot = TestSetup.selenium_client.screenshot
  98. screenshot and item.debug['screenshots'].append(screenshot)
  99. html = TestSetup.selenium_client.html
  100. html and item.debug['html'].append(html)
  101. log = TestSetup.selenium_client.log
  102. log and item.debug['logs'].append(log)
  103. report.sections.append(('pytest-mozwebqa', _debug_summary(item.debug)))
  104. network_traffic = TestSetup.selenium_client.network_traffic
  105. network_traffic and item.debug['network_traffic'].append(network_traffic)
  106. report.debug = item.debug
  107. if hasattr(item, 'sauce_labs_credentials') and report.session_id:
  108. result = {'passed': report.passed or (report.failed and 'xfail' in report.keywords)}
  109. import sauce_labs
  110. sauce_labs.Job(report.session_id).send_result(
  111. result,
  112. item.sauce_labs_credentials)
  113. return report
  114. def pytest_funcarg__mozwebqa(request):
  115. return TestSetup(request)
  116. def pytest_addoption(parser):
  117. config = ConfigParser.ConfigParser(defaults={
  118. 'baseurl': '',
  119. 'api': 'webdriver'
  120. })
  121. config.read('mozwebqa.cfg')
  122. group = parser.getgroup('selenium', 'selenium')
  123. group._addoption('--baseurl',
  124. action='store',
  125. dest='base_url',
  126. default=config.get('DEFAULT', 'baseurl'),
  127. metavar='url',
  128. help='base url for the application under test.')
  129. group._addoption('--skipurlcheck',
  130. action='store_true',
  131. dest='skip_url_check',
  132. default=False,
  133. help='skip the base url and sensitivity checks. (default: %default)')
  134. group._addoption('--api',
  135. action='store',
  136. default=config.get('DEFAULT', 'api'),
  137. metavar='api',
  138. help="version of selenium api to use. 'rc' uses selenium rc. 'webdriver' uses selenium webdriver. (default: %default)")
  139. group._addoption('--host',
  140. action='store',
  141. default='localhost',
  142. metavar='str',
  143. help='host that selenium server is listening on. (default: %default)')
  144. group._addoption('--port',
  145. action='store',
  146. type='int',
  147. default=4444,
  148. metavar='num',
  149. help='port that selenium server is listening on. (default: %default)')
  150. group._addoption('--driver',
  151. action='store',
  152. dest='driver',
  153. default='Remote',
  154. metavar='str',
  155. help='webdriver implementation. (default: %default)')
  156. group._addoption('--capabilities',
  157. action='store',
  158. dest='capabilities',
  159. metavar='str',
  160. help='json string of additional capabilties to set (webdriver).')
  161. group._addoption('--chromepath',
  162. action='store',
  163. dest='chrome_path',
  164. metavar='path',
  165. help='path to the google chrome driver executable.')
  166. group._addoption('--firefoxpath',
  167. action='store',
  168. dest='firefox_path',
  169. metavar='path',
  170. help='path to the target firefox binary.')
  171. group._addoption('--firefoxpref',
  172. action='store',
  173. dest='firefox_preferences',
  174. metavar='str',
  175. help='json string of firefox preferences to set (webdriver).')
  176. group._addoption('--profilepath',
  177. action='store',
  178. dest='profile_path',
  179. metavar='str',
  180. help='path to the firefox profile to use (webdriver).')
  181. group._addoption('--extension',
  182. action='append',
  183. dest='extension_paths',
  184. metavar='str',
  185. help='path to browser extension to install (webdriver).')
  186. group._addoption('--chromeopts',
  187. action='store',
  188. dest='chrome_options',
  189. metavar='str',
  190. help='json string of google chrome options to set (webdriver).')
  191. group._addoption('--operapath',
  192. action='store',
  193. dest='opera_path',
  194. metavar='path',
  195. help='path to the opera driver.')
  196. group._addoption('--browser',
  197. action='store',
  198. dest='browser',
  199. metavar='str',
  200. help='target browser (standalone rc server).')
  201. group._addoption('--environment',
  202. action='store',
  203. dest='environment',
  204. metavar='str',
  205. help='target environment (grid rc).')
  206. group._addoption('--browsername',
  207. action='store',
  208. dest='browser_name',
  209. metavar='str',
  210. help='target browser name (webdriver).')
  211. group._addoption('--browserver',
  212. action='store',
  213. dest='browser_version',
  214. metavar='str',
  215. help='target browser version (webdriver).')
  216. group._addoption('--platform',
  217. action='store',
  218. metavar='str',
  219. help='target platform (webdriver).')
  220. group._addoption('--webqatimeout',
  221. action='store',
  222. type='int',
  223. default=60,
  224. metavar='num',
  225. help='timeout (in seconds) for page loads, etc. (default: %default)')
  226. group._addoption('--capturenetwork',
  227. action='store_true',
  228. dest='capture_network',
  229. default=False,
  230. help='capture network traffic to test_method_name.json (selenium rc). (default: %default)')
  231. group._addoption('--build',
  232. action='store',
  233. dest='build',
  234. metavar='str',
  235. help='build identifier (for continuous integration).')
  236. group._addoption('--untrusted',
  237. action='store_true',
  238. dest='assume_untrusted',
  239. default=False,
  240. help='assume that all certificate issuers are untrusted. (default: %default)')
  241. group = parser.getgroup('safety', 'safety')
  242. group._addoption('--sensitiveurl',
  243. action='store',
  244. dest='sensitive_url',
  245. default='mozilla\.(com|org)',
  246. metavar='str',
  247. help='regular expression for identifying sensitive urls. (default: %default)')
  248. group._addoption('--destructive',
  249. action='store_true',
  250. dest='run_destructive',
  251. default=False,
  252. help='include destructive tests (tests not explicitly marked as \'nondestructive\'). (default: %default)')
  253. group = parser.getgroup('credentials', 'credentials')
  254. group._addoption("--credentials",
  255. action="store",
  256. dest='credentials_file',
  257. metavar='path',
  258. help="location of yaml file containing user credentials.")
  259. group._addoption('--saucelabs',
  260. action='store',
  261. dest='sauce_labs_credentials_file',
  262. metavar='path',
  263. help='credendials file containing sauce labs username and api key.')
  264. group = parser.getgroup("terminal reporting")
  265. group.addoption('--webqareport',
  266. action='store',
  267. dest='webqa_report_path',
  268. metavar='path',
  269. default='results/index.html',
  270. help='create mozilla webqa custom report file at given path. (default: %default)')
  271. def split_class_and_test_names(nodeid):
  272. names = nodeid.split("::")
  273. names[0] = names[0].replace("/", '.')
  274. names = [x.replace(".py", "") for x in names if x != "()"]
  275. classnames = names[:-1]
  276. classname = ".".join(classnames)
  277. name = names[-1]
  278. return (classname, name)
  279. def _debug_summary(debug):
  280. summary = []
  281. if debug['urls']:
  282. summary.append('Failing URL: %s' % debug['urls'][-1])
  283. return '\n'.join(summary)
  284. class TestSetup:
  285. '''
  286. This class is just used for monkey patching
  287. '''
  288. def __init__(self, request):
  289. self.request = request