Skip to content

Commit 1309acf

Browse files
PeteSongshbenzer
andauthored
petesong/Add a Python example for the test practice (#1924)
* Fix the Hugo installation link * Add an example for the test practices Use `python + pytest + selenium` to test the Todo page with the best test practices: "ActionBot" and "LoadableComponent" * Update design_strategies.en.md As suggested, 1. Break the long and complex example down into smaller, more manageable sections or separate files for better readability and maintainability 2. Use a `with` context manager for WebDriver to ensure proper resource cleanup 3. Use specific exception handling instead of a bare except clause 4. Convert static method to instance method for consistency and flexibility 5. Add assertions to verify initial state in test methods 6. Remove the commented code in the `hover` method * Create using_best_practice.py example for using best practice * Update design_strategies.zh-cn.md * Update design_strategies.ja.md * Update design_strategies.pt-br.md * small update * update "deisgn_strategies.en.md" to utilize `codeblock` * update files to utilize `codeblock` update the following files to utilize `codeblock` 1. "design_strategies.ja.md" 2. "design_strategies.pt-br.md" 3. "design_strategies.zh-cn.md" * Add the other languages in the codeblock elements. So that readers can see where examples are missing and for ease of contributions. * Added "badgecode" --------- Co-authored-by: Simon Benzer <[email protected]>
1 parent 1596177 commit 1309acf

File tree

6 files changed

+768
-1
lines changed

6 files changed

+768
-1
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ to use **[Hugo 0.125.4](https://github.com/gohugoio/hugo/releases/tag/v0.125.4)*
2121

2222
Steps needed to have this working locally and work on it:
2323

24-
- Follow the [Install Hugo](https://www.docsy.dev/docs/get-started/other-options/#install-hugo) instructions from Docsy
24+
- [Install Hugo](https://gohugo.io/installation/) and follow the [Get Started](https://www.docsy.dev/docs/get-started/) instructions from Docsy
2525
- [Install go](https://go.dev/doc/install)
2626
- Clone this repository
2727
- Run `cd website_and_docs`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
"""
2+
An example of `python + pytest + selenium`
3+
which implemented "**Action Bot**, **Loadable Component** and **Page Object**".
4+
"""
5+
6+
import pytest
7+
from selenium import webdriver
8+
from selenium.common import (
9+
ElementNotInteractableException,
10+
NoSuchElementException,
11+
StaleElementReferenceException,
12+
)
13+
from selenium.webdriver import ActionChains
14+
from selenium.webdriver.common.by import By
15+
from selenium.webdriver.remote.webelement import WebElement
16+
from selenium.webdriver.support import expected_conditions as EC
17+
from selenium.webdriver.support.ui import WebDriverWait
18+
19+
20+
@pytest.fixture(scope="function")
21+
def chrome_driver():
22+
with webdriver.Chrome() as driver:
23+
driver.set_window_size(1024, 768)
24+
driver.implicitly_wait(0.5)
25+
yield driver
26+
27+
28+
class ActionBot:
29+
def __init__(self, driver) -> None:
30+
self.driver = driver
31+
self.wait = WebDriverWait(
32+
driver,
33+
timeout=10,
34+
poll_frequency=2,
35+
ignored_exceptions=[
36+
NoSuchElementException,
37+
StaleElementReferenceException,
38+
ElementNotInteractableException,
39+
],
40+
)
41+
42+
def element(self, locator: tuple) -> WebElement:
43+
self.wait.until(lambda driver: driver.find_element(*locator))
44+
return self.driver.find_element(*locator)
45+
46+
def elements(self, locator: tuple) -> list[WebElement]:
47+
return self.driver.find_elements(*locator)
48+
49+
def hover(self, locator: tuple) -> None:
50+
element = self.element(locator)
51+
ActionChains(self.driver).move_to_element(element).perform()
52+
53+
def click(self, locator: tuple) -> None:
54+
element = self.element(locator)
55+
element.click()
56+
57+
def type(self, locator: tuple, value: str) -> None:
58+
element = self.element(locator)
59+
element.clear()
60+
element.send_keys(value)
61+
62+
def text(self, locator: tuple) -> str:
63+
element = self.element(locator)
64+
return element.text
65+
66+
67+
class LoadableComponent:
68+
def load(self):
69+
raise NotImplementedError("Subclasses must implement this method")
70+
71+
def is_loaded(self):
72+
raise NotImplementedError("Subclasses must implement this method")
73+
74+
def get(self):
75+
if not self.is_loaded():
76+
self.load()
77+
if not self.is_loaded():
78+
raise Exception("Page not loaded properly.")
79+
return self
80+
81+
82+
class TodoPage(LoadableComponent):
83+
url = "https://todomvc.com/examples/react/dist/"
84+
85+
new_todo_by = (By.CSS_SELECTOR, "input.new-todo")
86+
count_todo_left_by = (By.CSS_SELECTOR, "span.todo-count")
87+
todo_items_by = (By.CSS_SELECTOR, "ul.todo-list>li")
88+
89+
view_all_by = (By.LINK_TEXT, "All")
90+
view_active_by = (By.LINK_TEXT, "Active")
91+
view_completed_by = (By.LINK_TEXT, "Completed")
92+
93+
toggle_all_by = (By.CSS_SELECTOR, "input.toggle-all")
94+
clear_completed_by = (By.CSS_SELECTOR, "button.clear-completed")
95+
96+
@staticmethod
97+
def build_todo_by(s: str) -> tuple:
98+
p = f"//li[.//label[contains(text(), '{s}')]]"
99+
return By.XPATH, p
100+
101+
@staticmethod
102+
def build_todo_item_label_by(s: str) -> tuple:
103+
p = f"//label[contains(text(), '{s}')]"
104+
return By.XPATH, p
105+
106+
@staticmethod
107+
def build_todo_item_toggle_by(s: str) -> tuple:
108+
by, using = TodoPage.build_todo_item_label_by(s)
109+
p = f"{using}/../input[@class='toggle']"
110+
return by, p
111+
112+
@staticmethod
113+
def build_todo_item_delete_by(s: str) -> tuple:
114+
by, using = TodoPage.build_todo_item_label_by(s)
115+
p = f"{using}/../button[@class='destroy']"
116+
return by, p
117+
118+
def build_count_todo_left(self, count: int) -> str:
119+
if count == 1:
120+
return "1 item left!"
121+
else:
122+
return f"{count} items left!"
123+
124+
def __init__(self, driver):
125+
self.driver = driver
126+
self.bot = ActionBot(driver)
127+
128+
def load(self):
129+
self.driver.get(self.url)
130+
131+
def is_loaded(self):
132+
try:
133+
WebDriverWait(self.driver, 10).until(EC.visibility_of_element_located(self.new_todo_by))
134+
return True
135+
except:
136+
return False
137+
138+
# business domain below
139+
def count_todo_items_left(self) -> str:
140+
return self.bot.text(self.count_todo_left_by)
141+
142+
def todo_count(self) -> int:
143+
return len(self.bot.elements(self.todo_items_by))
144+
145+
def new_todo(self, s: str):
146+
self.bot.type(self.new_todo_by, s + "\n")
147+
148+
def toggle_todo(self, s: str):
149+
self.bot.click(self.build_todo_item_toggle_by(s))
150+
151+
def hover_todo(self, s: str) -> None:
152+
self.bot.hover(self.build_todo_by(s))
153+
154+
def delete_todo(self, s: str):
155+
self.hover_todo(s)
156+
self.bot.click(self.build_todo_item_delete_by(s))
157+
158+
def clear_completed_todo(self):
159+
self.bot.click(self.clear_completed_by)
160+
161+
def toggle_all_todo(self):
162+
self.bot.click(self.toggle_all_by)
163+
164+
def view_all_todo(self):
165+
self.bot.click(self.view_all_by)
166+
167+
def view_active_todo(self):
168+
self.bot.click(self.view_active_by)
169+
170+
def view_completed_todo(self):
171+
self.bot.click(self.view_completed_by)
172+
173+
174+
@pytest.fixture
175+
def page(chrome_driver) -> TodoPage:
176+
driver = chrome_driver
177+
return TodoPage(driver).get()
178+
179+
180+
class TestTodoPage:
181+
def test_new_todo(self, page: TodoPage):
182+
assert page.todo_count() == 0
183+
page.new_todo("aaa")
184+
assert page.count_todo_items_left() == page.build_count_todo_left(1)
185+
186+
def test_todo_toggle(self, page: TodoPage):
187+
s = "aaa"
188+
page.new_todo(s)
189+
assert page.count_todo_items_left() == page.build_count_todo_left(1)
190+
191+
page.toggle_todo(s)
192+
assert page.count_todo_items_left() == page.build_count_todo_left(0)
193+
194+
page.toggle_todo(s)
195+
assert page.count_todo_items_left() == page.build_count_todo_left(1)
196+
197+
def test_todo_delete(self, page: TodoPage):
198+
s1 = "aaa"
199+
s2 = "bbb"
200+
page.new_todo(s1)
201+
page.new_todo(s2)
202+
assert page.count_todo_items_left() == page.build_count_todo_left(2)
203+
204+
page.delete_todo(s1)
205+
assert page.count_todo_items_left() == page.build_count_todo_left(1)
206+
207+
page.delete_todo(s2)
208+
assert page.todo_count() == 0
209+
210+
def test_new_100_todo(self, page: TodoPage):
211+
for i in range(100):
212+
s = f"ToDo{i}"
213+
page.new_todo(s)
214+
assert page.count_todo_items_left() == page.build_count_todo_left(100)
215+
216+
def test_toggle_all_todo(self, page: TodoPage):
217+
for i in range(10):
218+
s = f"ToDo{i}"
219+
page.new_todo(s)
220+
assert page.count_todo_items_left() == page.build_count_todo_left(10)
221+
assert page.todo_count() == 10
222+
223+
page.toggle_all_todo()
224+
assert page.count_todo_items_left() == page.build_count_todo_left(0)
225+
assert page.todo_count() == 10
226+
227+
page.toggle_all_todo()
228+
assert page.count_todo_items_left() == page.build_count_todo_left(10)
229+
assert page.todo_count() == 10
230+
231+
def test_clear_completed_todo(self, page: TodoPage):
232+
for i in range(10):
233+
s = f"ToDo{i}"
234+
page.new_todo(s)
235+
assert page.count_todo_items_left() == page.build_count_todo_left(10)
236+
assert page.todo_count() == 10
237+
238+
for i in range(5):
239+
s = f"ToDo{i}"
240+
page.toggle_todo(s)
241+
assert page.count_todo_items_left() == page.build_count_todo_left(5)
242+
assert page.todo_count() == 10
243+
244+
page.clear_completed_todo()
245+
assert page.count_todo_items_left() == page.build_count_todo_left(5)
246+
assert page.todo_count() == 5
247+
248+
def test_view_todo(self, page: TodoPage):
249+
for i in range(10):
250+
s = f"ToDo{i}"
251+
page.new_todo(s)
252+
for i in range(4):
253+
s = f"ToDo{i}"
254+
page.toggle_todo(s)
255+
256+
page.view_all_todo()
257+
assert page.count_todo_items_left() == page.build_count_todo_left(6)
258+
assert page.todo_count() == 10
259+
260+
page.view_active_todo()
261+
assert page.count_todo_items_left() == page.build_count_todo_left(6)
262+
assert page.todo_count() == 6
263+
264+
page.view_completed_todo()
265+
assert page.count_todo_items_left() == page.build_count_todo_left(6)
266+
assert page.todo_count() == 4

0 commit comments

Comments
 (0)