{"id":72,"date":"2019-06-17T19:19:19","date_gmt":"2019-06-17T19:19:19","guid":{"rendered":"https:\/\/www.bddtesting.com\/?page_id=72"},"modified":"2019-06-17T19:20:07","modified_gmt":"2019-06-17T19:20:07","slug":"using-the-behave-framework-for-selenium-bdd-testing-a-tutorial","status":"publish","type":"page","link":"https:\/\/www.bddtesting.com\/using-the-behave-framework-for-selenium-bdd-testing-a-tutorial\/","title":{"rendered":"Using the behave Framework for Selenium BDD Testing: A Tutorial"},"content":{"rendered":"\n
Let\u2019s say you have a task to automate the testing of an application. Where should you start? The first step is to choose an approach to test automation<\/a>, which will be the basis for your test development. When you are searching for possible options, you will find out that there are many of them, like unit testing, test-driven development, keyword-driven development, behavior-driven development and so on. In this article, we are going to talk about one of the most popular approaches to test automation \u2013 BDD or behavior-driven development. Follow the examples here on GitHub.<\/a><\/p>\n\n\n\n I suspect you might have a question here: \u201cThere is nothing about testing in the technique\u2019s name, so how it can be used for testing?\u201d. BDD originates from the test-driven development technique (TDD). This technique defines that before any functionality is implemented, tests should be created first. Usually TDD is useful for short term iterations when you need to keep your functionality safe from regression for a single unit that is under development.<\/p>\n\n\n\n But what about integration with other modules? Integration tests are more complex and require more knowledge and time to implement them. As this point when we need to turn our focus to BDD, where instead of module tests, behavior tests are automated.<\/p>\n\n\n\n What are considered as \u201cbehavior tests\u201d? Behavior tests come from specification and business requirements. Business stakeholders, QA engineers, analysts, application and test developers work together to identify the correct flow and test it. With this approach, every new requirement and functionality can be added so they are covered by tests in the same iteration. Seems promising!<\/p>\n\n\n\n Let\u2019s have a look at BDD in action. In python, the behave<\/a> framework is a great implementation of that technique. Scenarios in behave are written using the Gherkin syntax. A simple scenario in Gherkin looks like this:<\/p>\n\n\n\n Let\u2019s automate a scenario for http:\/\/blazedemo.com\/<\/a> and verify if the user can find flights from Paris to London. The scenario will look like this:<\/p>\n\n\n\n To automate the test, we will need:<\/p>\n\n\n\n 1. Python 2.7.14 or above. You can download it from here<\/a>. There are two major versions of python nowadays: 2.7.14 and 3.6.4. Code snippets in the blog post will be given for version 2.7.14. If there is any difference for version 3.6.4, a note will be made with appropriate changes to version 3.6.4. It\u2019s up to the reader to choose which version to install.<\/p>\n\n\n\n 2. To install python package manager (pip). It can be downloaded from its download page.<\/a> All further installations in the blog post will make use of pip so it\u2019s highly recommended to install it.<\/p>\n\n\n\n 3. A development environment. The PyCharm Community edition will be used in this blog post. You can download it from the Pycharm website<\/a>. You can use any IDE of your choice since code snippets are not IDE dependent.<\/p>\n\n\n\n Now, let\u2019s create our project.<\/p>\n\n\n\n 4. Create the project in PyCharm IDE with File -> New Project.<\/em><\/p>\n\n\n\n 5. Specify the location for the project (the last part of the path will be the project\u2019s name).<\/p>\n\n\n\n When developing a python application, it\u2019s a good practice to isolate its dependencies from others. By doing this, you can be sure that you are using the right version of the library in case there are multiple versions of it in your PYTHON_PATH. (The PYTHON_PATH variable is used in python applications to find any dependency that is declared via import statement in python modules).<\/p>\n\n\n\n To do this, you need to install the virtual environment.<\/p>\n\n\n\n 6. Install the Virtual Environment<\/a> tool with the command pip install virtualenv<\/strong> in the prompt.<\/p>\n\n\n\n 7. In the project root directory, create the environment with virtualenv BLAZEDEMO<\/strong> in the prompt where BLAZEDEMO<\/strong> is the environment\u2019s name.<\/p>\n\n\n\n You will notice that in the project root you have a new directory created \u2013 BLAZEDEMO. This is the folder of your virtual environment. Once activated, the python interpreter will be used to switch to the one available in the virtual environment. Also, all packages and dependencies will be installed within the virtual environment and will not be available outside of it.<\/p>\n\n\n\n 8. To activate it, in POSIX systems run the source bin\/activate<\/strong> command from the environment root folder. In Windows systems, go to environment folder -> Scripts<\/em> and execute activate.bat<\/strong> from the prompt.<\/p>\n\n\n\n If the environment is activated, your prompt will be prefixed with the environment\u2019s name as below:<\/p>\n\n\n\n Now, we can install all the packages that are required to automate our first BDD scenario.<\/p>\n\n\n\n 9. Install \u2018behave\u2019 by executing \u2018pip install behave\u2019 in the prompt.<\/p>\n\n\n\n 10. Since our application is a web application, we will need a tool that helps us interact with the Graphical User Interface<\/a>. Selenium will make a perfect match here since it enables interacting with any element on a web page just like a user would: clicking on buttons, typing into text fields and so on. Additionally, Selenium<\/a> has wide support of all popular browsers, and good and full documentation, which make it an easy-to-use tool. To install it, just execute \u2018pip install selenium\u2019 in the prompt.<\/p>\n\n\n\n In behave, all scenarios are kept in feature files that should be put in the features directory.<\/p>\n\n\n\n Let\u2019s create flight_search.feature and put the scenario we created earlier into it.<\/p>\n\n\n\n All scenario steps must have an implementation. But we need to take care of a few things before:<\/p>\n\n\n\n First, define a browser to run scenarios in. As Selenium only defines the interface to interact with a browser and a few useful tools, the actual implementation is performed by WebDriver<\/a>. Each browser has its own implementation of WebDriver. Download all the implementations you need from the download page<\/a>, i.e. for Chrome browser you will need ChromeDriver. Select the appropriate version depending on your operating system.<\/p>\n\n\n\n Once WebDriver is downloaded, we need to define the way to access it. For web applications, there is one thing we need to take care of: since elements are not loaded simultaneously, we need to wait until they become available. Web driver itself doesn\u2019t wait for elements to be accessible \u2013 it tries to get them immediately. To bypass this, Selenium has a WebDriverWait<\/a> class that explicitly waits for elements by different conditions. Create a new python directory web and add web.py there.<\/p>\n\n\n\n Here is the web.py script for waiting for elements:<\/p>\n\n\n\n In the constructor, there is the instance variable self._web_driver_wait that references to the instance of the WebDriverWait class. In the method find_by_xpath we use self._web_driver_wait to wait until the element, which can be found by xpath, becomes visible on a web page. The same goes for the method finds_by_xpath with the difference that it searches for multiple elements to be present on a web page. The method \u2018open\u2019 simply opens a web page by url.<\/p>\n\n\n\n As the final step we need to take care of when a Web instance should be initialized. We need to remember that with every instance of the web driver there is a new instance of the corresponding web browser. I believe you don\u2019t want to start a new browser every time for every single test; except rare cases when you need to have a clean browser, without cache or any local stored data. This behavior can be achieved in two ways:<\/p>\n\n\n\n To understand how these two approaches work, we need to be familiar with one important behave feature \u2013 Context. Think about a context as an object that can store any user-defined data, together with behave-defined data, in context attributes.<\/p>\n\n\n\n Depending on the level of execution: feature, scenario or test, behave will add specific attributes to context, such as: feature, store the currently executed feature, scenario, store the currently executed scenario and so on. Also, you can access the result of a test, test parameters and the environment. You can browse all the possible behave-based attributes in Context in the behave API documentation.<\/a><\/p>\n\n\n\n Ok, let\u2019s start with fixture implementation in the fixtures.py file<\/p>\n\n\n\n Here, we have browser_chrome(context, timeout=30, **kwargs) function that will be used as a fixture to start a web browser.<\/p>\n\n\n\n This function starts a webdriver instance that starts a Chrome browser. Then we create the instance of the Web class to access web elements on webpages. Later, we create a new attribute in context \u2013 web, that can be referenced further in our steps implementations to access the Web instance. By using yield and browser.quit() in the next line, we are making sure that the browser will be closed after all the tests that use browser_chrome are complete.<\/p>\n\n\n\n Let\u2019s see this in action and provide the implementation for the steps in the scenario we defined earlier in the flight_search.feature file. In the features directory of the project create a new python directory, steps, and add flight_search_steps.py there.<\/p>\n\n\n\n Every step that is mentioned in the flight_search.feature file has an implementation here. In every step we reference a web instance through the context. This is a convenient way to access a shared resource without taking care of the way it was initialized, isn\u2019t it?<\/p>\n\n\n\n To apply a fixture, we need to define the behavior for the before_tag option in the features\/environment.py file.<\/p>\n\n\n\n As you can see, if a tag equals \u201cfixture.browser.chrome\u201d then we execute the browser_chrome fixture by calling the use_fixture method. To try it out, in your project root directory, execute \u201cbehave\u201d in the command line. The output should look like this:<\/p>\n\n\n\n There are two main disadvantages to loading a webdriver with this fixtures approach:<\/p>\n\n\n\n 1. Fixtures have a scope that is defined by the scope of the tag \u201c@fixture.*\u201d If we are using @fixture in a scenario, then the web browser will be opened and closed for every scenario with the @fixture tag. The same happens if @fixture is applied to a feature. So, if we have multiple features, the browser will start and close for every feature. This is not good if we don\u2019t want our features to be executed in parallel, but a great option otherwise, by the way.<\/p>\n\n\n\n 2. We need to assign a @fixture tag to every scenario or feature that is supposed to have access to a web page. This is not a good option. Moreover, what if we want to switch to another browser? Do we need to go over all the features and modify @fixture.browser every time?! This issue can be solved by applying the fixture in before_all(context) function from environment.py. like this:<\/p>\n\n\n\n But it looks like we are searching for a solution for the issue with instruments that can be used themselves without fixtures. Let\u2019s have a look at how.<\/p>\n\n\n\n Behave gives us the option to define configurations in configuration files that can be called either \u201c.behaverc\u201d, \u201cbehave.ini\u201d, \u201csetup.cfg\u201d or \u201ctox.ini\u201d (your preference) and are located in one of three places:<\/p>\n\n\n\n There are a number of configurations that are used by behave to setup your environment. But you can also use this file to identify your own environment variables, such as a browser.<\/p>\n\n\n\n Besides the browser, we have stderr_capture and stdout_capture set to False. By default those parameters are set to True, which means that behave will not print any message to the console, or any other output you specified, if the test is not failed. Setting to False will force behave to print any output even if the test passed. This is a great option if you need to see what is going on in your tests.<\/p>\n\n\n\n Earlier in the blog post, I mentioned there were two ways to start a webdriver. The first one is to use fixtures. We have already defined how to do it. The second option is to use environment functions. Let\u2019s see how to do it.<\/p>\n\n\n\n We will get access to a Web instance through the before_all environment function in environment.py file. To do that, at first create the web_source\/web_factory.py file.<\/p>\n\n\n\n The code is quite simple and straightforward.<\/p>\n\n\n\n In the environment.py file for before_all(context):<\/p>\n\n\n\n Here, in the code, we are getting the currently set browser from the \u201cbrowser\u201d variable defined in behave.ini in the [behave.userdata] section.<\/p>\n\n\n\n You can try out this code by executing \u201cbehave\u201d in the command line.<\/p>\n\n\n\n This approach is more flexible since we don\u2019t need to modify feature files to switch to another browser.<\/p>\n\n\n\n But then you can ask: when should I use fixtures? Fixtures are a good option if your initialization depends on the environment you are currently on. For example, if for the development environment you would like to setup a connection to one database, and for the production environment you would like to setup another connection. This can be achieved in the following way:<\/p>\n\n\n\n For any feature and scenario tagged with @environment.develop or @environment.production, in the before_tag environment function, the appropriate fixture will be loaded and executed as defined in fixture_registry.<\/p>\n\n\n\n If you don\u2019t know if you should be using fixtures or another approach, just ask yourself: will fixtures create more issues than they solve? If answer is no, that you can use it. Basically, almost everything that is configurable by tags, can be managed by fixtures. If you have tag : slow, you can increase your timeout and then revert it back for fast test cases and so on.<\/p>\n\n\n\n In the feature from the features\/flight_search.feature file we saw how to create a test with static data. But what if we want to search for a flight not only from Paris to London? For such purposes, you can use parameterized steps. Let\u2019s have a look at the implementation:<\/p>\n\n\n\n The feature file will be modified to a new one:<\/p>\n\n\n\n And the steps implementation for choosing cities will be changed: As you can see now the required city is loaded from parameters.<\/p>\n\n\n\n So far, we have executed our features using the simple command \u201cbehave\u201d. But the behave framework suggests different options for execution. The most useful ones:<\/p>\n\n\n\n https:\/\/www.blazemeter.com\/blog\/using-the-behave-framework-for-selenium-bdd-testing-a-tutorial\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":" Let\u2019s say you have a task to automate the testing of an application. Where should you start? The first step is to choose an approach to test automation, which will be the basis for your test development. When you are searching for possible …. Read More<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"pagelayer_contact_templates":[],"_pagelayer_content":"","_exactmetrics_skip_tracking":false,"_exactmetrics_sitenote_active":false,"_exactmetrics_sitenote_note":"","_exactmetrics_sitenote_category":0,"footnotes":""},"class_list":["post-72","page","type-page","status-publish","hentry"],"yoast_head":"\nExplaining BDD<\/h1>\n\n\n\n
BDD Scenarios in Gherkin<\/h1>\n\n\n\n
Feature: User authorization and authorization\nScenario: The user can log into the system\nGiven The user is registered in the system\nWhen The user enters a login\nAnd enters a password\nThen the user is logged in\n<\/pre>\n\n\n\n
Our BDD Scenario<\/h1>\n\n\n\n
Feature: The user can book available flights \nScenario: The user can find a flight from Paris to London\nGiven the user is on the search page\nWhen the user selects Paris as a departure city\nAnd the user selects London as a destination city\nAnd clicks on the Find Flights button\nThen flights are presented on the search result page\n<\/pre>\n\n\n\n
Prerequisites and Installations<\/h1>\n\n\n\n
<\/figure>\n\n\n\n
<\/figure>\n\n\n\n
Creating our Selenium Scenario in behave<\/h1>\n\n\n\n
<\/figure>\n\n\n\n
Waiting for Elements with WebDriverWait<\/h1>\n\n\n\n
<\/figure>\n\n\n\n
from selenium.webdriver.common.by import By\nfrom selenium.webdriver.support.wait import WebDriverWait\nfrom selenium.webdriver.support import expected_conditions as EC\n\n\nfrom selenium.webdriver.common.by import By\nfrom selenium.webdriver.support.wait import WebDriverWait\nfrom selenium.webdriver.support import expected_conditions as EC\n\n\nclass Web(object):\n __TIMEOUT = 10\n\n def __init__(self, web_driver):\n super(Web, self).__init__() # in python 3.6 you can just call super().__init__()<\/em>\n self._web_driver_wait = WebDriverWait(web_driver, Web.__TIMEOUT)\n self._web_driver = web_driver\n\n def open(self, url):\n self._web_driver.get(url)\n\n def find_by_xpath(self, xpath):\n return self._web_driver_wait.until(EC.visibility_of_element_located((By.XPATH, xpath)))\n\n def finds_by_xpath(self, xpath):\n return self._web_driver_wait.until(EC.presence_of_all_elements_located((By.XPATH, xpath)))\n \n<\/pre>\n\n\n\n
Browser Initialization<\/h1>\n\n\n\n
Browser Initialization with Fixtures<\/h1>\n\n\n\n
from selenium import webdriver\nfrom web_driver.web import Web\n\n\n\ndef browser_chrome(context, timeout=30, **kwargs):\n browser = webdriver.Chrome(\"C:\/chromedriver.exe\")\n web = Web(browser)\n context.web = web\n yield context.web\n browser.quit()\n<\/pre>\n\n\n\n
<\/figure>\n\n\n\n
from behave import given, when, then\nfrom behave.log_capture import capture\n\n\n@given(\"the user is on search page\")\ndef user_on_search_page(context):\n context.web.open(\"http:\/\/blazedemo.com\/\")\n\n\n@when(\"user selects Paris as departure city\")\ndef user_select_departure_city(context):\n context.web.find_by_xpath(\"\/\/select[@name='fromPort']\/option[text()='Paris']\").click()\n\n@when(\"user selects London as destination city\")\ndef user_select_destination_city(context):\n context.web.find_by_xpath(\"\/\/select[@name='toPort']\/option[text()='London']\").click()\n\n@when(\"clicks on Find Flights button\")\ndef user_clicks_on_find_flights(context):\n context.web.find_by_xpath(\"\/\/input[@type='submit']\").click()\n\n@then(\"flights are found\")\ndef flights_are_found(context):\n elements = context.web.finds_by_xpath(\"\/\/table\/tbody\/tr\")\n assert len(elements) > 1\n<\/pre>\n\n\n\n
<\/figure>\n\n\n\n
from behave import use_fixture\n\nfrom fixtures import browser_chrome\n\ndef before_tag(context, tag):\n if tag == \"fixture.browser.chrome\":\n use_fixture(browser_chrome, context)\n<\/pre>\n\n\n\n
<\/figure>\n\n\n\n
def before_all(context):\n if context.browser == \"chrome\":\n use_fixture(browser_chrome, context)\n<\/pre>\n\n\n\n
<\/figure>\n\n\n\n
Browser Initialization Environment Functions<\/h1>\n\n\n\n
<\/figure>\n\n\n\n
from selenium import webdriver\nfrom web.web import Web\n\n\ndef get_web(browser):\n if browser == \"chrome\":\n return Web(webdriver.Chrome(\"C:\/chromedriver.exe\"))\n<\/pre>\n\n\n\n
from web_source.web_factory import get_web\n\n\ndef before_all(context):\n web = get_web(context.config.userdata['browser'])\n context.web = web\n<\/pre>\n\n\n\n
When to Use Fixtures<\/h1>\n\n\n\n
fixture_registry = {\"develop\": develop_database,\n \"production\": production_database}\n\n\ndef before_tag(context, tag):\n if tag.startswith(\"environment\"):\n use_fixture_by_tag(tag, context, fixture_registry)\n<\/pre>\n\n\n\n
Implementing Parameterized Steps<\/h1>\n\n\n\n
Feature: The user can book available flights\n Scenario: The user can find a flight from Paris to London\n Given the user is on the search page\n When the user selects a departure city \"Paris\"\n And the user selects a destination city \"London\"\n And clicks on the Find Flights button\n Then flights are present on the search result page\n<\/pre>\n\n\n\n
<\/p>\n\n\n\n@when('the user select departure city \"{city}\"')\ndef user_select_departure_city(context, city):\n context.web.find_by_xpath(\"\/\/select[@name='fromPort']\/option[text()='{}']\".format(city)).click()\n\n@when('the user select destination city \"{city}\"')\ndef user_select_destination_city(context, city):\n context.web.find_by_xpath(\"\/\/select[@name='toPort']\/option[text()='{}']\".format(city)).click()\n \n<\/pre>\n\n\n\n
Execution Commands<\/h1>\n\n\n\n