diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..9c554d9e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,6 @@ +repos: +- repo: https://github.com/ambv/black + rev: stable + hooks: + - id: black + language_version: python3 diff --git a/README.rst b/README.rst index 4bac8d50..c75ae9b4 100644 --- a/README.rst +++ b/README.rst @@ -16,6 +16,8 @@ support for running `Selenium `_ based tests. .. image:: https://img.shields.io/badge/docs-latest-brightgreen.svg :target: http://pytest-selenium.readthedocs.io/en/latest/ :alt: Read the Docs +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/ambv/black .. image:: https://img.shields.io/github/issues-raw/pytest-dev/pytest-selenium.svg :target: https://github.com/pytest-dev/pytest-selenium/issues :alt: Issues diff --git a/docs/conf.py b/docs/conf.py index 8e21024c..0a47b7ae 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,48 +19,45 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.viewcode', -] +extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'pytest-selenium' -copyright = u'2015, Dave Hunt' -author = u'Dave Hunt' +project = u"pytest-selenium" +copyright = u"2015, Dave Hunt" +author = u"Dave Hunt" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = 'latest' +version = "latest" # The full version, including alpha/beta/rc tags. -release = 'latest' +release = "latest" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -71,37 +68,37 @@ # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -111,31 +108,31 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -145,109 +142,111 @@ # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' +# html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} +# html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' +# html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'pytest-seleniumdoc' +htmlhelp_basename = "pytest-seleniumdoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', + # Latex figure (float) alignment + # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'pytest-selenium.tex', u'pytest-selenium Documentation', - u'Dave Hunt', 'manual'), + ( + master_doc, + "pytest-selenium.tex", + u"pytest-selenium Documentation", + u"Dave Hunt", + "manual", + ) ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output --------------------------------------- @@ -255,12 +254,11 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'pytest-selenium', u'pytest-selenium Documentation', - [author], 1) + (master_doc, "pytest-selenium", u"pytest-selenium Documentation", [author], 1) ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -269,19 +267,25 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'pytest-selenium', u'pytest-selenium Documentation', - author, 'pytest-selenium', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "pytest-selenium", + u"pytest-selenium Documentation", + author, + "pytest-selenium", + "One line description of project.", + "Miscellaneous", + ) ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False diff --git a/docs/user_guide.rst b/docs/user_guide.rst index 4ad31fac..daab7a79 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -136,6 +136,16 @@ preferences, and a command line argument: See the `Firefox options API documentation`_ for full details of what can be configured. +You can also use the ``firefox_preferences`` and ``firefox_arguments`` markers: + +.. code-block:: python + + import pytest + @pytest.mark.firefox_arguments('-foreground') + @pytest.mark.firefox_preferences({'browser.anchor_color': '#FF0000'}) + def test_firefox(selenium): + selenium.get('http://www.example.com') + Chrome ------ diff --git a/pytest_selenium/drivers/browserstack.py b/pytest_selenium/drivers/browserstack.py index 431c9eaa..7ba409cf 100644 --- a/pytest_selenium/drivers/browserstack.py +++ b/pytest_selenium/drivers/browserstack.py @@ -10,7 +10,7 @@ class BrowserStack(Provider): - API = 'https://www.browserstack.com/automate/sessions/{session}.json' + API = "https://www.browserstack.com/automate/sessions/{session}.json" @property def auth(self): @@ -18,65 +18,70 @@ def auth(self): @property def executor(self): - return 'http://{0}:{1}@hub.browserstack.com:80/wd/hub'.format( - self.username, self.key) + return "http://{0}:{1}@hub.browserstack.com:80/wd/hub".format( + self.username, self.key + ) @property def username(self): - return self.get_credential('username', ['BROWSERSTACK_USERNAME', - 'BROWSERSTACK_USR']) + return self.get_credential( + "username", ["BROWSERSTACK_USERNAME", "BROWSERSTACK_USR"] + ) @property def key(self): - return self.get_credential('key', ['BROWSERSTACK_ACCESS_KEY', - 'BROWSERSTACK_PSW']) + return self.get_credential( + "key", ["BROWSERSTACK_ACCESS_KEY", "BROWSERSTACK_PSW"] + ) @pytest.mark.optionalhook def pytest_selenium_runtest_makereport(item, report, summary, extra): provider = BrowserStack() - if not provider.uses_driver(item.config.getoption('driver')): + if not provider.uses_driver(item.config.getoption("driver")): return - passed = report.passed or (report.failed and hasattr(report, 'wasxfail')) + passed = report.passed or (report.failed and hasattr(report, "wasxfail")) session_id = item._driver.session_id api_url = provider.API.format(session=session_id) try: job_info = requests.get(api_url, auth=provider.auth, timeout=10).json() - job_url = job_info['automation_session']['browser_url'] + job_url = job_info["automation_session"]["browser_url"] # Add the job URL to the summary - summary.append('{0} Job: {1}'.format(provider.name, job_url)) - pytest_html = item.config.pluginmanager.getplugin('html') + summary.append("{0} Job: {1}".format(provider.name, job_url)) + pytest_html = item.config.pluginmanager.getplugin("html") # Add the job URL to the HTML report - extra.append(pytest_html.extras.url(job_url, '{0} Job'.format( - provider.name))) + extra.append(pytest_html.extras.url(job_url, "{0} Job".format(provider.name))) except Exception as e: - summary.append('WARNING: Failed to determine {0} job URL: {1}'.format( - provider.name, e)) + summary.append( + "WARNING: Failed to determine {0} job URL: {1}".format(provider.name, e) + ) try: # Update the job result - job_status = job_info['automation_session']['status'] - status = 'running' if passed else 'error' - if report.when == 'teardown' and passed: - status = 'completed' - if job_status not in ('error', status): + job_status = job_info["automation_session"]["status"] + status = "running" if passed else "error" + if report.when == "teardown" and passed: + status = "completed" + if job_status not in ("error", status): # Only update the result if it's not already marked as failed requests.put( api_url, - headers={'Content-Type': 'application/json'}, - params={'status': status}, + headers={"Content-Type": "application/json"}, + params={"status": status}, auth=provider.auth, - timeout=10) + timeout=10, + ) except Exception as e: - summary.append('WARNING: Failed to update job status: {0}'.format(e)) + summary.append("WARNING: Failed to update job status: {0}".format(e)) def driver_kwargs(request, test, capabilities, **kwargs): provider = BrowserStack() - capabilities.setdefault('name', test) + capabilities.setdefault("name", test) kwargs = { - 'command_executor': provider.executor, - 'desired_capabilities': capabilities} + "command_executor": provider.executor, + "desired_capabilities": capabilities, + } return kwargs diff --git a/pytest_selenium/drivers/chrome.py b/pytest_selenium/drivers/chrome.py index 5287e269..1ffbe093 100644 --- a/pytest_selenium/drivers/chrome.py +++ b/pytest_selenium/drivers/chrome.py @@ -8,21 +8,21 @@ from selenium.webdriver.chrome.options import Options -def driver_kwargs(capabilities, driver_args, driver_log, driver_path, - chrome_options, **kwargs): - kwargs = {'desired_capabilities': capabilities, - 'service_log_path': driver_log} +def driver_kwargs( + capabilities, driver_args, driver_log, driver_path, chrome_options, **kwargs +): + kwargs = {"desired_capabilities": capabilities, "service_log_path": driver_log} # Selenium 3.8.0 deprecated chrome_options in favour of options - if LooseVersion(SELENIUM_VERSION) < LooseVersion('3.8.0'): - kwargs['chrome_options'] = chrome_options + if LooseVersion(SELENIUM_VERSION) < LooseVersion("3.8.0"): + kwargs["chrome_options"] = chrome_options else: - kwargs['options'] = chrome_options + kwargs["options"] = chrome_options if driver_args is not None: - kwargs['service_args'] = driver_args + kwargs["service_args"] = driver_args if driver_path is not None: - kwargs['executable_path'] = driver_path + kwargs["executable_path"] = driver_path return kwargs diff --git a/pytest_selenium/drivers/cloud.py b/pytest_selenium/drivers/cloud.py index 47f533e3..aca7fc6c 100644 --- a/pytest_selenium/drivers/cloud.py +++ b/pytest_selenium/drivers/cloud.py @@ -15,24 +15,21 @@ class Provider(object): - @property def name(self): return type(self).__name__ @property def config(self): - name = '.{0}'.format(self.name.lower()) + name = ".{0}".format(self.name.lower()) config = configparser.ConfigParser() - config.read([name, os.path.join(os.path.expanduser('~'), name)]) + config.read([name, os.path.join(os.path.expanduser("~"), name)]) return config def get_credential(self, key, envs): try: - return self.config.get('credentials', key) - except (configparser.NoSectionError, - configparser.NoOptionError, - KeyError): + return self.config.get("credentials", key) + except (configparser.NoSectionError, configparser.NoOptionError, KeyError): for env in envs: value = os.getenv(env) if value: diff --git a/pytest_selenium/drivers/crossbrowsertesting.py b/pytest_selenium/drivers/crossbrowsertesting.py index 4943bf79..36633774 100644 --- a/pytest_selenium/drivers/crossbrowsertesting.py +++ b/pytest_selenium/drivers/crossbrowsertesting.py @@ -11,7 +11,7 @@ class CrossBrowserTesting(Provider): - API = 'https://crossbrowsertesting.com/api/v3/selenium/{session}' + API = "https://crossbrowsertesting.com/api/v3/selenium/{session}" @property def auth(self): @@ -19,91 +19,102 @@ def auth(self): @property def executor(self): - return 'http://{0}:{1}@hub.crossbrowsertesting.com:80/wd/hub'.format( - self.username, self.key) + return "http://{0}:{1}@hub.crossbrowsertesting.com:80/wd/hub".format( + self.username, self.key + ) @property def username(self): - return self.get_credential('username', ['CROSSBROWSERTESTING_USERNAME', - 'CROSSBROWSERTESTING_USR']) + return self.get_credential( + "username", ["CROSSBROWSERTESTING_USERNAME", "CROSSBROWSERTESTING_USR"] + ) @property def key(self): - return self.get_credential('key', ['CROSSBROWSERTESTING_AUTH_KEY', - 'CROSSBROWSERTESTING_PSW']) + return self.get_credential( + "key", ["CROSSBROWSERTESTING_AUTH_KEY", "CROSSBROWSERTESTING_PSW"] + ) @pytest.mark.optionalhook def pytest_selenium_capture_debug(item, report, extra): provider = CrossBrowserTesting() - if not provider.uses_driver(item.config.getoption('driver')): + if not provider.uses_driver(item.config.getoption("driver")): return - videos = requests.get( - provider.API.format(session=item._driver.session_id), - auth=provider.auth, - timeout=10).json().get('videos') + videos = ( + requests.get( + provider.API.format(session=item._driver.session_id), + auth=provider.auth, + timeout=10, + ) + .json() + .get("videos") + ) if videos and len(videos) > 0: - pytest_html = item.config.pluginmanager.getplugin('html') + pytest_html = item.config.pluginmanager.getplugin("html") extra.append(pytest_html.extras.html(_video_html(videos[0]))) @pytest.mark.optionalhook def pytest_selenium_runtest_makereport(item, report, summary, extra): provider = CrossBrowserTesting() - if not provider.uses_driver(item.config.getoption('driver')): + if not provider.uses_driver(item.config.getoption("driver")): return - passed = report.passed or (report.failed and hasattr(report, 'wasxfail')) + passed = report.passed or (report.failed and hasattr(report, "wasxfail")) # Add the test URL to the summary info = requests.get( provider.API.format(session=item._driver.session_id), auth=provider.auth, - timeout=10).json() + timeout=10, + ).json() - url = info.get('show_result_public_url') - summary.append('{0}: {1}'.format(provider.name, url)) - pytest_html = item.config.pluginmanager.getplugin('html') + url = info.get("show_result_public_url") + summary.append("{0}: {1}".format(provider.name, url)) + pytest_html = item.config.pluginmanager.getplugin("html") # Add the job URL to the HTML report extra.append(pytest_html.extras.url(url, provider.name)) try: # Update the test result - if report.when == 'setup' or info.get('test_score') is not 'fail': + if report.when == "setup" or info.get("test_score") is not "fail": # Only update the result if it's not already marked as failed - score = 'pass' if passed else 'fail' - data = {'action': 'set_score', 'score': score} + score = "pass" if passed else "fail" + data = {"action": "set_score", "score": score} r = requests.put( - provider.API.format(session=info.get('selenium_test_id')), + provider.API.format(session=info.get("selenium_test_id")), data=data, auth=provider.auth, - timeout=10) + timeout=10, + ) r.raise_for_status() except Exception as e: - summary.append('WARNING: Failed to update {0} job status: {1}'.format( - provider.name, e)) + summary.append( + "WARNING: Failed to update {0} job status: {1}".format(provider.name, e) + ) def driver_kwargs(request, test, capabilities, **kwargs): provider = CrossBrowserTesting() - capabilities.setdefault('name', test) + capabilities.setdefault("name", test) kwargs = { - 'command_executor': provider.executor, - 'desired_capabilities': capabilities} + "command_executor": provider.executor, + "desired_capabilities": capabilities, + } return kwargs def _video_html(video): - html.__tagspec__.update(dict([(x, 1) for x in ('video', 'source')])) + html.__tagspec__.update(dict([(x, 1) for x in ("video", "source")])) video_attrs = { - 'controls': '', - 'poster': video.get('image'), - 'play-pause-on-click': '', - 'style': 'border:1px solid #e6e6e6; float:right; height:240px; ' - 'margin-left:5px; overflow:hidden; width:320px'} - source_attrs = {'src': video.get('video'), 'type': 'video/mp4'} - return str(html.video( - html.source(**source_attrs), - **video_attrs)) + "controls": "", + "poster": video.get("image"), + "play-pause-on-click": "", + "style": "border:1px solid #e6e6e6; float:right; height:240px; " + "margin-left:5px; overflow:hidden; width:320px", + } + source_attrs = {"src": video.get("video"), "type": "video/mp4"} + return str(html.video(html.source(**source_attrs), **video_attrs)) diff --git a/pytest_selenium/drivers/edge.py b/pytest_selenium/drivers/edge.py index 071121ad..14b3f27d 100644 --- a/pytest_selenium/drivers/edge.py +++ b/pytest_selenium/drivers/edge.py @@ -8,13 +8,13 @@ def driver_kwargs(capabilities, driver_log, driver_path, **kwargs): # Selenium 3.14.0 deprecated log_path in favour of service_log_path - if LooseVersion(SELENIUM_VERSION) < LooseVersion('3.14.0'): - kwargs = {'log_path': driver_log} + if LooseVersion(SELENIUM_VERSION) < LooseVersion("3.14.0"): + kwargs = {"log_path": driver_log} else: - kwargs = {'service_log_path': driver_log} + kwargs = {"service_log_path": driver_log} if capabilities: - kwargs['capabilities'] = capabilities + kwargs["capabilities"] = capabilities if driver_path is not None: - kwargs['executable_path'] = driver_path + kwargs["executable_path"] = driver_path return kwargs diff --git a/pytest_selenium/drivers/firefox.py b/pytest_selenium/drivers/firefox.py index 6b35ccdc..5475ed10 100644 --- a/pytest_selenium/drivers/firefox.py +++ b/pytest_selenium/drivers/firefox.py @@ -3,6 +3,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. from distutils.version import LooseVersion import warnings +import logging import pytest from selenium import __version__ as SELENIUM_VERSION @@ -10,61 +11,70 @@ from selenium.webdriver.firefox.firefox_binary import FirefoxBinary from selenium.webdriver.firefox.options import Options +LOGGER = logging.getLogger(__name__) + def pytest_addoption(parser): - group = parser.getgroup('selenium', 'selenium') - group._addoption('--firefox-path', - metavar='path', - help='path to the firefox binary.') - group._addoption('--firefox-preference', - action='append', - default=[], - dest='firefox_preferences', - metavar=('name', 'value'), - nargs=2, - help='additional firefox preferences.') - group._addoption('--firefox-profile', - metavar='path', - help='path to the firefox profile.') - group._addoption('--firefox-extension', - action='append', - default=[], - dest='firefox_extensions', - metavar='path', - help='path to a firefox extension.') + group = parser.getgroup("selenium", "selenium") + group._addoption( + "--firefox-path", metavar="path", help="path to the firefox binary." + ) + group._addoption( + "--firefox-preference", + action="append", + default=[], + dest="firefox_preferences", + metavar=("name", "value"), + nargs=2, + help="additional firefox preferences.", + ) + group._addoption( + "--firefox-profile", metavar="path", help="path to the firefox profile." + ) + group._addoption( + "--firefox-extension", + action="append", + default=[], + dest="firefox_extensions", + metavar="path", + help="path to a firefox extension.", + ) def pytest_configure(config): config.addinivalue_line( - 'markers', "firefox_arguments(args): arguments to be passed to " + "markers", + "firefox_arguments(args): arguments to be passed to " "Firefox. This marker will be ignored for other browsers. For " - "example: firefox_arguments('-foreground')") + "example: firefox_arguments('-foreground')", + ) config.addinivalue_line( - 'markers', "firefox_preferences(dict): preferences to be passed to " + "markers", + "firefox_preferences(dict): preferences to be passed to " "Firefox. This marker will be ignored for other browsers. For " "example: firefox_preferences({'browser.startup.homepage': " - "'https://pytest.org/'})") + "'https://pytest.org/'})", + ) -def driver_kwargs(capabilities, driver_log, driver_path, firefox_options, - **kwargs): +def driver_kwargs(capabilities, driver_log, driver_path, firefox_options, **kwargs): # Selenium 3.14.0 deprecated log_path in favour of service_log_path - if LooseVersion(SELENIUM_VERSION) < LooseVersion('3.14.0'): - kwargs = {'log_path': driver_log} + if LooseVersion(SELENIUM_VERSION) < LooseVersion("3.14.0"): + kwargs = {"log_path": driver_log} else: - kwargs = {'service_log_path': driver_log} + kwargs = {"service_log_path": driver_log} if capabilities: - kwargs['capabilities'] = capabilities + kwargs["capabilities"] = capabilities if driver_path is not None: - kwargs['executable_path'] = driver_path + kwargs["executable_path"] = driver_path # Selenium 3.8.0 deprecated firefox_options in favour of options - if LooseVersion(SELENIUM_VERSION) < LooseVersion('3.8.0'): - kwargs['firefox_options'] = firefox_options + if LooseVersion(SELENIUM_VERSION) < LooseVersion("3.8.0"): + kwargs["firefox_options"] = firefox_options else: - kwargs['options'] = firefox_options + kwargs["options"] = firefox_options return kwargs @@ -78,67 +88,100 @@ def firefox_options(request, firefox_path, firefox_profile): if firefox_path is not None: options.binary = FirefoxBinary(firefox_path) - args = request.node.get_marker('firefox_arguments') - if args is not None: - for arg in args.args: - options.add_argument(arg) + for arg in get_arguments_from_markers(request.node): + options.add_argument(arg) - prefs = request.node.get_marker('firefox_preferences') - if prefs is not None: - for name, value in prefs.args[0].items(): - options.set_preference(name, value) + for name, value in get_preferences_from_markers(request.node).items(): + options.set_preference(name, value) return options -@pytest.fixture(scope='session') +def get_arguments_from_markers(node): + # get_marker is deprecated since pytest 3.6 + # https://docs.pytest.org/en/latest/mark.html#marker-revamp-and-iteration + try: + arguments = [] + [arguments.extend(m.args) for m in node.iter_markers("firefox_arguments")] + return arguments + except AttributeError: + arguments = node.get_marker("firefox_arguments") + # backwards-compat + # can be removed when minimum req pytest is 3.6 + return arguments.args if arguments else [] + + +def get_preferences_from_markers(node): + # get_marker is deprecated since pytest 3.6 + # https://docs.pytest.org/en/latest/mark.html#marker-revamp-and-iteration + try: + preferences = dict() + for mark in node.iter_markers("firefox_preferences"): + preferences.update(mark.args[0]) + return preferences + except AttributeError: + # backwards-compat + # can be removed when minimum req pytest is 3.6 + preferences = node.get_marker("firefox_preferences") + return preferences.args[0] if preferences else {} + + +@pytest.fixture(scope="session") def firefox_path(pytestconfig): - if pytestconfig.getoption('firefox_path'): + if pytestconfig.getoption("firefox_path"): warnings.warn( - '--firefox-path has been deprecated and will be removed in a ' - 'future release. Please make sure the Firefox binary is in the ' - 'default location, or the system path. If you want to specify a ' - 'binary path then use the firefox_options fixture to and set this ' - 'using firefox_options.binary.', DeprecationWarning) - return pytestconfig.getoption('firefox_path') + "--firefox-path has been deprecated and will be removed in a " + "future release. Please make sure the Firefox binary is in the " + "default location, or the system path. If you want to specify a " + "binary path then use the firefox_options fixture to and set this " + "using firefox_options.binary.", + DeprecationWarning, + ) + return pytestconfig.getoption("firefox_path") @pytest.fixture def firefox_profile(pytestconfig): profile = None - if pytestconfig.getoption('firefox_profile'): - profile = FirefoxProfile(pytestconfig.getoption('firefox_profile')) + if pytestconfig.getoption("firefox_profile"): + profile = FirefoxProfile(pytestconfig.getoption("firefox_profile")) warnings.warn( - '--firefox-profile has been deprecated and will be removed in ' - 'a future release. Please use the firefox_options fixture to ' - 'set a profile path or FirefoxProfile object using ' - 'firefox_options.profile.', DeprecationWarning) - if pytestconfig.getoption('firefox_preferences'): + "--firefox-profile has been deprecated and will be removed in " + "a future release. Please use the firefox_options fixture to " + "set a profile path or FirefoxProfile object using " + "firefox_options.profile.", + DeprecationWarning, + ) + if pytestconfig.getoption("firefox_preferences"): profile = profile or FirefoxProfile() warnings.warn( - '--firefox-preference has been deprecated and will be removed in ' - 'a future release. Please use the firefox_options fixture to set ' - 'preferences using firefox_options.set_preference. If you are ' - 'using Firefox 47 or earlier then you will need to create a ' - 'FirefoxProfile object with preferences and set this using ' - 'firefox_options.profile.', DeprecationWarning) - for preference in pytestconfig.getoption('firefox_preferences'): + "--firefox-preference has been deprecated and will be removed in " + "a future release. Please use the firefox_options fixture to set " + "preferences using firefox_options.set_preference. If you are " + "using Firefox 47 or earlier then you will need to create a " + "FirefoxProfile object with preferences and set this using " + "firefox_options.profile.", + DeprecationWarning, + ) + for preference in pytestconfig.getoption("firefox_preferences"): name, value = preference if value.isdigit(): # handle integer preferences value = int(value) - elif value.lower() in ['true', 'false']: + elif value.lower() in ["true", "false"]: # handle boolean preferences - value = value.lower() == 'true' + value = value.lower() == "true" profile.set_preference(name, value) profile.update_preferences() - if pytestconfig.getoption('firefox_extensions'): + if pytestconfig.getoption("firefox_extensions"): profile = profile or FirefoxProfile() warnings.warn( - '--firefox-extensions has been deprecated and will be removed in ' - 'a future release. Please use the firefox_options fixture to ' - 'create a FirefoxProfile object with extensions and set this ' - 'using firefox_options.profile.', DeprecationWarning) - for extension in pytestconfig.getoption('firefox_extensions'): + "--firefox-extensions has been deprecated and will be removed in " + "a future release. Please use the firefox_options fixture to " + "create a FirefoxProfile object with extensions and set this " + "using firefox_options.profile.", + DeprecationWarning, + ) + for extension in pytestconfig.getoption("firefox_extensions"): profile.add_extension(extension) return profile diff --git a/pytest_selenium/drivers/internet_explorer.py b/pytest_selenium/drivers/internet_explorer.py index 4b627870..adc23790 100644 --- a/pytest_selenium/drivers/internet_explorer.py +++ b/pytest_selenium/drivers/internet_explorer.py @@ -8,13 +8,13 @@ def driver_kwargs(capabilities, driver_log, driver_path, **kwargs): # Selenium 3.14.0 deprecated log_file in favour of service_log_path - if LooseVersion(SELENIUM_VERSION) < LooseVersion('3.14.0'): - kwargs = {'log_file': driver_log} + if LooseVersion(SELENIUM_VERSION) < LooseVersion("3.14.0"): + kwargs = {"log_file": driver_log} else: - kwargs = {'service_log_path': driver_log} + kwargs = {"service_log_path": driver_log} if capabilities: - kwargs['capabilities'] = capabilities + kwargs["capabilities"] = capabilities if driver_path is not None: - kwargs['executable_path'] = driver_path + kwargs["executable_path"] = driver_path return kwargs diff --git a/pytest_selenium/drivers/phantomjs.py b/pytest_selenium/drivers/phantomjs.py index 2bf51ca3..08c752ea 100644 --- a/pytest_selenium/drivers/phantomjs.py +++ b/pytest_selenium/drivers/phantomjs.py @@ -3,12 +3,10 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. -def driver_kwargs(capabilities, driver_args, driver_log, driver_path, - **kwargs): - kwargs = {'desired_capabilities': capabilities, - 'service_log_path': driver_log} +def driver_kwargs(capabilities, driver_args, driver_log, driver_path, **kwargs): + kwargs = {"desired_capabilities": capabilities, "service_log_path": driver_log} if driver_args is not None: - kwargs['service_args'] = driver_args + kwargs["service_args"] = driver_args if driver_path is not None: - kwargs['executable_path'] = driver_path + kwargs["executable_path"] = driver_path return kwargs diff --git a/pytest_selenium/drivers/remote.py b/pytest_selenium/drivers/remote.py index dd40245a..78179eb9 100644 --- a/pytest_selenium/drivers/remote.py +++ b/pytest_selenium/drivers/remote.py @@ -4,15 +4,16 @@ import os -HOST = os.environ.get('SELENIUM_HOST', 'localhost') -PORT = os.environ.get('SELENIUM_PORT', 4444) +HOST = os.environ.get("SELENIUM_HOST", "localhost") +PORT = os.environ.get("SELENIUM_PORT", 4444) def driver_kwargs(capabilities, firefox_profile, host, port, **kwargs): - executor = 'http://{0}:{1}/wd/hub'.format(host, port) + executor = "http://{0}:{1}/wd/hub".format(host, port) kwargs = { - 'command_executor': executor, - 'desired_capabilities': capabilities, - 'browser_profile': firefox_profile} + "command_executor": executor, + "desired_capabilities": capabilities, + "browser_profile": firefox_profile, + } return kwargs diff --git a/pytest_selenium/drivers/safari.py b/pytest_selenium/drivers/safari.py index 6ac3d764..2b96653e 100644 --- a/pytest_selenium/drivers/safari.py +++ b/pytest_selenium/drivers/safari.py @@ -4,7 +4,7 @@ def driver_kwargs(capabilities, driver_path, **kwargs): - kwargs = {'desired_capabilities': capabilities} + kwargs = {"desired_capabilities": capabilities} if driver_path is not None: - kwargs['executable_path'] = driver_path + kwargs["executable_path"] = driver_path return kwargs diff --git a/pytest_selenium/drivers/saucelabs.py b/pytest_selenium/drivers/saucelabs.py index 6fa16082..90d31ea2 100644 --- a/pytest_selenium/drivers/saucelabs.py +++ b/pytest_selenium/drivers/saucelabs.py @@ -15,8 +15,8 @@ class SauceLabs(Provider): - API = 'https://saucelabs.com/rest/v1/{username}/jobs/{session}' - JOB = 'https://saucelabs.com/jobs/{session}' + API = "https://saucelabs.com/rest/v1/{username}/jobs/{session}" + JOB = "https://saucelabs.com/jobs/{session}" @property def auth(self): @@ -24,19 +24,19 @@ def auth(self): @property def executor(self): - return 'https://ondemand.saucelabs.com/wd/hub' + return "https://ondemand.saucelabs.com/wd/hub" @property def username(self): - return self.get_credential('username', ['SAUCELABS_USERNAME', - 'SAUCELABS_USR', - 'SAUCE_USERNAME']) + return self.get_credential( + "username", ["SAUCELABS_USERNAME", "SAUCELABS_USR", "SAUCE_USERNAME"] + ) @property def key(self): - return self.get_credential('key', ['SAUCELABS_API_KEY', - 'SAUCELABS_PSW', - 'SAUCE_ACCESS_KEY']) + return self.get_credential( + "key", ["SAUCELABS_API_KEY", "SAUCELABS_PSW", "SAUCE_ACCESS_KEY"] + ) def uses_driver(self, driver): return driver.lower() == self.name.lower() @@ -45,44 +45,42 @@ def uses_driver(self, driver): @pytest.mark.optionalhook def pytest_selenium_capture_debug(item, report, extra): provider = SauceLabs() - if not provider.uses_driver(item.config.getoption('driver')): + if not provider.uses_driver(item.config.getoption("driver")): return - pytest_html = item.config.pluginmanager.getplugin('html') + pytest_html = item.config.pluginmanager.getplugin("html") extra.append(pytest_html.extras.html(_video_html(item._driver.session_id))) @pytest.mark.optionalhook def pytest_selenium_runtest_makereport(item, report, summary, extra): provider = SauceLabs() - if not provider.uses_driver(item.config.getoption('driver')): + if not provider.uses_driver(item.config.getoption("driver")): return - passed = report.passed or (report.failed and hasattr(report, 'wasxfail')) + passed = report.passed or (report.failed and hasattr(report, "wasxfail")) session_id = item._driver.session_id # Add the job URL to the summary provider = SauceLabs() job_url = get_job_url(item.config, provider, session_id) - summary.append('{0} Job: {1}'.format(provider.name, job_url)) - pytest_html = item.config.pluginmanager.getplugin('html') + summary.append("{0} Job: {1}".format(provider.name, job_url)) + pytest_html = item.config.pluginmanager.getplugin("html") # Add the job URL to the HTML report - extra.append(pytest_html.extras.url(job_url, '{0} Job'.format( - provider.name))) + extra.append(pytest_html.extras.url(job_url, "{0} Job".format(provider.name))) try: # Update the job result - api_url = provider.API.format( - session=session_id, - username=provider.username) + api_url = provider.API.format(session=session_id, username=provider.username) job_info = requests.get(api_url, auth=provider.auth, timeout=10).json() - if report.when == 'setup' or job_info.get('passed') is not False: + if report.when == "setup" or job_info.get("passed") is not False: # Only update the result if it's not already marked as failed - data = json.dumps({'passed': passed}) + data = json.dumps({"passed": passed}) requests.put(api_url, data=data, auth=provider.auth, timeout=10) except Exception as e: - summary.append('WARNING: Failed to update {0} job status: {1}'.format( - provider.name, e)) + summary.append( + "WARNING: Failed to update {0} job status: {1}".format(provider.name, e) + ) def driver_kwargs(request, test, capabilities, **kwargs): @@ -91,19 +89,20 @@ def driver_kwargs(request, test, capabilities, **kwargs): markers = [m for m in keywords.keys() if isinstance(keywords[m], MarkInfo)] _capabilities = capabilities - if os.getenv('SAUCELABS_W3C') == 'true': - _capabilities = capabilities.setdefault('sauce:options', {}) + if os.getenv("SAUCELABS_W3C") == "true": + _capabilities = capabilities.setdefault("sauce:options", {}) - _capabilities.setdefault('username', provider.username) - _capabilities.setdefault('accessKey', provider.key) - _capabilities.setdefault('name', test) - tags = _capabilities.get('tags', []) + markers + _capabilities.setdefault("username", provider.username) + _capabilities.setdefault("accessKey", provider.key) + _capabilities.setdefault("name", test) + tags = _capabilities.get("tags", []) + markers if tags: - _capabilities['tags'] = tags + _capabilities["tags"] = tags kwargs = { - 'command_executor': provider.executor, - 'desired_capabilities': capabilities} + "command_executor": provider.executor, + "desired_capabilities": capabilities, + } return kwargs @@ -132,43 +131,48 @@ def _video_html(session): "url":"https://assets.saucelabs.com/jobs/{session}/video.flv",\ "provider":"streamer",\ "autoPlay":false,\ - "autoBuffering":true}}]}}'.format(session=session) - - return str(html.div(html.object( - html.param(value='true', name='allowfullscreen'), - html.param(value='always', name='allowscriptaccess'), - html.param(value='high', name='quality'), - html.param(value='#000000', name='bgcolor'), - html.param( - value=flash_vars.replace(' ', ''), - name='flashvars'), - width='100%', - height='100%', - type='application/x-shockwave-flash', - data='https://cdn1.saucelabs.com/sauce_skin_deprecated/lib/' - 'flowplayer/flowplayer-3.2.17.swf', - name='player_api', - id='player_api'), - id='player{session}'.format(session=session), - style='border:1px solid #e6e6e6; float:right; height:240px;' - 'margin-left:5px; overflow:hidden; width:320px')) + "autoBuffering":true}}]}}'.format( + session=session + ) + + return str( + html.div( + html.object( + html.param(value="true", name="allowfullscreen"), + html.param(value="always", name="allowscriptaccess"), + html.param(value="high", name="quality"), + html.param(value="#000000", name="bgcolor"), + html.param(value=flash_vars.replace(" ", ""), name="flashvars"), + width="100%", + height="100%", + type="application/x-shockwave-flash", + data="https://cdn1.saucelabs.com/sauce_skin_deprecated/lib/" + "flowplayer/flowplayer-3.2.17.swf", + name="player_api", + id="player_api", + ), + id="player{session}".format(session=session), + style="border:1px solid #e6e6e6; float:right; height:240px;" + "margin-left:5px; overflow:hidden; width:320px", + ) + ) def get_job_url(config, provider, session_id): from datetime import datetime job_url = provider.JOB.format(session=session_id) - job_auth = config.getini('saucelabs_job_auth').lower() + job_auth = config.getini("saucelabs_job_auth").lower() - if job_auth == 'none': + if job_auth == "none": return job_url - if job_auth == 'token': + if job_auth == "token": return get_auth_url(job_url, provider, session_id) - elif job_auth == 'hour': - time_format = '%Y-%m-%d-%H' - elif job_auth == 'day': - time_format = '%Y-%m-%d' + elif job_auth == "hour": + time_format = "%Y-%m-%d-%H" + elif job_auth == "day": + time_format = "%Y-%m-%d" else: raise ValueError("Invalid authorization type: {}".format(job_auth)) @@ -180,10 +184,8 @@ def get_auth_url(url, provider, session_id, ttl=None): import hmac from hashlib import md5 - key = '{0.username}:{0.key}'.format(provider) + key = "{0.username}:{0.key}".format(provider) if ttl: - key += ':{}'.format(ttl) - token = hmac.new(key.encode('utf-8'), - session_id.encode('utf-8'), - md5).hexdigest() - return '{}?auth={}'.format(url, token) + key += ":{}".format(ttl) + token = hmac.new(key.encode("utf-8"), session_id.encode("utf-8"), md5).hexdigest() + return "{}?auth={}".format(url, token) diff --git a/pytest_selenium/drivers/testingbot.py b/pytest_selenium/drivers/testingbot.py index d510d200..5195bf0c 100644 --- a/pytest_selenium/drivers/testingbot.py +++ b/pytest_selenium/drivers/testingbot.py @@ -9,14 +9,14 @@ from pytest_selenium.drivers.cloud import Provider -HOST = 'hub.testingbot.com' +HOST = "hub.testingbot.com" PORT = 80 class TestingBot(Provider): - API = 'https://api.testingbot.com/v1/tests/{session}' - JOB = 'http://testingbot.com/members/tests/{session}' + API = "https://api.testingbot.com/v1/tests/{session}" + JOB = "http://testingbot.com/members/tests/{session}" def __init__(self, host=None, port=None): super(TestingBot, self).__init__() @@ -29,71 +29,69 @@ def auth(self): @property def executor(self): - return 'http://{0.key}:{0.secret}@{0.host}:{0.port}/wd/hub'.format( - self) + return "http://{0.key}:{0.secret}@{0.host}:{0.port}/wd/hub".format(self) @property def key(self): - return self.get_credential('key', ['TESTINGBOT_KEY', - 'TESTINGBOT_USR']) + return self.get_credential("key", ["TESTINGBOT_KEY", "TESTINGBOT_USR"]) @property def secret(self): - return self.get_credential('secret', ['TESTINGBOT_SECRET', - 'TESTINGBOT_PSW']) + return self.get_credential("secret", ["TESTINGBOT_SECRET", "TESTINGBOT_PSW"]) @pytest.mark.optionalhook def pytest_selenium_capture_debug(item, report, extra): provider = TestingBot() - if not provider.uses_driver(item.config.getoption('driver')): + if not provider.uses_driver(item.config.getoption("driver")): return - pytest_html = item.config.pluginmanager.getplugin('html') + pytest_html = item.config.pluginmanager.getplugin("html") extra.append(pytest_html.extras.html(_video_html(item._driver.session_id))) @pytest.mark.optionalhook def pytest_selenium_runtest_makereport(item, report, summary, extra): provider = TestingBot() - if not provider.uses_driver(item.config.getoption('driver')): + if not provider.uses_driver(item.config.getoption("driver")): return - passed = report.passed or (report.failed and hasattr(report, 'wasxfail')) + passed = report.passed or (report.failed and hasattr(report, "wasxfail")) session_id = item._driver.session_id # Add the job URL to the summary job_url = provider.JOB.format(session=session_id) - summary.append('{0} Job: {1}'.format(provider.name, job_url)) - pytest_html = item.config.pluginmanager.getplugin('html') + summary.append("{0} Job: {1}".format(provider.name, job_url)) + pytest_html = item.config.pluginmanager.getplugin("html") # Add the job URL to the HTML report - extra.append(pytest_html.extras.url(job_url, '{0} Job'.format( - provider.name))) + extra.append(pytest_html.extras.url(job_url, "{0} Job".format(provider.name))) try: # Update the job result api_url = provider.API.format(session=session_id) job_info = requests.get(api_url, auth=provider.auth, timeout=10).json() - if report.when == 'setup' or job_info.get('success') is not False: + if report.when == "setup" or job_info.get("success") is not False: # Only update the result if it's not already marked as failed - data = {'test[success]': '1' if passed else '0'} + data = {"test[success]": "1" if passed else "0"} requests.put(api_url, data=data, auth=provider.auth, timeout=10) except Exception as e: - summary.append('WARNING: Failed to update {0} job status: {1}'.format( - provider.name, e)) + summary.append( + "WARNING: Failed to update {0} job status: {1}".format(provider.name, e) + ) def driver_kwargs(request, test, capabilities, host, port, **kwargs): provider = TestingBot(host, port) keywords = request.node.keywords - capabilities.setdefault('name', test) + capabilities.setdefault("name", test) markers = [m for m in keywords.keys() if isinstance(keywords[m], MarkInfo)] - groups = capabilities.get('groups', []) + markers + groups = capabilities.get("groups", []) + markers if groups: - capabilities['groups'] = groups + capabilities["groups"] = groups kwargs = { - 'command_executor': provider.executor, - 'desired_capabilities': capabilities} + "command_executor": provider.executor, + "desired_capabilities": capabilities, + } return kwargs @@ -116,23 +114,28 @@ def _video_html(session): "playerId":"mediaplayer{session}",\ "playlist":[{{\ "url":"{session}",\ - "provider":"rtmp"}}]}}'.format(session=session) - - return str(html.div(html.object( - html.param(value='true', name='allowfullscreen'), - html.param(value='always', name='allowscriptaccess'), - html.param(value='high', name='quality'), - html.param(value='#000000', name='bgcolor'), - html.param(value='opaque', name='wmode'), - html.param( - value=flash_vars.replace(' ', ''), - name='flashvars'), - width='100%', - height='100%', - type='application/x-shockwave-flash', - data='http://testingbot.com/assets/flowplayer-3.2.14.swf', - name='mediaplayer_api', - id='mediaplayer_api'), - id='mediaplayer{session}'.format(session=session), - style='border:1px solid #e6e6e6; float:right; height:240px;' - 'margin-left:5px; overflow:hidden; width:320px')) + "provider":"rtmp"}}]}}'.format( + session=session + ) + + return str( + html.div( + html.object( + html.param(value="true", name="allowfullscreen"), + html.param(value="always", name="allowscriptaccess"), + html.param(value="high", name="quality"), + html.param(value="#000000", name="bgcolor"), + html.param(value="opaque", name="wmode"), + html.param(value=flash_vars.replace(" ", ""), name="flashvars"), + width="100%", + height="100%", + type="application/x-shockwave-flash", + data="http://testingbot.com/assets/flowplayer-3.2.14.swf", + name="mediaplayer_api", + id="mediaplayer_api", + ), + id="mediaplayer{session}".format(session=session), + style="border:1px solid #e6e6e6; float:right; height:240px;" + "margin-left:5px; overflow:hidden; width:320px", + ) + ) diff --git a/pytest_selenium/exceptions.py b/pytest_selenium/exceptions.py index 77836c32..db853770 100644 --- a/pytest_selenium/exceptions.py +++ b/pytest_selenium/exceptions.py @@ -6,9 +6,9 @@ class MissingCloudCredentialError(pytest.UsageError): - def __init__(self, driver, key, envs): super(MissingCloudCredentialError, self).__init__( - '{0} {1} must be set. Try setting one of the following ' - 'environment variables {2}, or see the documentation for ' - 'how to use a configuration file.'.format(driver, key, envs)) + "{0} {1} must be set. Try setting one of the following " + "environment variables {2}, or see the documentation for " + "how to use a configuration file.".format(driver, key, envs) + ) diff --git a/pytest_selenium/pytest_selenium.py b/pytest_selenium/pytest_selenium.py index 4074881b..fd8bae6d 100644 --- a/pytest_selenium/pytest_selenium.py +++ b/pytest_selenium/pytest_selenium.py @@ -13,26 +13,28 @@ from requests.structures import CaseInsensitiveDict from selenium import webdriver from selenium.webdriver.common.desired_capabilities import DesiredCapabilities -from selenium.webdriver.support.event_firing_webdriver import \ - EventFiringWebDriver +from selenium.webdriver.support.event_firing_webdriver import EventFiringWebDriver from . import drivers LOGGER = logging.getLogger(__name__) -SUPPORTED_DRIVERS = CaseInsensitiveDict({ - 'BrowserStack': webdriver.Remote, - 'CrossBrowserTesting': webdriver.Remote, - 'Chrome': webdriver.Chrome, - 'Edge': webdriver.Edge, - 'Firefox': webdriver.Firefox, - 'IE': webdriver.Ie, - 'PhantomJS': webdriver.PhantomJS, - 'Remote': webdriver.Remote, - 'Safari': webdriver.Safari, - 'SauceLabs': webdriver.Remote, - 'TestingBot': webdriver.Remote}) +SUPPORTED_DRIVERS = CaseInsensitiveDict( + { + "BrowserStack": webdriver.Remote, + "CrossBrowserTesting": webdriver.Remote, + "Chrome": webdriver.Chrome, + "Edge": webdriver.Edge, + "Firefox": webdriver.Firefox, + "IE": webdriver.Ie, + "PhantomJS": webdriver.PhantomJS, + "Remote": webdriver.Remote, + "Safari": webdriver.Safari, + "SauceLabs": webdriver.Remote, + "TestingBot": webdriver.Remote, + } +) def _merge(a, b): @@ -60,43 +62,44 @@ def _merge(a, b): def pytest_addhooks(pluginmanager): from . import hooks - method = getattr(pluginmanager, 'add_hookspecs', None) + + method = getattr(pluginmanager, "add_hookspecs", None) if method is None: method = pluginmanager.addhooks method(hooks) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def session_capabilities(pytestconfig): """Returns combined capabilities from pytest-variables and command line""" - driver = pytestconfig.getoption('driver').upper() + driver = pytestconfig.getoption("driver").upper() capabilities = getattr(DesiredCapabilities, driver, {}).copy() - if driver == 'REMOTE': - browser = capabilities.get('browserName', '').upper() + if driver == "REMOTE": + browser = capabilities.get("browserName", "").upper() capabilities.update(getattr(DesiredCapabilities, browser, {})) capabilities.update(pytestconfig._capabilities) return capabilities @pytest.fixture -def capabilities(request, driver_class, chrome_options, firefox_options, - session_capabilities): +def capabilities( + request, driver_class, chrome_options, firefox_options, session_capabilities +): """Returns combined capabilities""" capabilities = copy.deepcopy(session_capabilities) # make a copy if driver_class == webdriver.Remote: - browser = capabilities.get('browserName', '').upper() + browser = capabilities.get("browserName", "").upper() key, options = (None, None) - if browser == 'CHROME': - key = getattr(chrome_options, 'KEY', 'goog:chromeOptions') + if browser == "CHROME": + key = getattr(chrome_options, "KEY", "goog:chromeOptions") options = chrome_options.to_capabilities() if key not in options: - key = 'chromeOptions' - elif browser == 'FIREFOX': + key = "chromeOptions" + elif browser == "FIREFOX": key = firefox_options.KEY options = firefox_options.to_capabilities() if all([key, options]): - capabilities[key] = _merge( - capabilities.get(key, {}), options.get(key, {})) + capabilities[key] = _merge(capabilities.get(key, {}), options.get(key, {})) capabilities.update(get_capabilities_from_markers(request.node)) return capabilities @@ -106,17 +109,18 @@ def get_capabilities_from_markers(node): # https://docs.pytest.org/en/latest/mark.html#marker-revamp-and-iteration try: capabilities = dict() - for level, mark in node.iter_markers_with_node('capabilities'): - LOGGER.debug('{0} marker <{1.name}> ' - 'contained kwargs <{1.kwargs}>'. - format(level.__class__.__name__, mark)) + for level, mark in node.iter_markers_with_node("capabilities"): + LOGGER.debug( + "{0} marker <{1.name}> " + "contained kwargs <{1.kwargs}>".format(level.__class__.__name__, mark) + ) capabilities.update(mark.kwargs) - LOGGER.info('Capabilities from markers: {}'.format(capabilities)) + LOGGER.info("Capabilities from markers: {}".format(capabilities)) return capabilities except AttributeError: # backwards-compat # can be removed when minimum req pytest is 3.6 - capabilities = node.get_marker('capabilities') + capabilities = node.get_marker("capabilities") return capabilities.kwargs if capabilities else {} @@ -127,45 +131,57 @@ def driver_args(): @pytest.fixture -def driver_kwargs(request, capabilities, chrome_options, driver_args, - driver_class, driver_log, driver_path, firefox_options, - firefox_profile, pytestconfig): +def driver_kwargs( + request, + capabilities, + chrome_options, + driver_args, + driver_class, + driver_log, + driver_path, + firefox_options, + firefox_profile, + pytestconfig, +): kwargs = {} - driver = getattr(drivers, pytestconfig.getoption('driver').lower()) - kwargs.update(driver.driver_kwargs( - capabilities=capabilities, - chrome_options=chrome_options, - driver_args=driver_args, - driver_log=driver_log, - driver_path=driver_path, - firefox_options=firefox_options, - firefox_profile=firefox_profile, - host=pytestconfig.getoption('host'), - port=pytestconfig.getoption('port'), - service_log_path=None, - request=request, - test='.'.join(split_class_and_test_names(request.node.nodeid)))) + driver = getattr(drivers, pytestconfig.getoption("driver").lower()) + kwargs.update( + driver.driver_kwargs( + capabilities=capabilities, + chrome_options=chrome_options, + driver_args=driver_args, + driver_log=driver_log, + driver_path=driver_path, + firefox_options=firefox_options, + firefox_profile=firefox_profile, + host=pytestconfig.getoption("host"), + port=pytestconfig.getoption("port"), + service_log_path=None, + request=request, + test=".".join(split_class_and_test_names(request.node.nodeid)), + ) + ) pytestconfig._driver_log = driver_log return kwargs -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def driver_class(request): - driver = request.config.getoption('driver') + driver = request.config.getoption("driver") if driver is None: - raise pytest.UsageError('--driver must be specified') + raise pytest.UsageError("--driver must be specified") return SUPPORTED_DRIVERS[driver] @pytest.fixture def driver_log(tmpdir): """Return path to driver log""" - return str(tmpdir.join('driver.log')) + return str(tmpdir.join("driver.log")) @pytest.fixture def driver_path(request): - return request.config.getoption('driver_path') + return request.config.getoption("driver_path") @pytest.yield_fixture @@ -173,10 +189,10 @@ def driver(request, driver_class, driver_kwargs): """Returns a WebDriver instance based on options and capabilities""" driver = driver_class(**driver_kwargs) - event_listener = request.config.getoption('event_listener') + event_listener = request.config.getoption("event_listener") if event_listener is not None: # Import the specified event listener and wrap the driver instance - mod_name, class_name = event_listener.rsplit('.', 1) + mod_name, class_name = event_listener.rsplit(".", 1) mod = __import__(mod_name, fromlist=[class_name]) event_listener = getattr(mod, class_name) if not isinstance(driver, EventFiringWebDriver): @@ -194,26 +210,30 @@ def selenium(driver): @pytest.hookimpl(trylast=True) def pytest_configure(config): - capabilities = config._variables.get('capabilities', {}) - capabilities.update({k: v for k, v in config.getoption('capabilities')}) + capabilities = config._variables.get("capabilities", {}) + capabilities.update({k: v for k, v in config.getoption("capabilities")}) config.addinivalue_line( - 'markers', 'capabilities(kwargs): add or change existing ' - 'capabilities. specify capabilities as keyword arguments, for example ' - 'capabilities(foo=''bar'')') - if hasattr(config, '_metadata'): - config._metadata['Driver'] = config.getoption('driver') - config._metadata['Capabilities'] = capabilities - if all((config.getoption('host'), config.getoption('port'))): - config._metadata['Server'] = '{0}:{1}'.format( - config.getoption('host'), - config.getoption('port')) + "markers", + "capabilities(kwargs): add or change existing " + "capabilities. specify capabilities as keyword arguments, for example " + "capabilities(foo=" + "bar" + ")", + ) + if hasattr(config, "_metadata"): + config._metadata["Driver"] = config.getoption("driver") + config._metadata["Capabilities"] = capabilities + if all((config.getoption("host"), config.getoption("port"))): + config._metadata["Server"] = "{0}:{1}".format( + config.getoption("host"), config.getoption("port") + ) config._capabilities = capabilities def pytest_report_header(config, startdir): - driver = config.getoption('driver') + driver = config.getoption("driver") if driver is not None: - return 'driver: {0}'.format(driver) + return "driver: {0}".format(driver) @pytest.mark.hookwrapper @@ -221,36 +241,38 @@ def pytest_runtest_makereport(item, call): outcome = yield report = outcome.get_result() summary = [] - extra = getattr(report, 'extra', []) - driver = getattr(item, '_driver', None) - xfail = hasattr(report, 'wasxfail') + extra = getattr(report, "extra", []) + driver = getattr(item, "_driver", None) + xfail = hasattr(report, "wasxfail") failure = (report.skipped and xfail) or (report.failed and not xfail) - when = item.config.getini('selenium_capture_debug').lower() - capture_debug = when == 'always' or (when == 'failure' and failure) + when = item.config.getini("selenium_capture_debug").lower() + capture_debug = when == "always" or (when == "failure" and failure) if capture_debug: - exclude = item.config.getini('selenium_exclude_debug').lower() - if 'logs' not in exclude: + exclude = item.config.getini("selenium_exclude_debug").lower() + if "logs" not in exclude: # gather logs that do not depend on a driver instance _gather_driver_log(item, summary, extra) if driver is not None: # gather debug that depends on a driver instance - if 'url' not in exclude: + if "url" not in exclude: _gather_url(item, report, driver, summary, extra) - if 'screenshot' not in exclude: + if "screenshot" not in exclude: _gather_screenshot(item, report, driver, summary, extra) - if 'html' not in exclude: + if "html" not in exclude: _gather_html(item, report, driver, summary, extra) - if 'logs' not in exclude: + if "logs" not in exclude: _gather_logs(item, report, driver, summary, extra) # gather debug from hook implementations item.config.hook.pytest_selenium_capture_debug( - item=item, report=report, extra=extra) + item=item, report=report, extra=extra + ) if driver is not None: # allow hook implementations to further modify the report item.config.hook.pytest_selenium_runtest_makereport( - item=item, report=report, summary=summary, extra=extra) + item=item, report=report, summary=summary, extra=extra + ) if summary: - report.sections.append(('pytest-selenium', '\n'.join(summary))) + report.sections.append(("pytest-selenium", "\n".join(summary))) report.extra = extra @@ -258,142 +280,161 @@ def _gather_url(item, report, driver, summary, extra): try: url = driver.current_url except Exception as e: - summary.append('WARNING: Failed to gather URL: {0}'.format(e)) + summary.append("WARNING: Failed to gather URL: {0}".format(e)) return - pytest_html = item.config.pluginmanager.getplugin('html') + pytest_html = item.config.pluginmanager.getplugin("html") if pytest_html is not None: # add url to the html report extra.append(pytest_html.extras.url(url)) - summary.append('URL: {0}'.format(url)) + summary.append("URL: {0}".format(url)) def _gather_screenshot(item, report, driver, summary, extra): try: screenshot = driver.get_screenshot_as_base64() except Exception as e: - summary.append('WARNING: Failed to gather screenshot: {0}'.format(e)) + summary.append("WARNING: Failed to gather screenshot: {0}".format(e)) return - pytest_html = item.config.pluginmanager.getplugin('html') + pytest_html = item.config.pluginmanager.getplugin("html") if pytest_html is not None: # add screenshot to the html report - extra.append(pytest_html.extras.image(screenshot, 'Screenshot')) + extra.append(pytest_html.extras.image(screenshot, "Screenshot")) def _gather_html(item, report, driver, summary, extra): try: html = driver.page_source except Exception as e: - summary.append('WARNING: Failed to gather HTML: {0}'.format(e)) + summary.append("WARNING: Failed to gather HTML: {0}".format(e)) return - pytest_html = item.config.pluginmanager.getplugin('html') + pytest_html = item.config.pluginmanager.getplugin("html") if pytest_html is not None: # add page source to the html report - extra.append(pytest_html.extras.text(html, 'HTML')) + extra.append(pytest_html.extras.text(html, "HTML")) def _gather_logs(item, report, driver, summary, extra): - pytest_html = item.config.pluginmanager.getplugin('html') + pytest_html = item.config.pluginmanager.getplugin("html") try: types = driver.log_types except Exception as e: # note that some drivers may not implement log types - summary.append('WARNING: Failed to gather log types: {0}'.format(e)) + summary.append("WARNING: Failed to gather log types: {0}".format(e)) return for name in types: try: log = driver.get_log(name) except Exception as e: - summary.append('WARNING: Failed to gather {0} log: {1}'.format( - name, e)) + summary.append("WARNING: Failed to gather {0} log: {1}".format(name, e)) return if pytest_html is not None: - extra.append(pytest_html.extras.text( - format_log(log), '%s Log' % name.title())) + extra.append( + pytest_html.extras.text(format_log(log), "%s Log" % name.title()) + ) def _gather_driver_log(item, summary, extra): - pytest_html = item.config.pluginmanager.getplugin('html') - if hasattr(item.config, '_driver_log') and \ - os.path.exists(item.config._driver_log): + pytest_html = item.config.pluginmanager.getplugin("html") + if hasattr(item.config, "_driver_log") and os.path.exists(item.config._driver_log): if pytest_html is not None: - with io.open(item.config._driver_log, 'r', encoding='utf8') as f: - extra.append(pytest_html.extras.text(f.read(), 'Driver Log')) - summary.append('Driver log: {0}'.format(item.config._driver_log)) + with io.open(item.config._driver_log, "r", encoding="utf8") as f: + extra.append(pytest_html.extras.text(f.read(), "Driver Log")) + summary.append("Driver log: {0}".format(item.config._driver_log)) def format_log(log): - timestamp_format = '%Y-%m-%d %H:%M:%S.%f' - entries = [u'{0} {1[level]} - {1[message]}'.format( - datetime.utcfromtimestamp(entry['timestamp'] / 1000.0).strftime( - timestamp_format), entry).rstrip() for entry in log] - log = '\n'.join(entries) + timestamp_format = "%Y-%m-%d %H:%M:%S.%f" + entries = [ + u"{0} {1[level]} - {1[message]}".format( + datetime.utcfromtimestamp(entry["timestamp"] / 1000.0).strftime( + timestamp_format + ), + entry, + ).rstrip() + for entry in log + ] + log = "\n".join(entries) return log def split_class_and_test_names(nodeid): """Returns the class and method name from the current test""" - names = nodeid.split('::') - names[0] = names[0].replace('/', '.') - names = [x.replace('.py', '') for x in names if x != '()'] + names = nodeid.split("::") + names[0] = names[0].replace("/", ".") + names = [x.replace(".py", "") for x in names if x != "()"] classnames = names[:-1] - classname = '.'.join(classnames) + classname = ".".join(classnames) name = names[-1] return classname, name class DriverAction(argparse.Action): - def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, values) driver = getattr(drivers, values.lower()) # set the default host and port if specified in the driver module - namespace.host = namespace.host or getattr(driver, 'HOST', None) - namespace.port = namespace.port or getattr(driver, 'PORT', None) + namespace.host = namespace.host or getattr(driver, "HOST", None) + namespace.port = namespace.port or getattr(driver, "PORT", None) def pytest_addoption(parser): - _capture_choices = ('never', 'failure', 'always') - parser.addini('selenium_capture_debug', - help='when debug is captured {0}'.format(_capture_choices), - default=os.getenv('SELENIUM_CAPTURE_DEBUG', 'failure')) - parser.addini('selenium_exclude_debug', - help='debug to exclude from capture', - default=os.getenv('SELENIUM_EXCLUDE_DEBUG')) - - _auth_choices = ('none', 'token', 'hour', 'day') - parser.addini('saucelabs_job_auth', - help='Authorization options for the Sauce Labs job: {0}'. - format(_auth_choices), - default=os.getenv('SAUCELABS_JOB_AUTH', 'none')) - - group = parser.getgroup('selenium', 'selenium') - group._addoption('--driver', - action=DriverAction, - choices=SUPPORTED_DRIVERS, - help='webdriver implementation.', - metavar='str') - group._addoption('--driver-path', - metavar='path', - help='path to the driver executable.') - group._addoption('--capability', - action='append', - default=[], - dest='capabilities', - metavar=('key', 'value'), - nargs=2, - help='additional capabilities.') - group._addoption('--event-listener', - metavar='str', - help='selenium eventlistener class, e.g. ' - 'package.module.EventListenerClassName.') - group._addoption('--host', - metavar='str', - help='host that the selenium server is listening on, ' - 'which will default to the cloud provider default ' - 'or localhost.') - group._addoption('--port', - type=int, - metavar='num', - help='port that the selenium server is listening on, ' - 'which will default to the cloud provider default ' - 'or localhost.') + _capture_choices = ("never", "failure", "always") + parser.addini( + "selenium_capture_debug", + help="when debug is captured {0}".format(_capture_choices), + default=os.getenv("SELENIUM_CAPTURE_DEBUG", "failure"), + ) + parser.addini( + "selenium_exclude_debug", + help="debug to exclude from capture", + default=os.getenv("SELENIUM_EXCLUDE_DEBUG"), + ) + + _auth_choices = ("none", "token", "hour", "day") + parser.addini( + "saucelabs_job_auth", + help="Authorization options for the Sauce Labs job: {0}".format(_auth_choices), + default=os.getenv("SAUCELABS_JOB_AUTH", "none"), + ) + + group = parser.getgroup("selenium", "selenium") + group._addoption( + "--driver", + action=DriverAction, + choices=SUPPORTED_DRIVERS, + help="webdriver implementation.", + metavar="str", + ) + group._addoption( + "--driver-path", metavar="path", help="path to the driver executable." + ) + group._addoption( + "--capability", + action="append", + default=[], + dest="capabilities", + metavar=("key", "value"), + nargs=2, + help="additional capabilities.", + ) + group._addoption( + "--event-listener", + metavar="str", + help="selenium eventlistener class, e.g. " + "package.module.EventListenerClassName.", + ) + group._addoption( + "--host", + metavar="str", + help="host that the selenium server is listening on, " + "which will default to the cloud provider default " + "or localhost.", + ) + group._addoption( + "--port", + type=int, + metavar="num", + help="port that the selenium server is listening on, " + "which will default to the cloud provider default " + "or localhost.", + ) diff --git a/pytest_selenium/safety.py b/pytest_selenium/safety.py index c67c5da8..d4931b61 100644 --- a/pytest_selenium/safety.py +++ b/pytest_selenium/safety.py @@ -11,37 +11,44 @@ def pytest_addoption(parser): - parser.addini('sensitive_url', - help='regular expression for identifying sensitive urls.') + parser.addini( + "sensitive_url", help="regular expression for identifying sensitive urls." + ) - group = parser.getgroup('safety', 'safety') - group._addoption('--sensitive-url', - help='regular expression for identifying sensitive urls.') + group = parser.getgroup("safety", "safety") + group._addoption( + "--sensitive-url", help="regular expression for identifying sensitive urls." + ) def pytest_configure(config): - if hasattr(config, 'slaveinput'): + if hasattr(config, "slaveinput"): return # xdist slave - config.option.sensitive_url = config.getoption('sensitive_url') or \ - config.getini('sensitive_url') or \ - os.getenv('SENSITIVE_URL') or '.*' + config.option.sensitive_url = ( + config.getoption("sensitive_url") + or config.getini("sensitive_url") + or os.getenv("SENSITIVE_URL") + or ".*" + ) config.addinivalue_line( - 'markers', 'nondestructive: mark the test as nondestructive. ' - 'Tests are assumed to be destructive unless this marker is ' - 'present. This reduces the risk of running destructive tests ' - 'accidentally.') + "markers", + "nondestructive: mark the test as nondestructive. " + "Tests are assumed to be destructive unless this marker is " + "present. This reduces the risk of running destructive tests " + "accidentally.", + ) def pytest_report_header(config, startdir): - base_url = config.getoption('base_url') - sensitive_url = config.getoption('sensitive_url') - msg = 'sensitiveurl: {0}'.format(sensitive_url) + base_url = config.getoption("base_url") + sensitive_url = config.getoption("sensitive_url") + msg = "sensitiveurl: {0}".format(sensitive_url) if base_url and sensitive_url and re.search(sensitive_url, base_url): - msg += ' *** WARNING: sensitive url matches {} ***'.format(base_url) + msg += " *** WARNING: sensitive url matches {} ***".format(base_url) return msg -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def sensitive_url(request, base_url): """Return the first sensitive URL from response history of the base URL""" if not base_url: @@ -55,7 +62,7 @@ def sensitive_url(request, base_url): urls.extend([history.url for history in response.history]) except requests.exceptions.RequestException: pass # ignore exceptions if this URL is unreachable - search = partial(re.search, request.config.getoption('sensitive_url')) + search = partial(re.search, request.config.getoption("sensitive_url")) matches = list(map(search, urls)) if any(matches): # return the first match @@ -63,13 +70,14 @@ def sensitive_url(request, base_url): return first_match.string -@pytest.fixture(scope='function', autouse=True) +@pytest.fixture(scope="function", autouse=True) def _skip_sensitive(request, sensitive_url): """Skip destructive tests if the environment is considered sensitive""" - destructive = 'nondestructive' not in request.node.keywords + destructive = "nondestructive" not in request.node.keywords if sensitive_url and destructive: pytest.skip( - 'This test is destructive and the target URL is ' - 'considered a sensitive environment. If this test is ' - 'not destructive, add the \'nondestructive\' marker to ' - 'it. Sensitive URL: {0}'.format(sensitive_url)) + "This test is destructive and the target URL is " + "considered a sensitive environment. If this test is " + "not destructive, add the 'nondestructive' marker to " + "it. Sensitive URL: {0}".format(sensitive_url) + ) diff --git a/setup.py b/setup.py index 7af45dac..f5ce580e 100644 --- a/setup.py +++ b/setup.py @@ -1,52 +1,57 @@ from setuptools import setup -setup(name='pytest-selenium', - use_scm_version=True, - description='pytest plugin for Selenium', - long_description=open('README.rst').read(), - author='Dave Hunt', - author_email='dhunt@mozilla.com', - url='https://github.com/pytest-dev/pytest-selenium', - packages=['pytest_selenium', 'pytest_selenium.drivers'], - install_requires=[ - 'pytest>=3.0', - 'pytest-base-url', - 'pytest-html>=1.14.0', - 'pytest-variables>=1.5.0', - 'selenium>=3.0.0', - 'requests'], - entry_points={'pytest11': [ - 'selenium = pytest_selenium.pytest_selenium', - 'selenium_safety = pytest_selenium.safety', - 'browserstack_driver = pytest_selenium.drivers.browserstack', - 'crossbrowsertesting_driver = ' - 'pytest_selenium.drivers.crossbrowsertesting', - 'chrome_driver = pytest_selenium.drivers.chrome', - 'edge_driver = pytest_selenium.drivers.edge', - 'firefox_driver = pytest_selenium.drivers.firefox', - 'ie_driver = pytest_selenium.drivers.internet_explorer', - 'remote_driver = pytest_selenium.drivers.remote', - 'phantomjs_driver = pytest_selenium.drivers.phantomjs', - 'safari_driver = pytest_selenium.drivers.safari', - 'saucelabs_driver = pytest_selenium.drivers.saucelabs', - 'testingbot_driver = pytest_selenium.drivers.testingbot']}, - setup_requires=['setuptools_scm'], - license='Mozilla Public License 2.0 (MPL 2.0)', - keywords='py.test pytest selenium saucelabs browserstack webqa qa ' - 'mozilla', - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Framework :: Pytest', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)', - 'Operating System :: POSIX', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: MacOS :: MacOS X', - 'Topic :: Software Development :: Quality Assurance', - 'Topic :: Software Development :: Testing', - 'Topic :: Utilities', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - ]) +setup( + name="pytest-selenium", + use_scm_version=True, + description="pytest plugin for Selenium", + long_description=open("README.rst").read(), + author="Dave Hunt", + author_email="dhunt@mozilla.com", + url="https://github.com/pytest-dev/pytest-selenium", + packages=["pytest_selenium", "pytest_selenium.drivers"], + install_requires=[ + "pytest>=3.0", + "pytest-base-url", + "pytest-html>=1.14.0", + "pytest-variables>=1.5.0", + "selenium>=3.0.0", + "requests", + ], + entry_points={ + "pytest11": [ + "selenium = pytest_selenium.pytest_selenium", + "selenium_safety = pytest_selenium.safety", + "browserstack_driver = pytest_selenium.drivers.browserstack", + "crossbrowsertesting_driver = " + "pytest_selenium.drivers.crossbrowsertesting", + "chrome_driver = pytest_selenium.drivers.chrome", + "edge_driver = pytest_selenium.drivers.edge", + "firefox_driver = pytest_selenium.drivers.firefox", + "ie_driver = pytest_selenium.drivers.internet_explorer", + "remote_driver = pytest_selenium.drivers.remote", + "phantomjs_driver = pytest_selenium.drivers.phantomjs", + "safari_driver = pytest_selenium.drivers.safari", + "saucelabs_driver = pytest_selenium.drivers.saucelabs", + "testingbot_driver = pytest_selenium.drivers.testingbot", + ] + }, + setup_requires=["setuptools_scm"], + license="Mozilla Public License 2.0 (MPL 2.0)", + keywords="py.test pytest selenium saucelabs browserstack webqa qa " "mozilla", + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Framework :: Pytest", + "Intended Audience :: Developers", + "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", + "Operating System :: POSIX", + "Operating System :: Microsoft :: Windows", + "Operating System :: MacOS :: MacOS X", + "Topic :: Software Development :: Quality Assurance", + "Topic :: Software Development :: Testing", + "Topic :: Utilities", + "Programming Language :: Python", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + ], +) diff --git a/testing/conftest.py b/testing/conftest.py index dd95ad02..2ea0c79c 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -6,7 +6,7 @@ import pytest -pytest_plugins = 'pytester' +pytest_plugins = "pytester" def base_url(httpserver): @@ -15,49 +15,55 @@ def base_url(httpserver): @pytest.fixture def httpserver_base_url(httpserver): - return '--base-url={0}'.format(base_url(httpserver)) + return "--base-url={0}".format(base_url(httpserver)) @pytest.fixture(autouse=True) def testdir(request, httpserver_base_url): item = request.node - if 'testdir' not in item.funcargnames: + if "testdir" not in item.funcargnames: return - testdir = request.getfixturevalue('testdir') + testdir = request.getfixturevalue("testdir") - testdir.makepyfile(conftest=""" + testdir.makepyfile( + conftest=""" import pytest @pytest.fixture def webtext(base_url, selenium): selenium.get(base_url) return selenium.find_element_by_tag_name('h1').text - """) + """ + ) - testdir.makefile('.cfg', setup=""" + testdir.makefile( + ".cfg", + setup=""" [tool:pytest] filterwarnings = error::DeprecationWarning ignore:--firefox-\w+ has been deprecated:DeprecationWarning - ignore:MarkInfo:DeprecationWarning:pytest_selenium.drivers.firefox:88 - """) + """, + ) def runpytestqa(*args, **kwargs): - return testdir.runpytest(httpserver_base_url, '--driver', 'Firefox', - *args, **kwargs) + return testdir.runpytest( + httpserver_base_url, "--driver", "Firefox", *args, **kwargs + ) testdir.runpytestqa = runpytestqa def inline_runqa(*args, **kwargs): - return testdir.inline_run(httpserver_base_url, '--driver', 'Firefox', - *args, **kwargs) + return testdir.inline_run( + httpserver_base_url, "--driver", "Firefox", *args, **kwargs + ) testdir.inline_runqa = inline_runqa def quick_qa(*args, **kwargs): reprec = inline_runqa(*args) outcomes = reprec.listoutcomes() - names = ('passed', 'skipped', 'failed') + names = ("passed", "skipped", "failed") for name, val in zip(names, outcomes): wantlen = kwargs.get(name) if wantlen is not None: diff --git a/testing/test_browserstack.py b/testing/test_browserstack.py index dd0a4d41..7d68de20 100644 --- a/testing/test_browserstack.py +++ b/testing/test_browserstack.py @@ -12,11 +12,13 @@ @pytest.fixture def testfile(testdir): - return testdir.makepyfile(""" + return testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(selenium): pass - """) + """ + ) def failure_with_output(testdir, *args, **kwargs): @@ -29,43 +31,52 @@ def failure_with_output(testdir, *args, **kwargs): @pytest.fixture def failure(testdir, testfile, httpserver_base_url): - return partial(failure_with_output, testdir, testfile, httpserver_base_url, - '--driver', 'BrowserStack') + return partial( + failure_with_output, + testdir, + testfile, + httpserver_base_url, + "--driver", + "BrowserStack", + ) def test_missing_username(failure, monkeypatch, tmpdir): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - assert 'BrowserStack username must be set' in failure() + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + assert "BrowserStack username must be set" in failure() def test_missing_access_key_env(failure, monkeypatch, tmpdir): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - monkeypatch.setenv('BROWSERSTACK_USERNAME', 'foo') - assert 'BrowserStack key must be set' in failure() + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + monkeypatch.setenv("BROWSERSTACK_USERNAME", "foo") + assert "BrowserStack key must be set" in failure() def test_missing_access_key_file(failure, monkeypatch, tmpdir): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - tmpdir.join('.browserstack').write('[credentials]\nusername=foo') - assert 'BrowserStack key must be set' in failure() - - -@pytest.mark.parametrize(('username', 'key'), [('BROWSERSTACK_USERNAME', - 'BROWSERSTACK_ACCESS_KEY'), - ('BROWSERSTACK_USR', - 'BROWSERSTACK_PSW')]) + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + tmpdir.join(".browserstack").write("[credentials]\nusername=foo") + assert "BrowserStack key must be set" in failure() + + +@pytest.mark.parametrize( + ("username", "key"), + [ + ("BROWSERSTACK_USERNAME", "BROWSERSTACK_ACCESS_KEY"), + ("BROWSERSTACK_USR", "BROWSERSTACK_PSW"), + ], +) def test_invalid_credentials_env(failure, monkeypatch, tmpdir, username, key): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - monkeypatch.setenv(username, 'foo') - monkeypatch.setenv(key, 'bar') + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + monkeypatch.setenv(username, "foo") + monkeypatch.setenv(key, "bar") out = failure() - messages = ['Invalid username or password', 'basic auth failed'] + messages = ["Invalid username or password", "basic auth failed"] assert any(message in out for message in messages) def test_invalid_credentials_file(failure, monkeypatch, tmpdir): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - tmpdir.join('.browserstack').write('[credentials]\nusername=foo\nkey=bar') + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + tmpdir.join(".browserstack").write("[credentials]\nusername=foo\nkey=bar") out = failure() - messages = ['Invalid username or password', 'basic auth failed'] + messages = ["Invalid username or password", "basic auth failed"] assert any(message in out for message in messages) diff --git a/testing/test_capabilities.py b/testing/test_capabilities.py index e3a7a5e6..cfc737bc 100644 --- a/testing/test_capabilities.py +++ b/testing/test_capabilities.py @@ -11,56 +11,68 @@ @pytest.fixture def testfile(testdir): - return testdir.makepyfile(""" + return testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_capabilities(capabilities): assert capabilities['foo'] == 'bar' - """) + """ + ) def test_command_line(testfile, testdir): - testdir.quick_qa('--capability', 'foo', 'bar', testfile, passed=1) + testdir.quick_qa("--capability", "foo", "bar", testfile, passed=1) def test_file(testfile, testdir): - variables = testdir.makefile('.json', '{"capabilities": {"foo": "bar"}}') - testdir.quick_qa('--variables', variables, testfile, passed=1) + variables = testdir.makefile(".json", '{"capabilities": {"foo": "bar"}}') + testdir.quick_qa("--variables", variables, testfile, passed=1) def test_file_remote(testdir): - key = 'goog:chromeOptions' - capabilities = {'browserName': 'chrome', key: {'args': ['foo']}} - variables = testdir.makefile('.json', '{{"capabilities": {}}}'.format( - json.dumps(capabilities))) - file_test = testdir.makepyfile(""" + key = "goog:chromeOptions" + capabilities = {"browserName": "chrome", key: {"args": ["foo"]}} + variables = testdir.makefile( + ".json", '{{"capabilities": {}}}'.format(json.dumps(capabilities)) + ) + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_capabilities(session_capabilities, capabilities): assert session_capabilities['{0}']['args'] == ['foo'] assert capabilities['{0}']['args'] == ['foo'] - """.format(key)) + """.format( + key + ) + ) testdir.quick_qa( - '--driver', 'Remote', '--variables', variables, file_test, passed=1) + "--driver", "Remote", "--variables", variables, file_test, passed=1 + ) def test_fixture(testfile, testdir): - testdir.makeconftest(""" + testdir.makeconftest( + """ import pytest @pytest.fixture(scope='session') def capabilities(): return {'foo': 'bar'} - """) + """ + ) testdir.quick_qa(testfile, passed=1) def test_mark(testdir): - file_test = testdir.makepyfile(""" + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive @pytest.mark.capabilities(foo='bar') def test_capabilities(session_capabilities, capabilities): assert 'foo' not in session_capabilities assert capabilities['foo'] == 'bar' - """) + """ + ) testdir.quick_qa(file_test, passed=1) diff --git a/testing/test_chrome.py b/testing/test_chrome.py index fce9f7bd..f65afd1e 100644 --- a/testing/test_chrome.py +++ b/testing/test_chrome.py @@ -11,19 +11,22 @@ @pytest.mark.chrome def test_launch(testdir, httpserver): - httpserver.serve_content(content='

Success!

') - file_test = testdir.makepyfile(""" + httpserver.serve_content(content="

Success!

") + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(webtext): assert webtext == u'Success!' - """) - testdir.quick_qa('--driver', 'Chrome', file_test, passed=1) + """ + ) + testdir.quick_qa("--driver", "Chrome", file_test, passed=1) @pytest.mark.chrome def test_options(testdir): - testdir.makepyfile(""" + testdir.makepyfile( + """ import pytest @pytest.fixture def chrome_options(chrome_options): @@ -32,17 +35,19 @@ def chrome_options(chrome_options): @pytest.mark.nondestructive def test_pass(selenium): pass - """) - reprec = testdir.inline_run('--driver', 'Chrome') + """ + ) + reprec = testdir.inline_run("--driver", "Chrome") passed, skipped, failed = reprec.listoutcomes() assert len(failed) == 1 out = failed[0].longrepr.reprcrash.message - assert 'no chrome binary at /foo/bar' in out + assert "no chrome binary at /foo/bar" in out @pytest.mark.chrome def test_args(testdir): - file_test = testdir.makepyfile(""" + file_test = testdir.makepyfile( + """ import pytest @pytest.fixture def driver_log(): @@ -54,6 +59,7 @@ def driver_args(): @pytest.mark.nondestructive def test_pass(selenium): pass - """) - testdir.quick_qa('--driver', 'Chrome', file_test, passed=1) - assert os.path.exists(str(testdir.tmpdir.join('foo.log'))) + """ + ) + testdir.quick_qa("--driver", "Chrome", file_test, passed=1) + assert os.path.exists(str(testdir.tmpdir.join("foo.log"))) diff --git a/testing/test_crossbrowsertesting.py b/testing/test_crossbrowsertesting.py index e3fa679a..53835dec 100644 --- a/testing/test_crossbrowsertesting.py +++ b/testing/test_crossbrowsertesting.py @@ -12,11 +12,13 @@ @pytest.fixture def testfile(testdir): - return testdir.makepyfile(""" + return testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(selenium): pass - """) + """ + ) def failure_with_output(testdir, *args, **kwargs): @@ -29,45 +31,53 @@ def failure_with_output(testdir, *args, **kwargs): @pytest.fixture def failure(testdir, testfile, httpserver_base_url): - return partial(failure_with_output, testdir, testfile, httpserver_base_url, - '--driver', 'CrossBrowserTesting') + return partial( + failure_with_output, + testdir, + testfile, + httpserver_base_url, + "--driver", + "CrossBrowserTesting", + ) def test_missing_username(failure, monkeypatch, tmpdir): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - assert 'CrossBrowserTesting username must be set' in failure() + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + assert "CrossBrowserTesting username must be set" in failure() def test_missing_access_key_env(failure, monkeypatch, tmpdir): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - monkeypatch.setenv('CROSSBROWSERTESTING_USERNAME', 'foo') - assert 'CrossBrowserTesting key must be set' in failure() + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + monkeypatch.setenv("CROSSBROWSERTESTING_USERNAME", "foo") + assert "CrossBrowserTesting key must be set" in failure() def test_missing_access_key_file(failure, monkeypatch, tmpdir): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - tmpdir.join('.crossbrowsertesting').write('[credentials]\nusername=foo') - assert 'CrossBrowserTesting key must be set' in failure() - - -@pytest.mark.parametrize(('username', 'key'), - [('CROSSBROWSERTESTING_USERNAME', - 'CROSSBROWSERTESTING_AUTH_KEY'), - ('CROSSBROWSERTESTING_USR', - 'CROSSBROWSERTESTING_PSW')]) + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + tmpdir.join(".crossbrowsertesting").write("[credentials]\nusername=foo") + assert "CrossBrowserTesting key must be set" in failure() + + +@pytest.mark.parametrize( + ("username", "key"), + [ + ("CROSSBROWSERTESTING_USERNAME", "CROSSBROWSERTESTING_AUTH_KEY"), + ("CROSSBROWSERTESTING_USR", "CROSSBROWSERTESTING_PSW"), + ], +) def test_invalid_credentials_env(failure, monkeypatch, tmpdir, username, key): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - monkeypatch.setenv(username, 'foo') - monkeypatch.setenv(key, 'bar') - out = failure('--capability', 'browser_api_name', 'FF46') - messages = ['missing auth', 'basic auth failed'] + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + monkeypatch.setenv(username, "foo") + monkeypatch.setenv(key, "bar") + out = failure("--capability", "browser_api_name", "FF46") + messages = ["missing auth", "basic auth failed"] assert any(message in out for message in messages) def test_invalid_credentials_file(failure, monkeypatch, tmpdir): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - config = tmpdir.join('.crossbrowsertesting') - config.write('[credentials]\nusername=foo\nkey=bar') - out = failure('--capability', 'browser_api_name', 'FF46') - messages = ['missing auth', 'basic auth failed'] + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + config = tmpdir.join(".crossbrowsertesting") + config.write("[credentials]\nusername=foo\nkey=bar") + out = failure("--capability", "browser_api_name", "FF46") + messages = ["missing auth", "basic auth failed"] assert any(message in out for message in messages) diff --git a/testing/test_destructive.py b/testing/test_destructive.py index 4d95d400..ae230340 100644 --- a/testing/test_destructive.py +++ b/testing/test_destructive.py @@ -8,70 +8,74 @@ def test_skip_destructive_by_default(testdir): - file_test = testdir.makepyfile('def test_pass(): pass') + file_test = testdir.makepyfile("def test_pass(): pass") testdir.quick_qa(file_test, passed=0, failed=0, skipped=1) def test_warn_when_url_is_sensitive(testdir, httpserver, monkeypatch, capsys): - monkeypatch.setenv('SENSITIVE_URL', r'127\.0\.0\.1') - file_test = testdir.makepyfile('def test_pass(): pass') - testdir.quick_qa(file_test, '--verbose', passed=0, failed=0, skipped=1) + monkeypatch.setenv("SENSITIVE_URL", r"127\.0\.0\.1") + file_test = testdir.makepyfile("def test_pass(): pass") + testdir.quick_qa(file_test, "--verbose", passed=0, failed=0, skipped=1) out, err = capsys.readouterr() - msg = '*** WARNING: sensitive url matches {} ***'.format(httpserver.url) + msg = "*** WARNING: sensitive url matches {} ***".format(httpserver.url) assert msg in out def test_skip_destructive_when_sensitive_command_line(testdir, httpserver): - file_test = testdir.makepyfile('def test_pass(): pass') + file_test = testdir.makepyfile("def test_pass(): pass") print(httpserver.url) - testdir.quick_qa('--sensitive-url', r'127\.0\.0\.1', file_test, passed=0, - failed=0, skipped=1) + testdir.quick_qa( + "--sensitive-url", r"127\.0\.0\.1", file_test, passed=0, failed=0, skipped=1 + ) def test_skip_destructive_when_sensitive_config_file(testdir, httpserver): - testdir.makefile('.ini', pytest='[pytest]\nsensitive_url=127\\.0\\.0\\.1') - file_test = testdir.makepyfile('def test_pass(): pass') + testdir.makefile(".ini", pytest="[pytest]\nsensitive_url=127\\.0\\.0\\.1") + file_test = testdir.makepyfile("def test_pass(): pass") testdir.quick_qa(file_test, passed=0, failed=0, skipped=1) def test_skip_destructive_when_sensitive_env(testdir, httpserver, monkeypatch): - monkeypatch.setenv('SENSITIVE_URL', r'127\.0\.0\.1') - file_test = testdir.makepyfile('def test_pass(): pass') + monkeypatch.setenv("SENSITIVE_URL", r"127\.0\.0\.1") + file_test = testdir.makepyfile("def test_pass(): pass") testdir.quick_qa(file_test, passed=0, failed=0, skipped=1) def test_run_non_destructive_by_default(testdir): - file_test = testdir.makepyfile(""" + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(): pass - """) + """ + ) testdir.quick_qa(file_test, passed=1) def test_run_destructive_when_not_sensitive_command_line(testdir, httpserver): - file_test = testdir.makepyfile('def test_pass(): pass') - testdir.quick_qa('--sensitive-url', 'foo', file_test, passed=1) + file_test = testdir.makepyfile("def test_pass(): pass") + testdir.quick_qa("--sensitive-url", "foo", file_test, passed=1) def test_run_destructive_when_not_sensitive_config_file(testdir, httpserver): - testdir.makefile('.ini', pytest='[pytest]\nsensitive_url=foo') - file_test = testdir.makepyfile('def test_pass(): pass') + testdir.makefile(".ini", pytest="[pytest]\nsensitive_url=foo") + file_test = testdir.makepyfile("def test_pass(): pass") testdir.quick_qa(file_test, passed=1, failed=0, skipped=0) -def test_run_destructive_when_not_sensitive_env(testdir, httpserver, - monkeypatch): - monkeypatch.setenv('SENSITIVE_URL', 'foo') - file_test = testdir.makepyfile('def test_pass(): pass') +def test_run_destructive_when_not_sensitive_env(testdir, httpserver, monkeypatch): + monkeypatch.setenv("SENSITIVE_URL", "foo") + file_test = testdir.makepyfile("def test_pass(): pass") testdir.quick_qa(file_test, passed=1, failed=0, skipped=0) def test_run_destructive_and_non_destructive_when_not_sensitive(testdir): - file_test = testdir.makepyfile(""" + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass1(): pass def test_pass2(): pass - """) - testdir.quick_qa('--sensitive-url', 'foo', file_test, passed=2) + """ + ) + testdir.quick_qa("--sensitive-url", "foo", file_test, passed=2) diff --git a/testing/test_driver.py b/testing/test_driver.py index 61bf662e..d985066b 100644 --- a/testing/test_driver.py +++ b/testing/test_driver.py @@ -11,11 +11,13 @@ @pytest.fixture def testfile(testdir): - return testdir.makepyfile(""" + return testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(selenium): pass - """) + """ + ) def failure_with_output(testdir, *args, **kwargs): @@ -32,121 +34,141 @@ def failure(testdir, testfile, httpserver_base_url): def test_driver_case_insensitive(testdir): - file_test = testdir.makepyfile(""" + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(request): assert request.config.getoption('driver') == 'SaUcELaBs' - """) - testdir.quick_qa('--driver', 'SaUcELaBs', file_test, passed=1) + """ + ) + testdir.quick_qa("--driver", "SaUcELaBs", file_test, passed=1) def test_missing_driver(failure): out = failure() - assert 'UsageError: --driver must be specified' in out + assert "UsageError: --driver must be specified" in out def test_invalid_driver(testdir): - testdir.makepyfile(""" + testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(): pass - """) - invalid_driver = 'noop' - result = testdir.runpytest('--driver', invalid_driver) + """ + ) + invalid_driver = "noop" + result = testdir.runpytest("--driver", invalid_driver) message = "--driver: invalid choice: '{}'".format(invalid_driver) assert message in result.errlines[1] def test_driver_quit(testdir): - testdir.makepyfile(""" + testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_driver_quit(selenium): selenium.quit() selenium.title - """) + """ + ) result = testdir.runpytestqa() - result.stdout.fnmatch_lines_random([ - 'WARNING: Failed to gather URL: *', - 'WARNING: Failed to gather screenshot: *', - 'WARNING: Failed to gather HTML: *', - 'WARNING: Failed to gather log types: *']) + result.stdout.fnmatch_lines_random( + [ + "WARNING: Failed to gather URL: *", + "WARNING: Failed to gather screenshot: *", + "WARNING: Failed to gather HTML: *", + "WARNING: Failed to gather log types: *", + ] + ) outcomes = result.parseoutcomes() - assert outcomes.get('failed') == 1 + assert outcomes.get("failed") == 1 def test_default_host_port(testdir): - host = 'localhost' - port = 4444 - file_test = testdir.makepyfile(""" + host = "localhost" + port = "4444" + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(driver_kwargs): assert driver_kwargs['command_executor'] == 'http://{}:{}/wd/hub' - """.format(host, port)) - testdir.quick_qa('--driver', 'Remote', file_test, passed=1) + """.format( + host, port + ) + ) + testdir.quick_qa("--driver", "Remote", file_test, passed=1) def test_arguments_order(testdir): - host = 'notlocalhost' - port = 4441 - file_test = testdir.makepyfile(""" + host = "notlocalhost" + port = "4441" + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(driver_kwargs): assert driver_kwargs['command_executor'] == 'http://{}:{}/wd/hub' - """.format(host, port)) - testdir.quick_qa('--driver', 'Remote', - '--host', host, - '--port', port, - file_test, passed=1) + """.format( + host, port + ) + ) + testdir.quick_qa( + "--driver", "Remote", "--host", host, "--port", port, file_test, passed=1 + ) def test_arguments_order_random(testdir): - host = 'notlocalhost' - port = 4441 - file_test = testdir.makepyfile(""" + host = "notlocalhost" + port = "4441" + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(driver_kwargs): assert driver_kwargs['command_executor'] == 'http://{}:{}/wd/hub' - """.format(host, port)) - testdir.quick_qa('--host', host, - '--driver', 'Remote', - '--port', port, - file_test, passed=1) - - -@pytest.mark.parametrize('name', - ['SauceLabs', - 'TestingBot', - 'CrossBrowserTesting', - 'BrowserStack']) + """.format( + host, port + ) + ) + testdir.quick_qa( + "--host", host, "--driver", "Remote", "--port", port, file_test, passed=1 + ) + + +@pytest.mark.parametrize( + "name", ["SauceLabs", "TestingBot", "CrossBrowserTesting", "BrowserStack"] +) def test_provider_naming(name): import importlib driver = name module = importlib.import_module( - 'pytest_selenium.drivers.{}'.format(driver.lower())) + "pytest_selenium.drivers.{}".format(driver.lower()) + ) provider = getattr(module, driver)() assert provider.uses_driver(driver) assert provider.name == name def test_service_log_path(testdir): - file_test = testdir.makepyfile(""" + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(driver_kwargs): assert driver_kwargs['service_log_path'] is not None - """) - testdir.quick_qa('--driver', 'Firefox', - file_test, passed=1) + """ + ) + testdir.quick_qa("--driver", "Firefox", file_test, passed=1) def test_no_service_log_path(testdir): - file_test = testdir.makepyfile(""" + file_test = testdir.makepyfile( + """ import pytest @pytest.fixture def driver_log(): @@ -155,6 +177,6 @@ def driver_log(): @pytest.mark.nondestructive def test_pass(driver_kwargs): assert driver_kwargs['service_log_path'] is None - """) - testdir.quick_qa('--driver', 'Firefox', - file_test, passed=1) + """ + ) + testdir.quick_qa("--driver", "Firefox", file_test, passed=1) diff --git a/testing/test_driver_log.py b/testing/test_driver_log.py index aa42b60b..d2ac77df 100644 --- a/testing/test_driver_log.py +++ b/testing/test_driver_log.py @@ -13,26 +13,28 @@ def test_driver_log(testdir, httpserver): - httpserver.serve_content(content='

Success!

') - testdir.makepyfile(""" + httpserver.serve_content(content="

Success!

") + testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_driver_log(webtext): assert False - """) - path = testdir.tmpdir.join('report.html') - testdir.runpytestqa('--html', path) + """ + ) + path = testdir.tmpdir.join("report.html") + testdir.runpytestqa("--html", path) with open(str(path)) as f: html = f.read() assert re.search(LOG_REGEX, html) is not None - log_path = testdir.tmpdir.dirpath( - 'basetemp', 'test_driver_log0', 'driver.log') + log_path = testdir.tmpdir.dirpath("basetemp", "test_driver_log0", "driver.log") assert os.path.exists(str(log_path)) def test_driver_log_fixture(testdir, httpserver): - httpserver.serve_content(content='

Success!

') - file_test = testdir.makepyfile(""" + httpserver.serve_content(content="

Success!

") + file_test = testdir.makepyfile( + """ import pytest @pytest.fixture def driver_log(): @@ -41,14 +43,16 @@ def driver_log(): @pytest.mark.nondestructive def test_pass(webtext): assert webtext == u'Success!' - """) + """ + ) testdir.quick_qa(file_test, passed=1) - assert os.path.exists(str(testdir.tmpdir.join('foo.log'))) + assert os.path.exists(str(testdir.tmpdir.join("foo.log"))) def test_no_driver_log(testdir, httpserver): - httpserver.serve_content(content='

Success!

') - testdir.makepyfile(""" + httpserver.serve_content(content="

Success!

") + testdir.makepyfile( + """ import pytest @pytest.fixture def driver_log(): @@ -57,12 +61,12 @@ def driver_log(): @pytest.mark.nondestructive def test_no_driver_log(webtext): assert False - """) - path = testdir.tmpdir.join('report.html') - testdir.runpytestqa('--html', path) + """ + ) + path = testdir.tmpdir.join("report.html") + testdir.runpytestqa("--html", path) with open(str(path)) as f: html = f.read() assert re.search(LOG_REGEX, html) is None - log_path = testdir.tmpdir.dirpath( - 'basetemp', 'test_no_driver_log0', 'driver.log') + log_path = testdir.tmpdir.dirpath("basetemp", "test_no_driver_log0", "driver.log") assert not os.path.exists(str(log_path)) diff --git a/testing/test_edge.py b/testing/test_edge.py index ea6ebb4f..59068623 100644 --- a/testing/test_edge.py +++ b/testing/test_edge.py @@ -9,11 +9,13 @@ @pytest.mark.edge def test_launch(testdir, httpserver): - httpserver.serve_content(content='

Success!

') - file_test = testdir.makepyfile(""" + httpserver.serve_content(content="

Success!

") + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(webtext): assert webtext == u'Success!' - """) - testdir.quick_qa('--driver', 'Edge', file_test, passed=1) + """ + ) + testdir.quick_qa("--driver", "Edge", file_test, passed=1) diff --git a/testing/test_firefox.py b/testing/test_firefox.py index 2153107e..2e3ab7aa 100644 --- a/testing/test_firefox.py +++ b/testing/test_firefox.py @@ -8,25 +8,29 @@ def test_launch(testdir, httpserver): - httpserver.serve_content(content='

Success!

') - file_test = testdir.makepyfile(""" + httpserver.serve_content(content="

Success!

") + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(webtext): assert webtext == u'Success!' - """) + """ + ) testdir.quick_qa(file_test, passed=1) def test_launch_case_insensitive(testdir, httpserver): - httpserver.serve_content(content='

Success!

') - file_test = testdir.makepyfile(""" + httpserver.serve_content(content="

Success!

") + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(webtext): assert webtext == u'Success!' - """) - testdir.quick_qa('--driver', 'firefox', file_test, passed=1) + """ + ) + testdir.quick_qa("--driver", "firefox", file_test, passed=1) def test_profile(testdir, httpserver): @@ -36,12 +40,14 @@ def test_profile(testdir, httpserver): when calling value_of_css_property. """ httpserver.serve_content(content='

Success!

Link') - profile = testdir.tmpdir.mkdir('profile') - profile.join('prefs.js').write( + profile = testdir.tmpdir.mkdir("profile") + profile.join("prefs.js").write( 'user_pref("browser.anchor_color", "#FF69B4");' 'user_pref("browser.display.foreground_color", "#FF0000");' - 'user_pref("browser.display.use_document_colors", false);') - file_test = testdir.makepyfile(""" + 'user_pref("browser.display.use_document_colors", false);' + ) + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_profile(base_url, selenium): @@ -52,8 +58,9 @@ def test_profile(base_url, selenium): anchor_color = anchor.value_of_css_property('color') assert header_color == 'rgb(255, 0, 0)' assert anchor_color == 'rgb(255, 105, 180)' - """) - testdir.quick_qa('--firefox-profile', profile, file_test, passed=1) + """ + ) + testdir.quick_qa("--firefox-profile", profile, file_test, passed=1) def test_profile_with_preferences(testdir, httpserver): @@ -65,12 +72,14 @@ def test_profile_with_preferences(testdir, httpserver): overridden by the preference. """ httpserver.serve_content(content='

Success!

Link') - profile = testdir.tmpdir.mkdir('profile') - profile.join('prefs.js').write( + profile = testdir.tmpdir.mkdir("profile") + profile.join("prefs.js").write( 'user_pref("browser.anchor_color", "#FF69B4");' 'user_pref("browser.display.foreground_color", "#FF0000");' - 'user_pref("browser.display.use_document_colors", false);') - file_test = testdir.makepyfile(""" + 'user_pref("browser.display.use_document_colors", false);' + ) + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_preferences(base_url, selenium): @@ -81,17 +90,27 @@ def test_preferences(base_url, selenium): anchor_color = anchor.value_of_css_property('color') assert header_color == 'rgb(255, 0, 0)' assert anchor_color == 'rgb(255, 0, 0)' - """) - testdir.quick_qa('--firefox-preference', 'browser.anchor_color', '#FF0000', - '--firefox-profile', profile, file_test, passed=1) + """ + ) + testdir.quick_qa( + "--firefox-preference", + "browser.anchor_color", + "#FF0000", + "--firefox-profile", + profile, + file_test, + passed=1, + ) def test_extension(testdir): """Test that a firefox extension can be added when starting Firefox.""" import os - path = os.path.join(os.path.split(os.path.dirname(__file__))[0], 'testing') - extension = os.path.join(path, 'empty.xpi') - file_test = testdir.makepyfile(""" + + path = os.path.join(os.path.split(os.path.dirname(__file__))[0], "testing") + extension = os.path.join(path, "empty.xpi") + file_test = testdir.makepyfile( + """ import time import pytest from selenium.common.exceptions import StaleElementReferenceException @@ -105,14 +124,16 @@ def test_extension(selenium): lambda s: s.find_element_by_id( 'extensions-tbody').text) assert 'Test Extension (empty)' in extensions - """) - testdir.quick_qa('--firefox-extension', extension, file_test, passed=1) + """ + ) + testdir.quick_qa("--firefox-extension", extension, file_test, passed=1) def test_preferences_marker(testdir, httpserver): """Test that preferences can be specified using the marker.""" httpserver.serve_content(content='

Success!

Link') - file_test = testdir.makepyfile(""" + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive @pytest.mark.firefox_preferences({ @@ -127,5 +148,22 @@ def test_preferences(base_url, selenium): anchor_color = anchor.value_of_css_property('color') assert header_color == 'rgb(255, 0, 0)' assert anchor_color == 'rgb(255, 105, 180)' - """) + """ + ) + testdir.quick_qa(file_test, passed=1) + + +def test_arguments_marker(testdir): + file_test = testdir.makepyfile( + """ + import pytest + pytestmark = pytest.mark.firefox_arguments('baz') + @pytest.mark.nondestructive + @pytest.mark.firefox_arguments('foo', 'bar') + def test_arguments(firefox_options): + actual = sorted(firefox_options.arguments) + expected = sorted(['baz', 'foo', 'bar']) + assert actual == expected + """ + ) testdir.quick_qa(file_test, passed=1) diff --git a/testing/test_metadata.py b/testing/test_metadata.py index 789e0ea0..80796865 100644 --- a/testing/test_metadata.py +++ b/testing/test_metadata.py @@ -8,27 +8,34 @@ def test_metadata_default_host_port(testdir): - host = 'localhost' - port = 4444 - file_test = testdir.makepyfile(""" + host = "localhost" + port = "4444" + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(metadata): assert metadata['Server'] == '{}:{}' - """.format(host, port)) - testdir.quick_qa('--driver', 'Remote', file_test, passed=1) + """.format( + host, port + ) + ) + testdir.quick_qa("--driver", "Remote", file_test, passed=1) def test_metadata_host_port(testdir): - host = 'notlocalhost' - port = 4441 - file_test = testdir.makepyfile(""" + host = "notlocalhost" + port = "4441" + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(metadata): assert metadata['Server'] == '{}:{}' - """.format(host, port)) - testdir.quick_qa('--driver', 'Remote', - '--host', host, - '--port', port, - file_test, passed=1) + """.format( + host, port + ) + ) + testdir.quick_qa( + "--driver", "Remote", "--host", host, "--port", port, file_test, passed=1 + ) diff --git a/testing/test_phantomjs.py b/testing/test_phantomjs.py index 047f7448..3df2aaf5 100644 --- a/testing/test_phantomjs.py +++ b/testing/test_phantomjs.py @@ -11,19 +11,22 @@ @pytest.mark.phantomjs def test_launch(testdir, httpserver): - httpserver.serve_content(content='

Success!

') - file_test = testdir.makepyfile(""" + httpserver.serve_content(content="

Success!

") + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(webtext): assert webtext == u'Success!' - """) - testdir.quick_qa('--driver', 'PhantomJS', file_test, passed=1) + """ + ) + testdir.quick_qa("--driver", "PhantomJS", file_test, passed=1) @pytest.mark.phantomjs def test_args(testdir): - file_test = testdir.makepyfile(""" + file_test = testdir.makepyfile( + """ import pytest @pytest.fixture def driver_args(): @@ -31,6 +34,7 @@ def driver_args(): @pytest.mark.nondestructive def test_pass(selenium): pass - """) - testdir.quick_qa('--driver', 'PhantomJS', file_test, passed=1) - assert os.path.exists(str(testdir.tmpdir.join('foo.log'))) + """ + ) + testdir.quick_qa("--driver", "PhantomJS", file_test, passed=1) + assert os.path.exists(str(testdir.tmpdir.join("foo.log"))) diff --git a/testing/test_report.py b/testing/test_report.py index d7837a42..738255fd 100644 --- a/testing/test_report.py +++ b/testing/test_report.py @@ -11,38 +11,46 @@ pytestmark = pytest.mark.nondestructive URL_LINK = 'URL' -SCREENSHOT_LINK_REGEX = 'Screenshot' # noqa +SCREENSHOT_LINK_REGEX = ( + 'Screenshot' +) # noqa SCREENSHOT_REGEX = '
' LOGS_REGEX = '.* Log' HTML_REGEX = 'HTML' def run(testdir, *args): - testdir.makepyfile(""" + testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_fail(webtext): assert False - """) - path = testdir.tmpdir.join('report.html') - result = testdir.runpytestqa('--html', path, *args) + """ + ) + path = testdir.tmpdir.join("report.html") + result = testdir.runpytestqa("--html", path, *args) with open(str(path)) as f: html = f.read() return result, html -@pytest.mark.parametrize('when', ['always', 'failure', 'never']) +@pytest.mark.parametrize("when", ["always", "failure", "never"]) def test_capture_debug_env(testdir, httpserver, monkeypatch, when): - httpserver.serve_content(content='

Success!

Ё

') - monkeypatch.setenv('SELENIUM_CAPTURE_DEBUG', when) - testdir.makepyfile(""" + httpserver.serve_content(content="

Success!

Ё

") + monkeypatch.setenv("SELENIUM_CAPTURE_DEBUG", when) + testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_capture_debug(webtext): assert {0} - """.format('True' if 'always' else 'False')) + """.format( + "True" if "always" else "False" + ) + ) result, html = run(testdir) - if when in ['always', 'failure']: + if when in ["always", "failure"]: assert URL_LINK.format(httpserver.url) in html assert re.search(SCREENSHOT_LINK_REGEX, html) is not None assert re.search(SCREENSHOT_REGEX, html) is not None @@ -56,21 +64,30 @@ def test_capture_debug(webtext): assert re.search(HTML_REGEX, html) is None -@pytest.mark.parametrize('when', ['always', 'failure', 'never']) +@pytest.mark.parametrize("when", ["always", "failure", "never"]) def test_capture_debug_config(testdir, httpserver, when): - httpserver.serve_content(content='

Success!

Ё

') - testdir.makefile('.ini', pytest=""" + httpserver.serve_content(content="

Success!

Ё

") + testdir.makefile( + ".ini", + pytest=""" [pytest] selenium_capture_debug={0} - """.format(when)) - testdir.makepyfile(""" + """.format( + when + ), + ) + testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_capture_debug(webtext): assert {0} - """.format('True' if 'always' else 'False')) + """.format( + "True" if "always" else "False" + ) + ) result, html = run(testdir) - if when in ['always', 'failure']: + if when in ["always", "failure"]: assert URL_LINK.format(httpserver.url) in html assert re.search(SCREENSHOT_LINK_REGEX, html) is not None assert re.search(SCREENSHOT_REGEX, html) is not None @@ -84,64 +101,69 @@ def test_capture_debug(webtext): assert re.search(HTML_REGEX, html) is None -@pytest.mark.parametrize('exclude', ['url', 'screenshot', 'html', 'logs']) +@pytest.mark.parametrize("exclude", ["url", "screenshot", "html", "logs"]) def test_exclude_debug_env(testdir, httpserver, monkeypatch, exclude): - httpserver.serve_content(content='

Success!

Ё

') - monkeypatch.setenv('SELENIUM_EXCLUDE_DEBUG', exclude) + httpserver.serve_content(content="

Success!

Ё

") + monkeypatch.setenv("SELENIUM_EXCLUDE_DEBUG", exclude) result, html = run(testdir) assert result.ret - if exclude == 'url': + if exclude == "url": assert URL_LINK.format(httpserver.url) not in html else: assert URL_LINK.format(httpserver.url) in html - if exclude == 'screenshot': + if exclude == "screenshot": assert re.search(SCREENSHOT_LINK_REGEX, html) is None assert re.search(SCREENSHOT_REGEX, html) is None else: assert re.search(SCREENSHOT_LINK_REGEX, html) is not None assert re.search(SCREENSHOT_REGEX, html) is not None - if exclude == 'logs': + if exclude == "logs": assert re.search(LOGS_REGEX, html) is None else: assert re.search(LOGS_REGEX, html) is not None - if exclude == 'html': + if exclude == "html": assert re.search(HTML_REGEX, html) is None else: assert re.search(HTML_REGEX, html) is not None -@pytest.mark.parametrize('exclude', ['url', 'screenshot', 'html', 'logs']) +@pytest.mark.parametrize("exclude", ["url", "screenshot", "html", "logs"]) def test_exclude_debug_config(testdir, httpserver, monkeypatch, exclude): - httpserver.serve_content(content='

Success!

Ё

') - testdir.makefile('.ini', pytest=""" + httpserver.serve_content(content="

Success!

Ё

") + testdir.makefile( + ".ini", + pytest=""" [pytest] selenium_exclude_debug={0} - """.format(exclude)) + """.format( + exclude + ), + ) result, html = run(testdir) assert result.ret - if exclude == 'url': + if exclude == "url": assert URL_LINK.format(httpserver.url) not in html else: assert URL_LINK.format(httpserver.url) in html - if exclude == 'screenshot': + if exclude == "screenshot": assert re.search(SCREENSHOT_LINK_REGEX, html) is None assert re.search(SCREENSHOT_REGEX, html) is None else: assert re.search(SCREENSHOT_LINK_REGEX, html) is not None assert re.search(SCREENSHOT_REGEX, html) is not None - if exclude == 'logs': + if exclude == "logs": assert re.search(LOGS_REGEX, html) is None else: assert re.search(LOGS_REGEX, html) is not None - if exclude == 'html': + if exclude == "html": assert re.search(HTML_REGEX, html) is None else: assert re.search(HTML_REGEX, html) is not None diff --git a/testing/test_safari.py b/testing/test_safari.py index a44981f7..50705eaf 100644 --- a/testing/test_safari.py +++ b/testing/test_safari.py @@ -9,11 +9,13 @@ @pytest.mark.safari def test_launch(testdir, httpserver): - httpserver.serve_content(content='

Success!

') - file_test = testdir.makepyfile(""" + httpserver.serve_content(content="

Success!

") + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(webtext): assert webtext == u'Success!' - """) - testdir.quick_qa('--driver', 'Safari', file_test, passed=1) + """ + ) + testdir.quick_qa("--driver", "Safari", file_test, passed=1) diff --git a/testing/test_saucelabs.py b/testing/test_saucelabs.py index 3b4d2f3f..fdd8400d 100644 --- a/testing/test_saucelabs.py +++ b/testing/test_saucelabs.py @@ -13,11 +13,13 @@ @pytest.fixture def testfile(testdir): - return testdir.makepyfile(""" + return testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(selenium): pass - """) + """ + ) def failure_with_output(testdir, *args, **kwargs): @@ -30,64 +32,75 @@ def failure_with_output(testdir, *args, **kwargs): @pytest.fixture def failure(testdir, testfile, httpserver_base_url): - return partial(failure_with_output, testdir, testfile, httpserver_base_url, - '--driver', 'SauceLabs') + return partial( + failure_with_output, + testdir, + testfile, + httpserver_base_url, + "--driver", + "SauceLabs", + ) def test_missing_username(failure, monkeypatch, tmpdir): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - assert 'SauceLabs username must be set' in failure() + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + assert "SauceLabs username must be set" in failure() def test_missing_api_key_env(failure, monkeypatch, tmpdir): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - monkeypatch.setenv('SAUCELABS_USERNAME', 'foo') - assert 'SauceLabs key must be set' in failure() + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + monkeypatch.setenv("SAUCELABS_USERNAME", "foo") + assert "SauceLabs key must be set" in failure() def test_missing_api_key_file(failure, monkeypatch, tmpdir): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - tmpdir.join('.saucelabs').write('[credentials]\nusername=foo') - assert 'SauceLabs key must be set' in failure() - - -@pytest.mark.parametrize(('username', 'key'), [('SAUCELABS_USERNAME', - 'SAUCELABS_API_KEY'), - ('SAUCELABS_USR', - 'SAUCELABS_PSW'), - ('SAUCE_USERNAME', - 'SAUCE_ACCESS_KEY')]) + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + tmpdir.join(".saucelabs").write("[credentials]\nusername=foo") + assert "SauceLabs key must be set" in failure() + + +@pytest.mark.parametrize( + ("username", "key"), + [ + ("SAUCELABS_USERNAME", "SAUCELABS_API_KEY"), + ("SAUCELABS_USR", "SAUCELABS_PSW"), + ("SAUCE_USERNAME", "SAUCE_ACCESS_KEY"), + ], +) def test_invalid_credentials_env(failure, monkeypatch, tmpdir, username, key): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - monkeypatch.setenv(username, 'foo') - monkeypatch.setenv(key, 'bar') + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + monkeypatch.setenv(username, "foo") + monkeypatch.setenv(key, "bar") out = failure() - messages = ['Sauce Labs Authentication Error', 'basic auth failed'] + messages = ["Sauce Labs Authentication Error", "basic auth failed"] assert any(message in out for message in messages) def test_invalid_credentials_file(failure, monkeypatch, tmpdir): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - tmpdir.join('.saucelabs').write('[credentials]\nusername=foo\nkey=bar') + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + tmpdir.join(".saucelabs").write("[credentials]\nusername=foo\nkey=bar") out = failure() - messages = ['Sauce Labs Authentication Error', 'basic auth failed'] + messages = ["Sauce Labs Authentication Error", "basic auth failed"] assert any(message in out for message in messages) def test_credentials_in_capabilities(monkeypatch, testdir): - file_test = testdir.makepyfile(""" + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_sauce_capabilities(driver_kwargs): assert driver_kwargs['desired_capabilities']['username'] == 'foo' assert driver_kwargs['desired_capabilities']['accessKey'] == 'bar' - """) + """ + ) run_sauce_test(monkeypatch, testdir, file_test) def test_no_sauce_options(monkeypatch, testdir): - file_test = testdir.makepyfile(""" + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_sauce_capabilities(driver_kwargs): @@ -96,107 +109,114 @@ def test_sauce_capabilities(driver_kwargs): raise AssertionError(' should not be present!') except KeyError: pass - """) + """ + ) run_sauce_test(monkeypatch, testdir, file_test) def run_sauce_test(monkeypatch, testdir, file_test): - monkeypatch.setenv('SAUCELABS_USERNAME', 'foo') - monkeypatch.setenv('SAUCELABS_API_KEY', 'bar') + monkeypatch.setenv("SAUCELABS_USERNAME", "foo") + monkeypatch.setenv("SAUCELABS_API_KEY", "bar") - capabilities = {'browserName': 'chrome'} - variables = testdir.makefile('.json', '{{"capabilities": {}}}'.format( - json.dumps(capabilities))) + capabilities = {"browserName": "chrome"} + variables = testdir.makefile( + ".json", '{{"capabilities": {}}}'.format(json.dumps(capabilities)) + ) testdir.quick_qa( - '--driver', 'saucelabs', '--variables', - variables, file_test, passed=1) + "--driver", "saucelabs", "--variables", variables, file_test, passed=1 + ) def test_empty_sauce_options(monkeypatch, testdir): - capabilities = {'browserName': 'chrome'} - expected = {'name': 'test_empty_sauce_options.test_sauce_capabilities'} + capabilities = {"browserName": "chrome"} + expected = {"name": "test_empty_sauce_options.test_sauce_capabilities"} run_w3c_sauce_test(capabilities, expected, monkeypatch, testdir) def test_merge_sauce_options(monkeypatch, testdir): - version = {'seleniumVersion': '3.8.1'} - capabilities = {'browserName': 'chrome', 'sauce:options': version} - expected = {'name': 'test_merge_sauce_options.test_sauce_capabilities'} + version = {"seleniumVersion": "3.8.1"} + capabilities = {"browserName": "chrome", "sauce:options": version} + expected = {"name": "test_merge_sauce_options.test_sauce_capabilities"} expected.update(version) run_w3c_sauce_test(capabilities, expected, monkeypatch, testdir) def test_merge_sauce_options_with_conflict(monkeypatch, testdir): - name = 'conflict' - capabilities = {'browserName': 'chrome', 'sauce:options': {'name': name}} - expected = {'name': name} + name = "conflict" + capabilities = {"browserName": "chrome", "sauce:options": {"name": name}} + expected = {"name": name} run_w3c_sauce_test(capabilities, expected, monkeypatch, testdir) def run_w3c_sauce_test(capabilities, expected_result, monkeypatch, testdir): - username = 'foo' - access_key = 'bar' + username = "foo" + access_key = "bar" - monkeypatch.setenv('SAUCELABS_USERNAME', username) - monkeypatch.setenv('SAUCELABS_API_KEY', access_key) - monkeypatch.setenv('SAUCELABS_W3C', 'true') + monkeypatch.setenv("SAUCELABS_USERNAME", username) + monkeypatch.setenv("SAUCELABS_API_KEY", access_key) + monkeypatch.setenv("SAUCELABS_W3C", "true") - expected_result.update({'username': username, - 'accessKey': access_key, - 'tags': ['nondestructive']}) + expected_result.update( + {"username": username, "accessKey": access_key, "tags": ["nondestructive"]} + ) - variables = testdir.makefile('.json', '{{"capabilities": {}}}'.format( - json.dumps(capabilities))) + variables = testdir.makefile( + ".json", '{{"capabilities": {}}}'.format(json.dumps(capabilities)) + ) - file_test = testdir.makepyfile(""" + file_test = testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_sauce_capabilities(driver_kwargs): actual = driver_kwargs['desired_capabilities']['sauce:options'] assert actual == {} - """.format(expected_result)) + """.format( + expected_result + ) + ) testdir.quick_qa( - '--driver', 'saucelabs', '--variables', - variables, file_test, passed=1) + "--driver", "saucelabs", "--variables", variables, file_test, passed=1 + ) def test_auth_type_none(monkeypatch): from pytest_selenium.drivers.saucelabs import SauceLabs, get_job_url - monkeypatch.setenv('SAUCELABS_USERNAME', 'foo') - monkeypatch.setenv('SAUCELABS_API_KEY', 'bar') + monkeypatch.setenv("SAUCELABS_USERNAME", "foo") + monkeypatch.setenv("SAUCELABS_API_KEY", "bar") - session_id = '123456' - expected = 'https://saucelabs.com/jobs/{}'.format(session_id) - actual = get_job_url(Config('none'), SauceLabs(), session_id) + session_id = "123456" + expected = "https://saucelabs.com/jobs/{}".format(session_id) + actual = get_job_url(Config("none"), SauceLabs(), session_id) assert actual == expected -@pytest.mark.parametrize('auth_type', ['token', 'day', 'hour']) +@pytest.mark.parametrize("auth_type", ["token", "day", "hour"]) def test_auth_type_expiration(monkeypatch, auth_type): import re from pytest_selenium.drivers.saucelabs import SauceLabs, get_job_url - monkeypatch.setenv('SAUCELABS_USERNAME', 'foo') - monkeypatch.setenv('SAUCELABS_API_KEY', 'bar') + monkeypatch.setenv("SAUCELABS_USERNAME", "foo") + monkeypatch.setenv("SAUCELABS_API_KEY", "bar") - session_id = '123456' - expected_pattern = r'https://saucelabs\.com/jobs/' \ - r'{}\?auth=[a-f0-9]{{32}}$'.format(session_id) + session_id = "123456" + expected_pattern = ( + r"https://saucelabs\.com/jobs/" r"{}\?auth=[a-f0-9]{{32}}$".format(session_id) + ) actual = get_job_url(Config(auth_type), SauceLabs(), session_id) assert re.match(expected_pattern, actual) class Config(object): - def __init__(self, value): self._value = value def getini(self, key): - if key == 'saucelabs_job_auth': + if key == "saucelabs_job_auth": return self._value else: raise KeyError diff --git a/testing/test_testingbot.py b/testing/test_testingbot.py index 040b2811..f191a2fa 100644 --- a/testing/test_testingbot.py +++ b/testing/test_testingbot.py @@ -12,11 +12,13 @@ @pytest.fixture def testfile(testdir): - return testdir.makepyfile(""" + return testdir.makepyfile( + """ import pytest @pytest.mark.nondestructive def test_pass(selenium): pass - """) + """ + ) def failure_with_output(testdir, *args, **kwargs): @@ -29,52 +31,60 @@ def failure_with_output(testdir, *args, **kwargs): @pytest.fixture def failure(testdir, testfile, httpserver_base_url): - return partial(failure_with_output, testdir, testfile, httpserver_base_url, - '--driver', 'TestingBot') + return partial( + failure_with_output, + testdir, + testfile, + httpserver_base_url, + "--driver", + "TestingBot", + ) def test_missing_key(failure, monkeypatch, tmpdir): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - assert 'TestingBot key must be set' in failure() + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + assert "TestingBot key must be set" in failure() def test_missing_secret_env(failure, monkeypatch, tmpdir): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - monkeypatch.setenv('TESTINGBOT_KEY', 'foo') - assert 'TestingBot secret must be set' in failure() + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + monkeypatch.setenv("TESTINGBOT_KEY", "foo") + assert "TestingBot secret must be set" in failure() def test_missing_secret_file(failure, monkeypatch, tmpdir): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - tmpdir.join('.testingbot').write('[credentials]\nkey=foo') - assert 'TestingBot secret must be set' in failure() + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + tmpdir.join(".testingbot").write("[credentials]\nkey=foo") + assert "TestingBot secret must be set" in failure() -@pytest.mark.parametrize(('key', 'secret'), [('TESTINGBOT_KEY', - 'TESTINGBOT_SECRET'), - ('TESTINGBOT_PSW', - 'TESTINGBOT_USR')]) +@pytest.mark.parametrize( + ("key", "secret"), + [("TESTINGBOT_KEY", "TESTINGBOT_SECRET"), ("TESTINGBOT_PSW", "TESTINGBOT_USR")], +) def test_invalid_credentials_env(failure, monkeypatch, tmpdir, key, secret): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - monkeypatch.setenv(key, 'foo') - monkeypatch.setenv(secret, 'bar') + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + monkeypatch.setenv(key, "foo") + monkeypatch.setenv(secret, "bar") out = failure() - messages = ['incorrect TestingBot credentials', 'basic auth failed'] + messages = ["incorrect TestingBot credentials", "basic auth failed"] assert any(message in out for message in messages) def test_invalid_credentials_file(failure, monkeypatch, tmpdir): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - tmpdir.join('.testingbot').write('[credentials]\nkey=foo\nsecret=bar') + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + tmpdir.join(".testingbot").write("[credentials]\nkey=foo\nsecret=bar") out = failure() - messages = ['incorrect TestingBot credentials', 'basic auth failed'] + messages = ["incorrect TestingBot credentials", "basic auth failed"] assert any(message in out for message in messages) def test_invalid_host(failure, monkeypatch, tmpdir): - monkeypatch.setattr(os.path, 'expanduser', lambda p: str(tmpdir)) - tmpdir.join('.testingbot').write('[credentials]\nkey=foo\nsecret=bar') - out = failure('--host', 'foo.bar.com') - messages = ['nodename nor servname provided, or not known', - 'Name or service not known'] + monkeypatch.setattr(os.path, "expanduser", lambda p: str(tmpdir)) + tmpdir.join(".testingbot").write("[credentials]\nkey=foo\nsecret=bar") + out = failure("--host", "foo.bar.com") + messages = [ + "nodename nor servname provided, or not known", + "Name or service not known", + ] assert any(message in out for message in messages) diff --git a/testing/test_webdriver.py b/testing/test_webdriver.py index 606a40aa..16dbed50 100644 --- a/testing/test_webdriver.py +++ b/testing/test_webdriver.py @@ -3,14 +3,14 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. import pytest -from selenium.webdriver.support.abstract_event_listener import \ - AbstractEventListener +from selenium.webdriver.support.abstract_event_listener import AbstractEventListener pytestmark = pytest.mark.nondestructive def test_event_listening_webdriver(testdir, httpserver): - file_test = testdir.makepyfile(""" + file_test = testdir.makepyfile( + """ import pytest from selenium.webdriver.support.event_firing_webdriver import \ EventFiringWebDriver @@ -21,12 +21,16 @@ def test_selenium(base_url, selenium): with pytest.raises(Exception) as e: selenium.get(base_url) assert 'before_navigate_to' in e.exconly() - """) - testdir.quick_qa('--event-listener', 'testing.' - 'test_webdriver.ConcreteEventListener', - file_test, passed=1) + """ + ) + testdir.quick_qa( + "--event-listener", + "testing." "test_webdriver.ConcreteEventListener", + file_test, + passed=1, + ) class ConcreteEventListener(AbstractEventListener): def before_navigate_to(self, url, driver): - raise Exception('before_navigate_to') + raise Exception("before_navigate_to") diff --git a/tox.ini b/tox.ini index 5dc01408..3b1f8d45 100644 --- a/tox.ini +++ b/tox.ini @@ -25,4 +25,5 @@ deps = flake8 commands = flake8 {posargs:.} [flake8] +max-line-length = 88 exclude = .eggs,.tox,docs