{"id":4629,"date":"2016-08-23T01:04:03","date_gmt":"2016-08-23T05:04:03","guid":{"rendered":"https:\/\/qxf2.com\/blog\/?p=4629"},"modified":"2021-02-16T06:18:49","modified_gmt":"2021-02-16T11:18:49","slug":"selenium-cross-browser-cross-platform-pytest","status":"publish","type":"post","link":"https:\/\/qxf2.com\/blog\/selenium-cross-browser-cross-platform-pytest\/","title":{"rendered":"pytest: Cross browser, cross platform Selenium tests"},"content":{"rendered":"<p>We showed you how to run tests on <a href=\"https:\/\/en.wikipedia.org\/wiki\/BrowserStack\">BrowserStack<\/a> using pytest in our previous post &#8211; <a href=\"https:\/\/qxf2.com\/blog\/pytest-and-browserstack\/\">pytest and Browserstack<\/a>. We had an example test and ran it across one browser from the command line argument. What if we need to run it across different browser versions, platforms and platform versions?<\/p>\n<h3>Why this post?<\/h3>\n<p>pytest responds really well when we run it for one combination of browser, browser version, platform, and platform version. But to make it run across a combination of different browsers, browser versions, platforms and platform versions is a bit tricky. It is tricky because not all platforms support all browsers. E.g.: Safari on Windows does not make much sense. Further, your company may have promised different browser versions on different platforms as part of the minimum browser requirements. We are writing this post to explain a clean, maintainable way to run your Selenium tests against different browsers, browser versions, platforms and platform versions.<\/p>\n<hr>\n<h3>Overview<\/h3>\n<p>To get your tests running against a combination of browsers, browser versions and platforms, you need to do the following:<br \/>\n1. Parameterize the test in <code>conftest.py<\/code><br \/>\n2. Generate the different browser\/OS combinations you want<br \/>\n3. Generate the tests using <code>pytest_generate_tests<\/code><br \/>\n4. Put it all together<br \/>\n5. Run the test<\/p>\n<p>The rest of the post explains each of these steps in detail.<\/p>\n<hr>\n<h4>1. Parameterize the test in <code>conftest.py<\/code><\/h4>\n<p>pytest allows easy parameterization of tests. To run a test against a list of browsers,<\/p>\n<pre lang=\"python\">\r\n#contents of conftest.py\r\ndef pytest_generate_tests(metafunc):\r\n    \"test generator function to run tests across different parameters\"\r\n    if \"browser\" in metafunc.fixturenames:\r\n        metafunc.parameterize(\"browser\",metafunc.config.option.browser)\r\n<\/pre>\n<p>This function would parameterize the test if it finds the browser argument in the test function.<br \/>\nThe corresponding changes that have to be made to the browser option are,<\/p>\n<pre lang=\"python\">\r\n#Contents of conftest.py\r\nparser.addoption(\"-B\",\"--browser\",\r\n                 dest=\"browser\",\r\n                 action=\"append\",\r\n                 default=[\"firefox\"],\r\n                 help=\"Browser. Valid options are firefox, ie and chrome\")\r\n<\/pre>\n<p>Now running the tests using the command <code>py.test<\/code> would run it across <code>Firefox<\/code> by default. <code>py.test -B chrome<\/code> command would append Chrome to the browser list for the test to run against. Hence the test would run against Firefox and Chrome.<\/p>\n<hr>\n<h4>2. Generate the different browser\/OS combinations you want<\/h4>\n<p>Let us create a conf file to store the list of browsers, browser versions, platforms and platform versions for the test to run against. This conf file would hold a method to create a tuple of browser-appropriate browser versions and platform-appropriate platform versions. <\/p>\n<pre lang=\"python\">\r\n#contents of the browser_platform_conf\r\n\"\"\"\r\nThis is a contrived version of the browser_platfor_conf\r\nWe use a more detailed version in our code for clients\r\n\"\"\"\r\nbrowsers = [\"chrome\"]\r\nos_list = [\"windows\",\"OS X\"]\r\nchrome_versions = [\"49\",\"50\"]\r\nwindows_versions = [\"7\",\"8.1\"]\r\nos_x_versions = [\"yosemite\"]\r\ndef generate_configuration(browsers=browsers,firefox_versions=firefox_versions,chrome_versions=chrome_versions,os_list=os_list,windows_versions=windows_versions,os_x_versions=os_x_versions):\r\n\t\"Generate test configuration\"\r\n\ttest_config = []\r\n\tfor browser in browsers:\r\n\t\tif browser == \"chrome\":\r\n\t\t\tfor chrome_version in chrome_versions:\r\n\t\t\t\tfor os_name in os_list:\r\n\t\t\t\t\tif os_name == \"windows\":\r\n\t\t\t\t\t\tfor windows_version in windows_versions:\r\n\t\t\t\t\t\t\tconfig = [browser,chrome_version,os_name,windows_version]\r\n\t\t\t\t\t\t\ttest_config.append(tuple(config))\r\n\t\t\t\t\tif os_name == \"OS X\":\r\n\t\t\t\t\t\tfor os_x_version in os_x_versions:\r\n\t\t\t\t\t\t\tconfig = [browser,chrome_version,os_name,os_x_version]\r\n\t\t\t\t\t\t\ttest_config.append(tuple(config))\r\n\r\n\r\n\r\n\treturn test_config\r\n\r\n\r\ncross_browser_cross_platform_config = generate_configuration()\r\n<\/pre>\n<h4>3. Generate the tests using <code>pytest_generate_tests<\/code><\/h4>\n<p>pytest has a method called <code>pytest_generate_tests<\/code>. This method will &#8216;generate&#8217; the same test for each of our browser\/OS combinations. All we need to do, is to provide <code>pytest_generate_tests<\/code> with the different browser\/OS combinations. The code snippet to do this, is:<\/p>\n<pre lang=\"pytest\">\r\n#contents of conftest.py\r\nfrom conf import browser_platform_conf\r\n\r\ndef pytest_generate_tests(metafunc):\r\n    \"test generator function to run tests across different parameters\"\r\n    if 'browser' in metafunc.fixturenames:\r\n        if metafunc.config.getoption(\"-M\").lower() == 'y':\r\n            if metafunc.config.getoption(\"-B\") == [\"all\"]:\r\n                metafunc.parametrize(\"browser,browser_version,platform,os_version\", \r\n                                    browser_platform_conf.cross_browser_cross_platform_config)\r\n<\/pre>\n<p>Refer <a href=\"http:\/\/doc.pytest.org\/en\/latest\/example\/parametrize.html\">this link<\/a> to know more about <code>pytest_generate_tests<\/code> and the <code>metafunc<\/code> object.<\/p>\n<hr>\n<h4>4. Put it all together<\/h4>\n<p>Now to run the test, have the following files that look like the corresponding code below:<br \/>\na. browser_platform_conf.py<br \/>\nb. conftest.py<br \/>\nc. Test file<\/p>\n<p><strong>4a. browser_platform_conf.py<\/strong><br \/>\nThe <code>browser_platform_conf<\/code> file to generate the test run configuration<\/p>\n<pre lang=\"python\">\r\n\"\"\"\r\nThis is a contrived version of the browser_platform_conf\r\nWe use a more detailed version in our code for clients\r\n\"\"\"\r\nbrowsers = [\"chrome\"]\r\nos_list = [\"windows\",\"OS X\"]\r\nchrome_versions = [\"49\",\"50\"]\r\nwindows_versions = [\"7\",\"8.1\"]\r\nos_x_versions = [\"yosemite\"]\r\n\r\n\r\ndef generate_configuration(browsers=browsers,firefox_versions=firefox_versions,chrome_versions=chrome_versions,os_list=os_list,windows_versions=windows_versions,os_x_versions=os_x_versions):\r\n\r\n\t\"Generate test configuration\"\r\n\ttest_config = []\r\n\tfor browser in browsers:\r\n\t\tif browser == \"chrome\":\r\n\t\t\tfor chrome_version in chrome_versions:\r\n\t\t\t\tfor os_name in os_list:\r\n\t\t\t\t\tif os_name == \"windows\":\r\n\t\t\t\t\t\tfor windows_version in windows_versions:\r\n\t\t\t\t\t\t\tconfig = [browser,chrome_version,os_name,windows_version]\r\n\t\t\t\t\t\t\ttest_config.append(tuple(config))\r\n\t\t\t\t\tif os_name == \"OS X\":\r\n\t\t\t\t\t\tfor os_x_version in os_x_versions:\r\n\t\t\t\t\t\t\tconfig = [browser,chrome_version,os_name,os_x_version]\r\n\t\t\t\t\t\t\ttest_config.append(tuple(config))\r\n\r\n\r\n\r\n\treturn test_config\r\n\r\n\r\ncross_browser_cross_platform_config = generate_configuration()\r\n<\/pre>\n<p><strong>4b. conftest.py<\/strong><br \/>\nThe <code>conftest.py<\/code> file to hold the fixtures and command line arguments and the <code>pytest_generate_test<\/code> to parametrize the test<\/p>\n<pre lang=\"python\">\r\nimport pytest\r\nimport os\r\nimport browser_platform_conf\r\n\r\n\r\n@pytest.fixture\r\ndef browser():\r\n    \"pytest fixture for browser\"\r\n    return pytest.config.getoption(\"-B\")\r\n\r\n\r\n@pytest.fixture\r\ndef browserstack_flag():\r\n    \"pytest fixture for browserstack flag\"\r\n    return pytest.config.getoption(\"-M\")\r\n\r\n\r\n@pytest.fixture\r\ndef browser_version():\r\n    \"pytest fixture for browser version\"\r\n    return pytest.config.getoption(\"-V\") \r\n\r\n\r\n@pytest.fixture\r\ndef platform():\r\n    \"pytest fixture for platform\"\r\n    return pytest.config.getoption(\"-P\") \r\n\r\n\r\n@pytest.fixture\r\ndef os_version():\r\n    \"pytest fixture for os version\"\r\n    return pytest.config.getoption(\"-O\") \r\n\r\n\r\ndef pytest_generate_tests(metafunc):\r\n    \"test generator function to run tests across different parameters\"\r\n    if 'browser' in metafunc.fixturenames:\r\n        if metafunc.config.getoption(\"-M\").lower() == 'y':\r\n            if metafunc.config.getoption(\"-B\") == [\"all\"]:\r\n                metafunc.parametrize(\"browser,browser_version,platform,os_version\", \r\n                                    browser_platform_conf.cross_browser_cross_platform_config)\r\n\r\n\r\ndef pytest_addoption(parser):\r\n    parser.addoption(\"-B\",\"--browser\",\r\n                      dest=\"browser\",\r\n                      action=\"append\",\r\n                      default=[],\r\n                      help=\"Browser. Valid options are firefox, ie and chrome\")                      \r\n    parser.addoption(\"-M\",\"--browserstack_flag\",\r\n                      dest=\"browserstack_flag\",\r\n                      default=\"N\",\r\n                      help=\"Run the test in Browserstack: Y or N\")\r\n    parser.addoption(\"-O\",\"--os_version\",\r\n                      dest=\"os_version\",\r\n                      action=\"append\",\r\n                      help=\"The operating system: xp, 7\",\r\n                      default=[])\r\n    parser.addoption(\"-V\",\"--ver\",\r\n                      dest=\"browser_version\",\r\n                      action=\"append\",\r\n                      help=\"The version of the browser: a whole number\",\r\n                      default=[])\r\n    parser.addoption(\"-P\",\"--platform\",\r\n                      dest=\"platform\",\r\n                      action=\"append\",\r\n                      help=\"The operating system: Windows 7, Linux\",\r\n                      default=[])\r\n\r\n\r\n\r\n<\/pre>\n<p><strong>4c. Test file<\/strong><br \/>\nTest file to test the <a href=\"https:\/\/qxf2.com\/selenium-tutorial-main\">selenium-tutorial-main<\/a> from the previous post<\/p>\n<pre lang=\"python\">\r\n#Contents of test file\r\n\"\"\"\r\nQxf2 Services: Utility script to test example form on Browserstack\r\nNOTE: This is a contrived example that was written up to make this blog post clear\r\nWe do not use this coding pattern at our clients\r\n\"\"\"\r\n \r\nfrom selenium import webdriver\r\nfrom selenium.webdriver.common.desired_capabilities import DesiredCapabilities\r\nimport sys,time\r\n\r\n \r\ndef test_example_form(browser,browserstack_flag,browser_version,platform,os_version,):\r\n    \"Test example form\"\r\n    #Create an instance of Driver_Factory\r\n    driver = get_webdriver(browser,browser_version,platform,os_version)\r\n    #Create variables to keep count of pass\/fail\r\n    pass_check_counter = 0\r\n    total_checks = 0\r\n    #Visit the tutorial page\r\n    driver.get('http:\/\/qxf2.com\/selenium-tutorial-main') \r\n    #Check 1: Is the page title correct?\r\n    if(driver.title==\"Qxf2 Services: Selenium training main\"):\r\n        print \"\\n\"\r\n        print (\"Success: Title of the Qxf2 Tutorial page is correct\")\r\n        pass_check_counter += 1\r\n    else:\r\n        print (\"Failed: Qxf2 Tutorial page Title is incorrect\")\r\n    total_checks += 1 \r\n    #Check 2: Fill name, email and phone in the example form\r\n    total_checks += 1\r\n    name_field = driver.find_element_by_xpath(\"\/\/input[@type='name']\")\r\n    name_field.send_keys('Shivahari')\r\n    email_field = driver.find_element_by_xpath(\"\/\/input[@type='email']\")\r\n    email_field.send_keys('test@qxf2.com')\r\n    phone_field = driver.find_element_by_xpath(\"\/\/input[@type='phone']\")\r\n    phone_field.send_keys('9999999999')\r\n    submit_button = driver.find_element_by_xpath(\"\/\/button[@type='submit']\") #Click on the Click me button\r\n    try:\r\n        submit_button.click()\r\n        pass_check_counter += 1\r\n    except Exception,e:\r\n        print str(e)\r\n    #Quit the browser window\r\n    driver.quit() \r\n    #Assert if the pass and fail check counters are equal\r\n    assert total_checks == pass_check_counter \r\n\r\n\r\n#NOTE: This is highly simplified code to make this post illustrative\r\n#We do not use this code at clients\r\n#We use Driver_Factory to return apporpriate drivers within our framework\r\ndef get_webdriver(browser,browser_version,platform,os_version):\r\n    \"Run the test in browser stack browser stack flag is 'Y'\"\r\n    USERNAME = user_name #We fetch values from a conf file in our framework we use on our clients\r\n    PASSWORD = access_key\r\n    if browser.lower() == 'firefox':\r\n        desired_capabilities = DesiredCapabilities.FIREFOX\r\n    if browser.lower() == 'chrome':\r\n        desired_capabilities = DesiredCapabilities.CHROME\r\n    desired_capabilities['os'] = platform\r\n    desired_capabilities['os_version'] = os_version\r\n    desired_capabilities['browser_version'] = browser_version\r\n \r\n    return webdriver.Remote(command_executor='http:\/\/%s:%s@hub.browserstack.com:80\/wd\/hub'%(USERNAME,PASSWORD),\r\n                            desired_capabilities=desired_capabilities)\r\n \r\n<\/pre>\n<hr>\n<h4>5. Run the test<\/h4>\n<p>Run the test using the command <code>py.test -v -M y -B all<\/code> where,<br \/>\n<code>-v<\/code> is the verbose flag,<br \/>\n<code>-M<\/code> is the Browserstack flag<br \/>\n<code>-B<\/code> is the Browser option<\/p>\n<p><a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2016\/08\/test_run-4.png\" data-rel=\"lightbox-image-0\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2016\/08\/test_run-4.png\" alt=\"test_run\" width=\"806\" height=\"173\" class=\"aligncenter size-full wp-image-4640\" srcset=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2016\/08\/test_run-4.png 806w, https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2016\/08\/test_run-4-300x64.png 300w, https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2016\/08\/test_run-4-768x165.png 768w\" sizes=\"auto, (max-width: 806px) 100vw, 806px\" \/><\/a><\/p>\n<hr>\n<p>And from now, all you need to do to add new browsers, browser versions, platforms is to edit the browser_platform_conf.py file. Happy testing!<\/p>\n<p><strong>If you liked what you read, know more <a href=\"https:\/\/qxf2.com\/blog\/about-qxf2\/\">about Qxf2<\/a>.<\/strong><\/p>\n<hr>\n<script>(function() {\n\twindow.mc4wp = window.mc4wp || {\n\t\tlisteners: [],\n\t\tforms: {\n\t\t\ton: function(evt, cb) {\n\t\t\t\twindow.mc4wp.listeners.push(\n\t\t\t\t\t{\n\t\t\t\t\t\tevent   : evt,\n\t\t\t\t\t\tcallback: cb\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n})();\n<\/script><!-- Mailchimp for WordPress v4.10.1 - https:\/\/wordpress.org\/plugins\/mailchimp-for-wp\/ --><form id=\"mc4wp-form-1\" class=\"mc4wp-form mc4wp-form-6165 mc4wp-form-theme mc4wp-form-theme-blue\" method=\"post\" data-id=\"6165\" data-name=\"Newsletter\" ><div class=\"mc4wp-form-fields\"><div style=\"border:3px; border-style:dashed;border-color:#56d1e1;padding:1.2em;\">\r\n  <h1 style=\"text-align: center; padding-top: 20px; padding-bottom: 20px; color: #592b1b;\">Subscribe to our weekly Newsletter<\/h1>\r\n  <input style=\"margin: auto;\" type=\"email\" name=\"EMAIL\" placeholder=\"Your email address\" required \/>\r\n  <br>\r\n  <p style=\"text-align: center;\">\r\n    <input style=\"background-color: #890c06 !important; border-color: #890c06;\" type=\"submit\" value=\"Sign up\" \/>\r\n    \r\n  <\/p>\r\n  <p style=\"text-align: center;\">\r\n    <a href=\"http:\/\/mailchi.mp\/c9c7b81ddf13\/the-informed-testers-newsletter-20-oct-2017\"><small>View a sample<\/small><\/a>\r\n  <\/p>\r\n  <br>\r\n<\/div><\/div><label style=\"display: none !important;\">Leave this field empty if you're human: <input type=\"text\" name=\"_mc4wp_honeypot\" value=\"\" tabindex=\"-1\" autocomplete=\"off\" \/><\/label><input type=\"hidden\" name=\"_mc4wp_timestamp\" value=\"1776097537\" \/><input type=\"hidden\" name=\"_mc4wp_form_id\" value=\"6165\" \/><input type=\"hidden\" name=\"_mc4wp_form_element_id\" value=\"mc4wp-form-1\" \/><div class=\"mc4wp-response\"><\/div><\/form><!-- \/ Mailchimp for WordPress Plugin -->\n<hr>\n","protected":false},"excerpt":{"rendered":"<p>We showed you how to run tests on BrowserStack using pytest in our previous post &#8211; pytest and Browserstack. We had an example test and ran it across one browser from the command line argument. What if we need to run it across different browser versions, platforms and platform versions? Why this post? pytest responds really well when we run [&hellip;]<\/p>\n","protected":false},"author":9,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[40,107,18,30],"tags":[],"class_list":["post-4629","post","type-post","status-publish","format-standard","hentry","category-browserstack","category-pytest","category-python","category-selenium"],"_links":{"self":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/4629","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/users\/9"}],"replies":[{"embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/comments?post=4629"}],"version-history":[{"count":23,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/4629\/revisions"}],"predecessor-version":[{"id":14844,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/4629\/revisions\/14844"}],"wp:attachment":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/media?parent=4629"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/categories?post=4629"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/tags?post=4629"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}