Selenium WebDriver is a free open source package for automating interactions with a live browser.
WebDriver itself is a W3C standard, and the Selenium project is one of the most popular implementations. WebDriver can handle every type of web UI interaction such as clicking, typing and scraping text.
Many people treat “Selenium WebDriver” as almost synonymous with web UI testing. Selenium releases WebDriver packages in many popular programming languages.
The lower case
selenium package is Python's implementation.
It's API is very similar to packages from other languages like Java and C#, just with a little bit of a Python accent.
If you ever get stuck on Python's WebDriver API, check out this read the docs page. I've always found it very helpful.
Now you might be wondering what browser types Selenium WebDriver can handle.
Good news, Selenium supports all major browsers on all major operating systems.
That includes Chrome, Firefox, Safari, Internet Explorer, Edge and Opera.
Just be warned that each browser has its own quirks, so you might need to write certain automated calls differently depending on the target browser.
Selenium WebDriver by itself is just a programming package — it needs a proxy server to communicate with live browsers.
For example, it needs ChromeDriver to work with Chrome, and it needs GeckoDriver to work with Firefox.
These extra drivers can be downloaded from their project pages. You'll need to install them onto your system path of your test machine.
Many people overlook this step when they first set up a new test automation solution, and then they don't know why their web UI tests won't open the browser.
If you need help installing these drivers, please follow the "README" instructions from our GitHub project.
As a warning though, be wary of browser versions. Browser vendors frequently release updates, and version incompatibilities between browsers and drivers can break your tests. Believe me, I've been burned a number of times before.
Just make sure to stay on top of updates, and make sure your driver versions match your browser versions.
Before jumping back into the code, there's one more thing we should discuss about WebDriver.
Every test case should have its own WebDriver instance.
Each test should initialize a fresh WebDriver instance as part of setup, and then it should quit the instance as part of cleanup, no matter what. Why is this so important?
Test cases should be independent from each other.
That means no test case should share any resources or dependencies with another. Otherwise, problems in one test case could impact others.
Sharing a WebDriver instance might seem like a nifty way to gain efficiency, but it's dangerous. Plus, tests that share WebDriver instances can't easily be run in parallel.
Quitting the WebDriver at the end of the test is also crucial.
Without explicit quits, web drivers and browsers might become zombie processes. They could lock system resources.
Make sure WebDriver instances get
quit no matter what, regardless of any failures or exceptions.
Let's jump back into our project now to apply what we've learned about WebDriver.
Here at the command line, I'm going to make sure that my ChromeDriver and GeckoDriver are set up correctly.
I’m just going to check the ChromeDriver version (using
chromedriver –version) and it looks like I'm up to date. And I'll do the same with GeckoDriver (using
geckodriver –version). Great, that one's up to date too.
So I can see that these are both accessible from my path and they're not outdated. Like I mentioned before, if you have trouble with ChromeDriver or GeckoDriver, just check the README docs in our project.
Next, we'll need to install the Selenium package for Python.
I'll do that with
pipenv install selenium in the command line. It might take a minute or two, but
pipenv will install that package so that we can use Selenium in our project.
Now, let's add some code to set up and clean up our WebDriver instance.
Typically, with other tests frameworks, set up and cleanup methods handle Selenium WebDriver initialization and quits. However, with pytest, we don't have
cleanup methods like we do in JUnit or Python’s
We have what we call fixture functions — a fixture in pytest is a function that includes both setup and cleanup phases in one body.
It's really cool.
The fixtures belong in a special file called
conftest.py. It should be located under your “tests” directory.
The naming of this file is important because pytest looks for files of this name when it looks for things like fixtures and plugins.
With my Python editor open, I've already written our
fixture for us.
First, you'll need to add a few
We'll need to import the
pytest module as well as the
My fixture function here will be called
browser and it's been given a decorator called
@pytest.fixture to let pytest as a framework know, "Hey, this particular function is a fixture."
My fixture has 4 lines.
The first line will initialize the ChromeDriver instance.
It's very simple. I just assign the variable a value of
selenium.webdriver.Chrome(). That will initialize my ChromeDriver and connect it to a live Chrome browser session.
The second line sets an implicit wait.
Since I'll be running these tests locally here, I don't expect too much delay time. So, I want to set an implicit wait for all of my interactions of 10 seconds. That means every time WebDriver will make a call, it'll wait smartly, up to 10 seconds for the element to appear on the page before timing out and giving up.
With that, my WebDriver is basically ready to go. I could add other options to it, but for basic testing purposes I don't need to do that right now.
So, I'll return my WebDriver instance.
And, to do that with a fixture, we use a
If you're a Pythonista, you'll recognize this immediately as a generator, and you'll see why we use a generator here in a second.
But, if you're not familiar with generators, that's okay. Just know that it's going to temporarily return the instance to that WebDriver that we've initialized as the return value for my fixture.
Now, everything up to the
yield statement here is part of the setup phase of this fixture.
Everything after the
yield is part of what we consider the cleanup phase.
So, the setup phase will be run before a test with this fixture runs.
Then the test will run its stuff.
Then no matter what, at the end of the test, it will come back into this fixture here at this
yield point and continue with the cleanup phase.
So, what do we need to do to clean up a WebDriver?
We need to call the
quit method to make sure a cleanly quits and closes.
""" This module contains shared fixtures. """ import pytest import selenium.webdriver @pytest.fixture def browser(): # Initialize the ChromeDriver instance b = selenium.webdriver.Chrome() # Make its calls wait up to 10 seconds for elements to appear b.implicitly_wait(10) # Return the WebDriver instance for the setup yield b # Quit the WebDriver instance for the cleanup b.quit()
Quit vs. Close
Make sure you're using
quit and not
close because with WebDriver that means two different things.
Now that we've written our fixture, we can add our fixture to our test case.
Let's go back to
test_search.py and look at our test case again.
I've added the fixture as a named argument to this test function.
Now, you might think that's a little bit weird. Why am I passing the name in here?
What pytest will do anytime it sees a test case with an argument, it will check that argument's name against all available fixtures in the project, which will come from
Since there is a fixture named
browser, pytest will then call the
browser fixture, execute its set up routine and then pass in whatever's returned in for this named argument.
So, that way the test case can have access to whatever value the fixture returns.
Then, once the test case is complete, it'll
yield back into the fixture to do the cleanup portion.
Pytest fixtures are a really, really nifty form of dependency injection.
Your fixture can provide the values it needs for the tests, and the tests can simply consume them. This is one of the reasons why I love pytest.
Let's drop to the command line one more time and try to run our test now.
pipenv run python -m pytest
And, what we should see is that the browser will momentarily flicker up and disappear.
The test will still fail, but since we saw the browser appear, we know that the Selenium part of our test automation solution is now working.
We've successfully initialized a browser and also cleaned it up. So, that's big progress.