Skip to content

Commit 6d22f79

Browse files
Merge branch '3-assertions' into 4-page-objects
2 parents 36121c7 + c0e9282 commit 6d22f79

File tree

4 files changed

+87
-54
lines changed

4 files changed

+87
-54
lines changed

tests/test_search.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
These tests cover DuckDuckGo searches.
33
"""
44

5-
def test_basic_duckduckgo_search(search_page, result_page):
5+
from playwright.sync_api import expect, Page
66

7+
8+
def test_basic_duckduckgo_search(search_page, result_page) -> None:
9+
710
# Given the DuckDuckGo home page is displayed
811
search_page.load()
912

@@ -18,3 +21,4 @@ def test_basic_duckduckgo_search(search_page, result_page):
1821

1922
# And the search result title contains the phrase
2023
assert 'panda' in result_page.title()
24+

tutorial/1-getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ $ touch tests/test_search.py
162162
Add the following code to `tests/test_search.py`:
163163

164164
```python
165-
def test_basic_duckduckgo_search():
165+
def test_basic_duckduckgo_search() -> None:
166166
# Given the DuckDuckGo home page is displayed
167167
# When the user searches for a phrase
168168
# Then the search result query is the phrase

tutorial/2-first-steps.md

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,22 @@ You do not need to explicitly close the browser.
5858
> Asynchronous calls could be useful for other types of automation, such as web scraping.
5959
6060
Let's update our test stub to call the `page` fixture.
61-
In `tests/test_search.py`, change the test function signature from this:
61+
In `tests/test_search.py`, add the following import statement:
6262

6363
```python
64-
def test_basic_duckduckgo_search():
64+
from playwright.sync_api import Page
65+
```
66+
67+
Then, change the test function signature from this:
68+
69+
```python
70+
def test_basic_duckduckgo_search() -> None:
6571
```
6672

6773
To this:
6874

6975
```python
70-
def test_basic_duckduckgo_search(page):
76+
def test_basic_duckduckgo_search(page: Page) -> None:
7177
```
7278

7379
Now the test has access to a fresh page in a new browser context.
@@ -172,22 +178,32 @@ Here's the inspection panel for the search input element:
172178
Thankfully, this element has an ID.
173179
We can use the selector `#search_form_input_homepage` to uniquely identify this element.
174180

175-
To enter text into this input element, we must use Playwright's
176-
[`fill`](https://playwright.dev/python/docs/api/class-page#page-fill) method.
181+
To interact with elements with Playwright, we must use [locators](https://playwright.dev/python/docs/locators).
182+
The [Locator](https://playwright.dev/python/docs/next/api/class-locator) class
183+
takes in a selector and produces an object that can interact with the target element.
184+
185+
For example, to enter text into this input element, we must use `Locator`'s
186+
[`fill`](https://playwright.dev/python/docs/next/api/class-locator#locator-fill) method.
177187
Append the following line to the test case:
178188

179189
```python
180-
page.fill('#search_form_input_homepage', 'panda')
190+
page.locator('#search_form_input_homepage').fill('panda')
181191
```
182192

183193
> Since `search_form_input_homepage` is an ID, we could also use Playwright's
184194
> [ID attribute selector](https://playwright.dev/python/docs/selectors#id-data-testid-data-test-id-data-test-selectors):
185195
>
186-
> `page.fill('id=search_form_input_homepage', 'panda')`
196+
> `page.locator('id=search_form_input_homepage').fill('panda')`
187197
188-
Using Selenium WebDriver, we would need to locate the element and then send the interaction to it.
189-
However, in Playwright, these two parts are combined into a single call.
190-
Furthermore, Playwright waits for the target element to be visible and editable before it attempts to enter the text.
198+
> Playwright's `Page` class also provides methods for element interactions like this:
199+
>
200+
> `page.fill('#search_form_input_homepage', 'panda')`
201+
>
202+
> However, using locators is recommended over direct page calls.
203+
> Locators use "strict" mode - a locator raises an exception if its selector finds more than one element.
204+
> Locators are also more reusable, especially when using page object classes.
205+
206+
Playwright waits for the target element to be visible and editable before it attempts to enter the text.
191207
We are arbitrarily using the phrase `'panda'` as our search phrase because, well, why not?
192208

193209
Let's handle the second part of the interaction: clicking the search button.
@@ -198,11 +214,11 @@ Here's the inspection panel for the search button:
198214
This element also has an ID: `#search_button_homepage`. Nice!
199215

200216
To click an element, we must use Playwright's
201-
[`click`](https://playwright.dev/python/docs/api/class-page#page-click) method.
217+
[`click`](https://playwright.dev/python/docs/next/api/class-locator#locator-click) method.
202218
Append the following line to the test case:
203219

204220
```python
205-
page.click('#search_button_homepage')
221+
page.locator('#search_button_homepage').click()
206222
```
207223

208224
Again, Playwright is nice and concise.
@@ -211,14 +227,15 @@ The `click` method waits for the target element to be ready to receive clicks, t
211227
Our test case should now look like this:
212228

213229
```python
214-
def test_basic_duckduckgo_search(page):
230+
from playwright.sync_api import Page
215231

232+
def test_basic_duckduckgo_search(page: Page) -> None:
216233
# Given the DuckDuckGo home page is displayed
217234
page.goto('https://www.duckduckgo.com')
218235

219236
# When the user searches for a phrase
220-
page.fill('#search_form_input_homepage', 'panda')
221-
page.click('#search_button_homepage')
237+
page.locator('#search_form_input_homepage').fill('panda')
238+
page.locator('#search_button_homepage').click()
222239

223240
# Then the search result query is the phrase
224241
# And the search result links pertain to the phrase
@@ -231,5 +248,7 @@ Now, you should see the test actually perform the search!
231248

232249
Navigation, input filling, and clicking are only three of many page interactions you can do with Playwright.
233250
Anything a user can do on a web page, Playwright can do as well.
234-
Check out the Playwright [Page](https://playwright.dev/python/docs/api/class-page) API to see *all* methods and attributes.
251+
Check out the Playwright [Page](https://playwright.dev/python/docs/api/class-page)
252+
and [Locator](https://playwright.dev/python/docs/next/api/class-locator) classes
253+
to see *all* methods and attributes.
235254
We will use more of these calls in the next tutorial parts.

tutorial/3-assertions.md

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,30 @@ Here's the inspection panel for the search input element on the result page:
1717
This element also has an ID,
1818
so we can use the `#search_form_input` selector to uniquely identify it.
1919

20-
To get the textual value inside this input element,
21-
you might think that we should use the
22-
[`inner_text`](https://playwright.dev/python/docs/api/class-page#page-inner-text) method
23-
or the [`text_content`](https://playwright.dev/python/docs/api/class-page#page-text-content) method.
24-
However, neither of these are correct.
25-
To get the value of an input element, you need to get its `value` attribute.
26-
Thankfully, Playwright provides a convenient method named
27-
[`input_value`](https://playwright.dev/python/docs/api/class-page#page-input-value) to get this.
20+
To verify the textual value inside this input element,
21+
we should use Playwright's `expect` function.
22+
`expect` connects page and locator calls to Playwright's assertions.
2823

29-
Add the following line to the test case:
24+
Import the `expect` function like this:
3025

3126
```python
32-
assert 'panda' == page.input_value('#search_form_input')
27+
from playwright.sync_api import expect, Page
28+
```
29+
30+
Then, add the following line to the test case:
31+
32+
```python
33+
expect(page.locator('#search_form_input')).to_have_value('panda')
3334
```
3435

3536
This line will get the input value from the target element and assert that it is equal to the original search phrase.
3637

3738
The full test case should now look like this:
3839

3940
```python
40-
def test_basic_duckduckgo_search(page):
41+
from playwright.sync_api import expect, Page
42+
43+
def test_basic_duckduckgo_search(page: Page) -> None:
4144

4245
# Given the DuckDuckGo home page is displayed
4346
page.goto('https://www.duckduckgo.com')
@@ -47,7 +50,7 @@ def test_basic_duckduckgo_search(page):
4750
page.click('#search_button_homepage')
4851

4952
# Then the search result query is the phrase
50-
assert 'panda' == page.input_value('#search_form_input')
53+
expect(page.locator('#search_form_input')).to_have_value('panda')
5154

5255
# And the search result links pertain to the phrase
5356
# And the search result title contains the phrase
@@ -63,10 +66,18 @@ Is this a problem?
6366
Thankfully, in this case, there is no problem.
6467
Playwright automatically waits for elements to be ready before interacting with them.
6568
So, even though the test does not perform any *explicit* waiting for the result page,
66-
the `input_value` method performs *implicit* waiting for the element to be ready.
69+
the `expect` function performs *implicit* waiting for the element to satisfy the `to_have_value` condition.
6770
Check the [Auto-waiting](https://playwright.dev/python/docs/actionability) page
6871
for a full list of actionability checks for each interaction.
6972

73+
> You could use the following assert statement for this verification:
74+
>
75+
> `assert 'panda' == page.input_value('#search_form_input')`
76+
>
77+
> However, while this call will wait for the element to appear,
78+
> it will not wait for the input value to become `'panda'`.
79+
> The recommended practice is to use `expect` with locator objects.
80+
7081
Rerun the test using the same pytest command (`python3 -m pytest tests --headed --slowmo 1000`).
7182
This time, you should see the result page for a good second or two before the browser window closes.
7283
The test should still pass.
@@ -110,7 +121,7 @@ Explicit waiting will be tricky.
110121
Add the following line to the test:
111122

112123
```python
113-
page.locator('.result__title a.result__a >> nth=4').wait_for()
124+
page.locator('.result__title a.result__a').nth(4).wait_for()
114125
```
115126

116127
Let's break this down:
@@ -120,9 +131,9 @@ Let's break this down:
120131
A `Locator` object can make many of the same calls as a page, like clicking and getting text.
121132
However, it can also make calls for explicit waiting and calls that target multiple elements.
122133
2. `.result__title a.result__a` is the selector for the result links.
123-
3. `>> nth=4` is an [N-th element selector](https://playwright.dev/python/docs/selectors#n-th-element-selector).
124-
N-th element selectors are zero-indexed and may be appended to any selector.
125-
In this `locator` call, it will fetch the fifth result link element.
134+
3. `nth(4)` is an [N-th element](https://playwright.dev/python/docs/api/class-locator#locator-nth) fetcher.
135+
N-th element fetchers are zero-indexed and may be appended to any selector.
136+
In this call, it will fetch the fifth result link element.
126137
4. [`wait_for`](https://playwright.dev/python/docs/api/class-locator#locator-wait-for)
127138
is a method that will wait for the target element to be visible.
128139

@@ -168,20 +179,22 @@ Add this assertion to the test:
168179
The full test case should now look like this:
169180

170181
```python
171-
def test_basic_duckduckgo_search(page):
182+
from playwright.sync_api import expect, Page
172183

184+
def test_basic_duckduckgo_search(page: Page) -> None:
185+
173186
# Given the DuckDuckGo home page is displayed
174187
page.goto('https://www.duckduckgo.com')
175188

176189
# When the user searches for a phrase
177-
page.fill('#search_form_input_homepage', 'panda')
178-
page.click('#search_button_homepage')
190+
page.locator('#search_form_input_homepage').fill('panda')
191+
page.locator('#search_button_homepage').click()
179192

180193
# Then the search result query is the phrase
181-
assert 'panda' == page.input_value('#search_form_input')
194+
expect(page.locator('#search_form_input')).to_have_value('panda')
182195

183196
# And the search result links pertain to the phrase
184-
page.locator('.result__title a.result__a >> nth=4').wait_for()
197+
page.locator('.result__title a.result__a').nth(4).wait_for()
185198
titles = page.locator('.result__title a.result__a').all_text_contents()
186199
matches = [t for t in titles if 'panda' in t.lower()]
187200
assert len(matches) > 0
@@ -201,13 +214,13 @@ Thankfully, this one will be short.
201214

202215
The page title is an attribute of the page.
203216
It is not associated with any element on the page.
204-
We can use the [`title`](https://playwright.dev/python/docs/api/class-page#page-title) method
205-
to access it directly.
217+
We can use the [`title`](https://playwright.dev/python/docs/api/class-page#page-title) method to access it directly.
218+
For assertions, we should use `expect` with the `to_have_title` condition.
206219

207220
Add the following line to the test:
208221

209222
```python
210-
assert 'panda' in page.title()
223+
expect(page).to_have_title('panda at DuckDuckGo')
211224
```
212225

213226
This will make sure the search phrase appears in the page title.
@@ -217,26 +230,28 @@ Make sure the page is fully loaded first.
217230
The full test case should now look like this:
218231

219232
```python
220-
def test_basic_duckduckgo_search(page):
233+
from playwright.sync_api import expect, Page
221234

235+
def test_basic_duckduckgo_search(page: Page) -> None:
236+
222237
# Given the DuckDuckGo home page is displayed
223238
page.goto('https://www.duckduckgo.com')
224239

225240
# When the user searches for a phrase
226-
page.fill('#search_form_input_homepage', 'panda')
227-
page.click('#search_button_homepage')
241+
page.locator('#search_form_input_homepage').fill('panda')
242+
page.locator('#search_button_homepage').click()
228243

229244
# Then the search result query is the phrase
230-
assert 'panda' == page.input_value('#search_form_input')
245+
expect(page.locator('#search_form_input')).to_have_value('panda')
231246

232247
# And the search result links pertain to the phrase
233-
page.locator('.result__title a.result__a >> nth=4').wait_for()
248+
page.locator('.result__title a.result__a').nth(4).wait_for()
234249
titles = page.locator('.result__title a.result__a').all_text_contents()
235250
matches = [t for t in titles if 'panda' in t.lower()]
236251
assert len(matches) > 0
237252

238253
# And the search result title contains the phrase
239-
assert 'panda' in page.title()
254+
expect(page).to_have_title('panda at DuckDuckGo')
240255
```
241256

242257
We can remove the `pass` statement at the end now.
@@ -245,11 +260,6 @@ Rerun the test again to make sure it works.
245260
If it does, congrats!
246261
You have just completed a full test case in Python using Playwright with pytest.
247262

248-
Playwright's [`Page`](https://playwright.dev/python/docs/api/class-page) class
249-
provides several methods for interacting with pages and getting state from them.
250-
Read the docs to familiarize yourself with them.
251-
`Page` provides methods to interact with *every* type of web element.
252-
253263
Notice how concise this code is.
254264
Unfortunately, it's not very reusable.
255265
If other tests needed to perform DuckDuckGo searches,

0 commit comments

Comments
 (0)