@@ -54,12 +54,12 @@ This workshop has five main parts:
54
54
3 . Test project setup
55
55
2 . First steps with Playwright
56
56
1 . Raw Playwright calls
57
- 3 . Refactoring using page objects
58
- 1 . The search page
59
- 4 . Writing assertions
57
+ 3 . Writing assertions
60
58
1 . Checking the search field
61
59
2 . Checking the result links
62
60
3 . Checking the title
61
+ 4 . Refactoring using page objects
62
+ 1 . The search page
63
63
5 . Nifty Playwright tricks
64
64
1 . Testing different browsers
65
65
2 . Capturing screenshots and videos
@@ -76,9 +76,9 @@ The branch names are:
76
76
| ------ | ------------------- |
77
77
| Start | 0-initial-project |
78
78
| Part 1 | 1-getting-started |
79
- | Part 2 | 2-raw-playwright |
80
- | Part 3 | 3-page-objects |
81
- | Part 4 | 4-assertions |
79
+ | Part 2 | 2-first-steps |
80
+ | Part 3 | 3-assertions |
81
+ | Part 4 | 4-page-objects |
82
82
| Part 5 | 5-playwright-tricks |
83
83
84
84
@@ -184,7 +184,10 @@ They should look something like this:
184
184
``` bash
185
185
$ pip3 freeze
186
186
attrs==21.2.0
187
+ certifi==2021.10.8
188
+ charset-normalizer==2.0.8
187
189
greenlet==1.1.2
190
+ idna==3.3
188
191
iniconfig==1.1.1
189
192
packaging==21.3
190
193
playwright==1.17.0
@@ -193,7 +196,13 @@ py==1.11.0
193
196
pyee==8.2.2
194
197
pyparsing==3.0.6
195
198
pytest==6.2.5
199
+ pytest-base-url==1.4.2
200
+ pytest-playwright==0.2.2
201
+ python-slugify==5.0.2
202
+ requests==2.26.0
203
+ text-unidecode==1.3
196
204
toml==0.10.2
205
+ urllib3==1.26.7
197
206
websockets==10.1
198
207
```
199
208
@@ -259,15 +268,217 @@ Therefore, I always recommend running the full `python3 -m pytest tests` command
259
268
260
269
### Part 2: First steps with Playwright
261
270
262
- TBD
271
+ Before we can automate interactions using Playwright's API, we must first understand how Playwright interacts with browsers.
272
+ There are three main layers to automation: * browsers* , * browser contexts* , and * pages* :
273
+
274
+ 1 . A [ browser] ( https://playwright.dev/python/docs/browsers/ )
275
+ is a single instance of a web browser.
276
+ Playwright will automatically launch a browser instance specified by code or by inputs.
277
+ Typically, this is either the Chromium, Firefox, or WebKit instance installed via ` playwright install ` ,
278
+ but it may also be other browsers installed on your local machine.
279
+ 2 . A [ browser context] ( https://playwright.dev/python/docs/browser-contexts/ )
280
+ is an isolated incognito-alike session within a browser instance.
281
+ They are fast and cheap to create.
282
+ One browser may have multiple browser contexts.
283
+ The recommended practice is for all tests to share one browser instance but for each test to have its own browser context.
284
+ 3 . A [ page] ( https://playwright.dev/python/docs/pages/ )
285
+ is a single tab or window within a browser context.
286
+ A browser context may have multiple pages.
287
+ Typically, an individual test should interact with only one page.
288
+
289
+ Below is a diagram illustrating how these three pieces work together:
290
+
291
+ ![ Browser Diagram] ( images/browser-diagram.png )
292
+
293
+ Typically, we would need the following Playwright calls to set up a browser, browser context, and page:
294
+
295
+ ``` python
296
+ from playwright.sync_api import sync_playwright
297
+
298
+ with sync_playwright() as p:
299
+ browser = p.chromium.launch()
300
+ context = browser.new_context()
301
+ page = context.new_page()
302
+ ```
303
+
304
+ However, the ` pytest-playwright ` plugin takes care of these things automatically with the following fixtures:
305
+
306
+ * The ` browser ` fixture provides the browser instance launched by Playwright.
307
+ * The ` context ` fixture provides a new browser context for a test.
308
+ * The ` page ` fixture provides a new browser page for a test.
309
+
310
+ All of the Playwright calls with ` pytest-playwright ` use the synchronous API instead of the async API.
311
+ The ` browser ` fixture has session scope, meaning all tests will share one browser instance.
312
+ The ` context ` and ` page ` fixtures have function scope, meaning each test gets new ones.
313
+ Typically, a test will only need to call the ` page ` fixture directly.
314
+ These fixtures will also automatically clean up everything after testing is complete.
315
+ You do not need to explicitly close the browser.
316
+
317
+ Let's update our test stub to call the ` page ` fixture.
318
+ In ` tests/test_search.py ` , change the test function signature from this:
319
+
320
+ ``` python
321
+ def test_basic_duckduckgo_search ():
322
+ ```
323
+
324
+ To this:
325
+
326
+ ``` python
327
+ def test_basic_duckduckgo_search (page ):
328
+ ```
329
+
330
+ Now the test has access to a fresh page in a new browser context.
331
+ If we write multiple tests, each test will get its own page and context,
332
+ but they will all share the same browser instance.
333
+
334
+ Now that we have a page, let's do something on it!
335
+ Our first test step is, "Given the DuckDuckGo home page is displayed".
336
+ Let's [ navigate] ( https://playwright.dev/python/docs/navigations ) to the DuckDuckGo home page like this:
337
+
338
+ ``` python
339
+ def test_basic_duckduckgo_search (page ):
340
+ # Given the DuckDuckGo home page is displayed
341
+ page.goto(' https://www.duckduckgo.com' )
342
+ ```
343
+
344
+ If you are familiar with Selenium WebDriver, then this command probably looks similar to the ` driver.get(...) ` method.
345
+ However, Playwright's [ ` goto ` ] ( https://playwright.dev/python/docs/api/class-page#page-goto ) method is more sophisticated:
346
+ it waits for the page to fire the ` load ` event.
347
+ Selenium WebDriver does not automatically wait for any event,
348
+ which frequently leads to race conditions that cause flaky tests.
349
+
350
+ In Playwright, you can also wait for other page events like this:
351
+
352
+ ``` python
353
+ def test_basic_duckduckgo_search (page ):
354
+ # Given the DuckDuckGo home page is displayed
355
+ page.goto(' https://www.duckduckgo.com' , wait_until = ' networkidle' )
356
+ ```
357
+
358
+ For our test, however, the default ` load ` event will suffice.
359
+
360
+ Let's try running our test to make sure Playwright works.
361
+ Launch pytest using the following command:
362
+
363
+ ``` bash
364
+ $ python3 -m pytest tests --headed --slowmo 1000
365
+ ```
366
+
367
+ This invocation has two new arguments.
368
+ The first one is ` --headed ` .
369
+ By default, Playwright runs tests in * headless* mode, in which the browser is not visibly rendered.
370
+ Headless mode is faster than headed mode and thus ideal for "real" testing (like in CI).
371
+ However, * headed* mode is better when developing tests so that you can see what is happening.
372
+
373
+ The second new argument is ` --slowmo ` .
374
+ By default, Playwright runs interactions as fast as it can.
375
+ Again, this is great for "real" testing, but it might be too fast for humans to watch when developing and debugging.
376
+ The ` --slowmo ` option lets the caller set a hard sleep time after every Playwright call.
377
+ For example, ` --slowmo 1000 ` will pause execution for 1 second (1000 ms) after each call.
378
+ This option is a much better way to slow down tests than to add ` time.sleep(...) ` calls everywhere!
379
+
380
+ When you launch pytest, Chromium should pop up, navigate to the DuckDuckGo home page, and close.
381
+ Try running it with and without the ` --headed ` and ` --slowmo ` options, too.
382
+ Verify that Playwright calls work and the test passes before moving on.
383
+
384
+ Next, let's try to interact with page elements.
385
+ Playwright is able to locate any element on the page using [ selectors] ( https://playwright.dev/python/docs/selectors ) .
386
+ Out of the box, Playwright supports the following types of selectors:
387
+
388
+ * Text
389
+ * CSS
390
+ * XPath
391
+ * N-th element
392
+ * React
393
+ * Vue
394
+
395
+ Text and CSS selectors also pierce the Shadow DOM by default!
396
+
397
+ In general, you should keep selectors as simple as possible.
398
+ Try to stick to text, IDs, or CSS selectors.
399
+ Use more complicated selectors only as necessary.
400
+
401
+ This workshop will not cover recommended practices for element selectors deeply.
402
+ If you want to learn more about selectors,
403
+ read the [ Element selectors] ( https://playwright.dev/python/docs/selectors ) page in the Playwright docs,
404
+ or take the [ Web Element Locator Strategies] ( https://testautomationu.applitools.com/web-element-locator-strategies/ ) course
405
+ from [ Test Automation University] ( https://testautomationu.applitools.com/ ) .
406
+
407
+ The next step in our test case is, "When the user searches for a phrase".
408
+ This is actually a compound interaction with two parts:
409
+
410
+ 1 . Entering text into the search input field
411
+ 2 . Clicking the search button
412
+
413
+ Let's start with the first part of the interaction: entering text into the search input field.
414
+ We need to find a selector for the search input.
415
+ One of the best ways to find selectors is to inspect elements through Chrome DevTools.
416
+ In Chrome, simply right-click any page and select "Inspect" to open DevTools.
417
+
418
+ Here's the inspection panel for the search input element:
419
+
420
+ ![ Inspecting the search input element] ( images/inspect-search-input.png )
421
+
422
+ Thankfully, this element has an ID.
423
+ We can use the selector ` #search_form_input_homepage ` to uniquely identify this element.
424
+
425
+ To enter text into this input element, we must use Playwright's
426
+ [ ` fill ` ] ( https://playwright.dev/python/docs/api/class-page#page-fill ) method.
427
+ Append the following line to the test case:
428
+
429
+ ``` python
430
+ page.fill(' #search_form_input_homepage' , ' panda' )
431
+ ```
432
+
433
+ Using Selenium WebDriver, we would need to locate the element and then send the interaction to it.
434
+ However, in Playwright, these two parts are combined into a single call.
435
+ We are arbitrarily using the phrase ` 'panda' ` as our search phrase because, well, why not?
436
+
437
+ Let's handle the second part of the interaction: clicking the search button.
438
+ Here's the inspection panel for the search button:
439
+
440
+ ![ Inspecting the search button element] ( images/inspect-search-button.png )
441
+
442
+ This element also has an ID: ` #search_button_homepage ` . Nice!
443
+
444
+ To click an element, we must use Playwright's
445
+ [ ` click ` ] ( https://playwright.dev/python/docs/api/class-page#page-click ) method.
446
+ Append the following line to the test case:
447
+
448
+ ``` python
449
+ page.click(' #search_button_homepage' )
450
+ ```
451
+
452
+ Again, Playwright is nice and concise.
453
+
454
+ Our test case should now look like this:
455
+
456
+ ``` python
457
+ def test_basic_duckduckgo_search (page ):
458
+
459
+ # Given the DuckDuckGo home page is displayed
460
+ page.goto(' https://www.duckduckgo.com' )
461
+
462
+ # When the user searches for a phrase
463
+ page.fill(' #search_form_input_homepage' , ' panda' )
464
+ page.click(' #search_button_homepage' )
465
+
466
+ # Then the search result query is the phrase
467
+ # And the search result links pertain to the phrase
468
+ # And the search result title contains the phrase
469
+ pass
470
+ ```
471
+
472
+ Rerun the test using ` python3 -m pytest tests --headed --slowmo 1000 ` .
473
+ Now, you should see the test actually perform the search!
263
474
264
475
265
- ### Part 3: Refactoring using page objects
476
+ ### Part 3: Writing assertions
266
477
267
478
TBD
268
479
269
480
270
- ### Part 4: Writing assertions
481
+ ### Part 4: Refactoring using page objects
271
482
272
483
TBD
273
484
0 commit comments