@@ -185,5 +185,141 @@ Now, it just uses a page object for the search page instead of raw calls.
185
185
186
186
## The result page
187
187
188
+ After writing the search page class, the result page class will be straightforward.
189
+ It will follow the same structure.
190
+ The main difference is that each interaction methods in the result page class will return a value
191
+ because test assertions will check page values.
192
+
193
+ Start by adding the class definition to ` pages/result.py ` :
194
+
195
+ ``` python
196
+ class DuckDuckGoResultPage :
197
+ ```
198
+
199
+ Add the selectors we used in the test case:
200
+
201
+ ``` python
202
+ RESULT_LINKS = ' .result__title a.result__a'
203
+ SEARCH_INPUT = ' #search_form_input'
204
+ ```
205
+
206
+ Add dependency injection:
207
+
208
+ ``` python
209
+ def __init__ (self , page ):
210
+ self .page = page
211
+ ```
212
+
213
+ Now, let's add interaction methods for all the things assertions must check.
214
+ The first assertion checked the input value of the search input.
215
+ Let's add a method to get and return that input value:
216
+
217
+ ``` python
218
+ def search_input_value (self ):
219
+ return self .page.input_value(self .SEARCH_INPUT )
220
+ ```
221
+
222
+ The second assertion was the most complex.
223
+ It checked if at least one result link title contained the search phrase.
224
+ We can break this down into two methods:
225
+
226
+ 1 . A method to get all result link titles as a list.
227
+ 2 . A method to check if the list of result link titles contains a phrase.
228
+
229
+ Add the following methods to the class:
230
+
231
+ ``` python
232
+ def result_link_titles (self ):
233
+ self .page.locator(f ' { self .RESULT_LINKS } >> nth=4 ' ).wait_for()
234
+ titles = self .page.locator(self .RESULT_LINKS ).all_text_contents()
235
+ return titles
236
+
237
+ def result_link_titles_contain_phrase (self , phrase , minimum = 1 ):
238
+ titles = self .result_link_titles()
239
+ matches = [t for t in titles if phrase.lower() in t.lower()]
240
+ return len (matches) >= minimum
241
+ ```
242
+
243
+ In the first method, the ` RESULT_LINKS ` selector is used twice.
244
+ The first time it is used, it is concatenated with the N-th element selector using an
245
+ [ f-string] ( https://realpython.com/python-f-strings/ ) .
246
+
247
+ The second method takes in a search phases and a minimum limit for matches.
248
+ It calls the first method to get the list of titles,
249
+ filters the titles using a list comprehension,
250
+ and returns a Boolean value indicating if the number of matches meets the minimum threshold.
251
+ Notice that this method does ** not** perform an asssertion.
252
+ Assertions should * not* be done in page objects.
253
+ They should only be done in test cases.
254
+
255
+ The third assertion checked the page title.
256
+ Let's add one final method to the result page class to get the title:
257
+
258
+ ``` python
259
+ def title (self ):
260
+ return self .page.title()
261
+ ```
262
+
263
+ The full code for ` pages/result.py ` should look like this
264
+ (after rearranging methods alphabetically):
265
+
266
+ ``` python
267
+ class DuckDuckGoResultPage :
268
+
269
+ RESULT_LINKS = ' .result__title a.result__a'
270
+ SEARCH_INPUT = ' #search_form_input'
271
+
272
+ def __init__ (self , page ):
273
+ self .page = page
274
+
275
+ def result_link_titles (self ):
276
+ self .page.locator(f ' { self .RESULT_LINKS } >> nth=4 ' ).wait_for()
277
+ titles = self .page.locator(self .RESULT_LINKS ).all_text_contents()
278
+ return titles
279
+
280
+ def result_link_titles_contain_phrase (self , phrase , minimum = 1 ):
281
+ titles = self .result_link_titles()
282
+ matches = [t for t in titles if phrase.lower() in t.lower()]
283
+ return len (matches) >= minimum
284
+
285
+ def search_input_value (self ):
286
+ return self .page.input_value(self .SEARCH_INPUT )
287
+
288
+ def title (self ):
289
+ return self .page.title()
290
+ ```
291
+
292
+ After rewriting the original test case to use ` DuckDuckGoResultPage ` ,
293
+ the code in ` tests/test_search.py ` should look like this:
294
+
295
+ ``` python
296
+ from pages.result import DuckDuckGoResultPage
297
+ from pages.search import DuckDuckGoSearchPage
298
+
299
+ def test_basic_duckduckgo_search (page ):
300
+ search_page = DuckDuckGoSearchPage(page)
301
+ result_page = DuckDuckGoResultPage(page)
302
+
303
+ # Given the DuckDuckGo home page is displayed
304
+ search_page.load()
305
+
306
+ # When the user searches for a phrase
307
+ search_page.search(' panda' )
308
+
309
+ # Then the search result query is the phrase
310
+ assert ' panda' == result_page.search_input_value()
311
+
312
+ # And the search result links pertain to the phrase
313
+ assert result_page.result_link_titles_contain_phrase(' panda' )
314
+
315
+ # And the search result title contains the phrase
316
+ assert ' panda' in result_page.title()
317
+ ```
318
+
319
+ These calls look less "code-y" than the raw Playwright calls.
320
+ They read much more like a test case.
321
+
322
+ Rerun the test again to make sure everything is still working.
323
+
188
324
189
325
## Page object fixtures
0 commit comments