Part 2: First steps with Playwright
Part 2 of the tutorial shows how to take your first steps with Playwright calls.
It will explain browsers, contexts, and pages.
It will also cover basic Playwright API calls.
Browsers, contexts, and pages
Before we can automate interactions using Playwright's API, we must first understand how Playwright interacts with browsers.
There are three main layers to automation: browsers, browser contexts, and pages:
- A browser
is a single instance of a web browser.
Playwright will automatically launch a browser instance specified by code or by inputs.
Typically, this is either the Chromium, Firefox, or WebKit instance installed viaplaywright install
,
but it may also be other browsers installed on your local machine. - A browser context
is an isolated incognito-alike session within a browser instance.
They are fast and cheap to create.
One browser may have multiple browser contexts.
The recommended practice is for all tests to share one browser instance but for each test to have its own browser context. - A page
is a single tab or window within a browser context.
A browser context may have multiple pages.
Typically, an individual test should interact with only one page.
Below is a diagram illustrating how these three pieces work together:
[图片上传失败...(image-568961-1657245798236)]
Typically, we would need the following Playwright calls to set up a browser, browser context, and page:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
context = browser.new_context()
page = context.new_page()
However, the pytest-playwright
plugin takes care of these things automatically with the following fixtures:
- The
browser
fixture provides the browser instance launched by Playwright. - The
context
fixture provides a new browser context for a test. - The
page
fixture provides a new browser page for a test.
All the Playwright calls with pytest-playwright
use the synchronous API instead of the async API.
The browser
fixture has session scope, meaning all tests will share one browser instance.
The context
and page
fixtures have function scope, meaning each test gets new ones.
Typically, a test will only need to call the page
fixture directly.
These fixtures will also automatically clean up everything after testing is complete.
You do not need to explicitly close the browser.
Playwright supports both synchronous and asynchronous calls for Python.
Synchronous calls are sufficient for almost all test automation needs.
Asynchronous calls could be useful for other types of automation, such as web scraping.
Let's update our test stub to call the page
fixture.
In tests/test_search.py
, add the following import statement:
from playwright.sync_api import Page
Then, change the test function signature from this:
def test_basic_duckduckgo_search() -> None:
To this:
def test_basic_duckduckgo_search(page: Page) -> None:
Now the test has access to a fresh page in a new browser context.
If we write multiple tests, each test will get its own page and context,
but they will all share the same browser instance.
Navigating to a web page
Now that we have a page, let's do something on it!
Our first test step is, "Given the DuckDuckGo home page is displayed".
Let's navigate to the DuckDuckGo home page like this:
def test_basic_duckduckgo_search(page):
# Given the DuckDuckGo home page is displayed
page.goto('https://www.duckduckgo.com')
If you are familiar with Selenium WebDriver, then this command probably looks similar to the driver.get(...)
method.
However, Playwright's goto
method is more sophisticated:
it waits for the page to fire the load
event.
Selenium WebDriver does not automatically wait for any event,
which frequently leads to race conditions that cause flaky tests.
In Playwright, you can also wait for other page events like this:
def test_basic_duckduckgo_search(page):
# Given the DuckDuckGo home page is displayed
page.goto('https://www.duckduckgo.com', wait_until='networkidle')
For our test, however, the default load
event will suffice.
Let's try running our test to make sure Playwright works.
Launch pytest using the following command:
$ python3 -m pytest tests --headed --slowmo 1000
This invocation has two new arguments.
The first one is --headed
.
By default, Playwright runs tests in headless mode, in which the browser is not visibly rendered.
Headless mode is faster than headed mode and thus ideal for "real" testing (like in CI).
However, headed mode is better when developing tests so that you can see what is happening.
The second new argument is --slowmo
.
By default, Playwright runs interactions as fast as it can.
Again, this is great for "real" testing, but it might be too fast for humans to watch when developing and debugging.
The --slowmo
option lets the caller set a hard sleep time after every Playwright call.
For example, --slowmo 1000
will pause execution for 1 second (1000 ms) after each call.
This option is a much better way to slow down tests than to add time.sleep(...)
calls everywhere!
When you launch pytest, Chromium should pop up, navigate to the DuckDuckGo home page, and close.
Try running it with and without the --headed
and --slowmo
options, too.
Verify that Playwright calls work and the test passes before moving on.
Performing a search
Next, let's try to interact with page elements.
Playwright is able to locate any element on the page using selectors.
Out of the box, Playwright supports the following types of selectors:
- Text
- CSS
- XPath
- N-th element
- React
- Vue
- ID attributes
Text and CSS selectors also pierce the Shadow DOM by default!
In general, you should keep selectors as simple as possible.
Try to stick to text, IDs, or CSS selectors.
Use more complicated selectors only as necessary.
This tutorial will not cover recommended practices for element selectors deeply.
If you want to learn more about selectors,
read the Element selectors page in the Playwright docs,
or take the Web Element Locator Strategies course
from Test Automation University.
The next step in our test case is, "When the user searches for a phrase".
This is actually a compound interaction with two parts:
- Entering text into the search input field
- Clicking the search button
Let's start with the first part of the interaction: entering text into the search input field.
We need to find a selector for the search input.
One of the best ways to find selectors is to inspect elements through Chrome DevTools.
In Chrome, simply right-click any page and select "Inspect" to open DevTools.
Here's the inspection panel for the search input element:
[图片上传失败...(image-9e0b6b-1657245798237)]
Thankfully, this element has an ID.
We can use the selector #search_form_input_homepage
to uniquely identify this element.
To interact with elements with Playwright, we must use locators.
The Locator class
takes in a selector and produces an object that can interact with the target element.
For example, to enter text into this input element, we must use Locator
's
fill
method.
Append the following line to the test case:
page.locator('#search_form_input_homepage').fill('panda')
Since
search_form_input_homepage
is an ID, we could also use Playwright's
ID attribute selector:
page.locator('id=search_form_input_homepage').fill('panda')
Playwright's
Page
class also provides methods for element interactions like this:
page.fill('#search_form_input_homepage', 'panda')
However, using locators is recommended over direct page calls.
Locators use "strict" mode - a locator raises an exception if its selector finds more than one element.
Locators are also more reusable, especially when using page object classes.
Playwright waits for the target element to be visible and editable before it attempts to enter the text.
We are arbitrarily using the phrase 'panda'
as our search phrase because, well, why not?
Let's handle the second part of the interaction: clicking the search button.
Here's the inspection panel for the search button:
[图片上传失败...(image-8ce4a8-1657245798237)]
This element also has an ID: #search_button_homepage
. Nice!
To click an element, we must use Playwright's
click
method.
Append the following line to the test case:
page.locator('#search_button_homepage').click()
Again, Playwright is nice and concise.
The click
method waits for the target element to be ready to receive clicks, too.
Our test case should now look like this:
from playwright.sync_api import Page
def test_basic_duckduckgo_search(page: Page) -> None:
# Given the DuckDuckGo home page is displayed
page.goto('https://www.duckduckgo.com')
# When the user searches for a phrase
page.locator('#search_form_input_homepage').fill('panda')
page.locator('#search_button_homepage').click()
# Then the search result query is the phrase
# And the search result links pertain to the phrase
# And the search result title contains the phrase
pass
Rerun the test using python3 -m pytest tests --headed --slowmo 1000
.
Now, you should see the test actually perform the search!
Navigation, input filling, and clicking are only three of many page interactions you can do with Playwright.
Anything a user can do on a web page, Playwright can do as well.
Check out the Playwright Page
and Locator classes
to see all methods and attributes.
We will use more of these calls in the next tutorial parts.
网友评论