From 9e120129fa2dc0d4c0cc669b62ccd9100c97aa69 Mon Sep 17 00:00:00 2001 From: Peter Song Date: Thu, 5 Sep 2024 10:45:07 +0800 Subject: [PATCH 01/11] Fix the Hugo installation link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6b50c176af7..581761b8ff6f 100755 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ to use Hugo 0.125.4 Steps needed to have this working locally and work on it: -- Follow the [Install Hugo](https://www.docsy.dev/docs/get-started/other-options/#install-hugo) instructions from Docsy +- [Install Hugo](https://gohugo.io/installation/) and follow the [Get Started](https://www.docsy.dev/docs/get-started/) instructions from Docsy - [Install go](https://go.dev/doc/install) - Clone this repository - Run `cd website_and_docs` From 3b5d7f9a8c5e15b99084c5df8a977ee51159dbf6 Mon Sep 17 00:00:00 2001 From: Peter Song Date: Thu, 5 Sep 2024 10:48:02 +0800 Subject: [PATCH 02/11] Add an example for the test practices Use `python + pytest + selenium` to test the Todo page with the best test practices: "ActionBot" and "LoadableComponent" --- .../test_practices/design_strategies.en.md | 280 ++++++++++++++++++ 1 file changed, 280 insertions(+) diff --git a/website_and_docs/content/documentation/test_practices/design_strategies.en.md b/website_and_docs/content/documentation/test_practices/design_strategies.en.md index e154621960a3..04c9c47dc67f 100644 --- a/website_and_docs/content/documentation/test_practices/design_strategies.en.md +++ b/website_and_docs/content/documentation/test_practices/design_strategies.en.md @@ -375,3 +375,283 @@ public class ActionBot { ``` Once these abstractions have been built and duplication in your tests identified, it's possible to layer PageObjects on top of bots. + +## Example + +An example of `python + pytest + selenium`. + +```python +import pytest +from selenium import webdriver +from selenium.common import ( + ElementNotInteractableException, + NoSuchElementException, + StaleElementReferenceException, +) +from selenium.webdriver import ActionChains +from selenium.webdriver.common.by import By +from selenium.webdriver.remote.webelement import WebElement +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait + + +@pytest.fixture(scope="function") +def chrome_driver(): + driver = webdriver.Chrome() + driver.set_window_size(1024, 768) + driver.implicitly_wait(0.5) + yield driver + driver.quit() + + +class ActionBot: + def __init__(self, driver) -> None: + self.driver = driver + self.wait = WebDriverWait( + driver, + timeout=10, + poll_frequency=2, + ignored_exceptions=[ + NoSuchElementException, + StaleElementReferenceException, + ElementNotInteractableException, + ], + ) + + def element(self, locator: tuple) -> WebElement: + self.wait.until(lambda driver: driver.find_element(*locator)) + return self.driver.find_element(*locator) + + def elements(self, locator: tuple) -> list[WebElement]: + return self.driver.find_elements(*locator) + + def hover(self, locator: tuple) -> None: + element = self.element(locator) + # hover_script = """ + # var event = new MouseEvent('mouseover', {bubbles: true, cancelable: true}); + # arguments[0].dispatchEvent(event); + # """ + # self.driver.execute_script(hover_script, element) + ActionChains(self.driver).move_to_element(element).perform() + + def click(self, locator: tuple) -> None: + element = self.element(locator) + element.click() + + def type(self, locator: tuple, value: str) -> None: + element = self.element(locator) + element.clear() + element.send_keys(value) + + def text(self, locator: tuple) -> str: + element = self.element(locator) + return element.text + + +class LoadableComponent: + def load(self): + raise NotImplementedError("Subclasses must implement this method") + + def is_loaded(self): + raise NotImplementedError("Subclasses must implement this method") + + def get(self): + if not self.is_loaded(): + self.load() + if not self.is_loaded(): + raise Exception("Page not loaded properly.") + return self + + +class TodoPage(LoadableComponent): + url = "https://todomvc.com/examples/react/dist/" + + new_todo_by = (By.CSS_SELECTOR, "input.new-todo") + count_todo_left_by = (By.CSS_SELECTOR, "span.todo-count") + todo_items_by = (By.CSS_SELECTOR, "ul.todo-list>li") + + view_all_by = (By.LINK_TEXT, "All") + view_active_by = (By.LINK_TEXT, "Active") + view_completed_by = (By.LINK_TEXT, "Completed") + + toggle_all_by = (By.CSS_SELECTOR, "input.toggle-all") + clear_completed_by = (By.CSS_SELECTOR, "button.clear-completed") + + @staticmethod + def build_todo_by(s: str) -> tuple: + p = f"//li[.//label[contains(text(), '{s}')]]" + return By.XPATH, p + + @staticmethod + def build_todo_item_label_by(s: str) -> tuple: + p = f"//label[contains(text(), '{s}')]" + return By.XPATH, p + + @staticmethod + def build_todo_item_toggle_by(s: str) -> tuple: + by, using = TodoPage.build_todo_item_label_by(s) + p = f"{using}/../input[@class='toggle']" + return by, p + + @staticmethod + def build_todo_item_delete_by(s: str) -> tuple: + by, using = TodoPage.build_todo_item_label_by(s) + p = f"{using}/../button[@class='destroy']" + return by, p + + @staticmethod + def build_count_todo_left(count: int) -> str: + if count == 1: + return "1 item left!" + else: + return f"{count} items left!" + + def __init__(self, driver): + self.driver = driver + self.bot = ActionBot(driver) + + def load(self): + self.driver.get(self.url) + + def is_loaded(self): + try: + WebDriverWait(self.driver, 10).until( + EC.visibility_of_element_located(self.new_todo_by) + ) + return True + except: + return False + + # business domain below + def count_todo_items_left(self) -> str: + return self.bot.text(self.count_todo_left_by) + + def todo_count(self) -> int: + return len(self.bot.elements(self.todo_items_by)) + + def new_todo(self, s: str): + self.bot.type(self.new_todo_by, s + "\n") + + def toggle_todo(self, s: str): + self.bot.click(self.build_todo_item_toggle_by(s)) + + def hover_todo(self, s: str) -> None: + """ + should hover on the
  • tag with specific label `s` + """ + self.bot.hover(self.build_todo_by(s)) + + def delete_todo(self, s: str): + self.hover_todo(s) + self.bot.click(self.build_todo_item_delete_by(s)) + + def clear_completed_todo(self): + self.bot.click(self.clear_completed_by) + + def toggle_all_todo(self): + self.bot.click(self.toggle_all_by) + + def view_all_todo(self): + self.bot.click(self.view_all_by) + + def view_active_todo(self): + self.bot.click(self.view_active_by) + + def view_completed_todo(self): + self.bot.click(self.view_completed_by) + + +@pytest.fixture +def page(chrome_driver) -> TodoPage: + driver = chrome_driver + return TodoPage(driver).get() + + +class TestTodoPage: + def test_new_todo(self, page: TodoPage): + page.new_todo("aaa") + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + def test_todo_toggle(self, page: TodoPage): + s = "aaa" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + page.toggle_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(0) + + page.toggle_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + def test_todo_delete(self, page: TodoPage): + s1 = "aaa" + s2 = "bbb" + page.new_todo(s1) + page.new_todo(s2) + assert page.count_todo_items_left() == page.build_count_todo_left(2) + + page.delete_todo(s1) + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + page.delete_todo(s2) + assert page.todo_count() == 0 + + def test_new_100_todo(self, page: TodoPage): + for i in range(100): + s = f"ToDo{i}" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(100) + + def test_toggle_all_todo(self, page: TodoPage): + for i in range(10): + s = f"ToDo{i}" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(10) + assert page.todo_count() == 10 + + page.toggle_all_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(0) + assert page.todo_count() == 10 + + page.toggle_all_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(10) + assert page.todo_count() == 10 + + def test_clear_completed_todo(self, page: TodoPage): + for i in range(10): + s = f"ToDo{i}" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(10) + assert page.todo_count() == 10 + + for i in range(5): + s = f"ToDo{i}" + page.toggle_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(5) + assert page.todo_count() == 10 + + page.clear_completed_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(5) + assert page.todo_count() == 5 + + def test_view_todo(self, page: TodoPage): + for i in range(10): + s = f"ToDo{i}" + page.new_todo(s) + for i in range(4): + s = f"ToDo{i}" + page.toggle_todo(s) + + page.view_all_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(6) + assert page.todo_count() == 10 + + page.view_active_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(6) + assert page.todo_count() == 6 + + page.view_completed_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(6) + assert page.todo_count() == 4 + +``` From 62bc307e3a8596dabbbffce8e92566ff3d2c4a31 Mon Sep 17 00:00:00 2001 From: Peter Song Date: Thu, 5 Sep 2024 17:19:58 +0800 Subject: [PATCH 03/11] 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 --- .../test_practices/design_strategies.en.md | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/website_and_docs/content/documentation/test_practices/design_strategies.en.md b/website_and_docs/content/documentation/test_practices/design_strategies.en.md index 04c9c47dc67f..2934d25ff94f 100644 --- a/website_and_docs/content/documentation/test_practices/design_strategies.en.md +++ b/website_and_docs/content/documentation/test_practices/design_strategies.en.md @@ -378,7 +378,10 @@ Once these abstractions have been built and duplication in your tests identified ## Example -An example of `python + pytest + selenium`. +An example of `python + pytest + selenium` +which implemented "**Action Bot**, **Loadable Component** and **Page Object**". + +A `pytest` fixture `chrome_driver`. ```python import pytest @@ -397,13 +400,15 @@ from selenium.webdriver.support.ui import WebDriverWait @pytest.fixture(scope="function") def chrome_driver(): - driver = webdriver.Chrome() - driver.set_window_size(1024, 768) - driver.implicitly_wait(0.5) - yield driver - driver.quit() + with webdriver.Chrome() as driver: + driver.set_window_size(1024, 768) + driver.implicitly_wait(0.5) + yield driver +``` +"**Action Bot**" implementation. +```python class ActionBot: def __init__(self, driver) -> None: self.driver = driver @@ -427,11 +432,6 @@ class ActionBot: def hover(self, locator: tuple) -> None: element = self.element(locator) - # hover_script = """ - # var event = new MouseEvent('mouseover', {bubbles: true, cancelable: true}); - # arguments[0].dispatchEvent(event); - # """ - # self.driver.execute_script(hover_script, element) ActionChains(self.driver).move_to_element(element).perform() def click(self, locator: tuple) -> None: @@ -446,8 +446,11 @@ class ActionBot: def text(self, locator: tuple) -> str: element = self.element(locator) return element.text +``` +"**Loadable Component** definition. +```python class LoadableComponent: def load(self): raise NotImplementedError("Subclasses must implement this method") @@ -461,8 +464,11 @@ class LoadableComponent: if not self.is_loaded(): raise Exception("Page not loaded properly.") return self +``` +"**Loadable Component** and **Page Object**" implementation. +```python class TodoPage(LoadableComponent): url = "https://todomvc.com/examples/react/dist/" @@ -499,8 +505,7 @@ class TodoPage(LoadableComponent): p = f"{using}/../button[@class='destroy']" return by, p - @staticmethod - def build_count_todo_left(count: int) -> str: + def build_count_todo_left(self, count: int) -> str: if count == 1: return "1 item left!" else: @@ -536,9 +541,6 @@ class TodoPage(LoadableComponent): self.bot.click(self.build_todo_item_toggle_by(s)) def hover_todo(self, s: str) -> None: - """ - should hover on the
  • tag with specific label `s` - """ self.bot.hover(self.build_todo_by(s)) def delete_todo(self, s: str): @@ -559,8 +561,11 @@ class TodoPage(LoadableComponent): def view_completed_todo(self): self.bot.click(self.view_completed_by) +``` +Test cases implementation with `pytest`. +```python @pytest.fixture def page(chrome_driver) -> TodoPage: driver = chrome_driver @@ -569,6 +574,7 @@ def page(chrome_driver) -> TodoPage: class TestTodoPage: def test_new_todo(self, page: TodoPage): + assert page.todo_count() == 0 page.new_todo("aaa") assert page.count_todo_items_left() == page.build_count_todo_left(1) @@ -653,5 +659,4 @@ class TestTodoPage: page.view_completed_todo() assert page.count_todo_items_left() == page.build_count_todo_left(6) assert page.todo_count() == 4 - ``` From 6a248565f6e6d3285429543b32787129802d9021 Mon Sep 17 00:00:00 2001 From: Peter Song Date: Thu, 26 Sep 2024 18:41:06 +0800 Subject: [PATCH 04/11] Create using_best_practice.py example for using best practice --- .../design_strategy/using_best_practice.py | 266 ++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 examples/python/tests/design_strategy/using_best_practice.py diff --git a/examples/python/tests/design_strategy/using_best_practice.py b/examples/python/tests/design_strategy/using_best_practice.py new file mode 100644 index 000000000000..29063b8f6a90 --- /dev/null +++ b/examples/python/tests/design_strategy/using_best_practice.py @@ -0,0 +1,266 @@ +""" +An example of `python + pytest + selenium` +which implemented "**Action Bot**, **Loadable Component** and **Page Object**". +""" + +import pytest +from selenium import webdriver +from selenium.common import ( + ElementNotInteractableException, + NoSuchElementException, + StaleElementReferenceException, +) +from selenium.webdriver import ActionChains +from selenium.webdriver.common.by import By +from selenium.webdriver.remote.webelement import WebElement +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait + + +@pytest.fixture(scope="function") +def chrome_driver(): + with webdriver.Chrome() as driver: + driver.set_window_size(1024, 768) + driver.implicitly_wait(0.5) + yield driver + + +class ActionBot: + def __init__(self, driver) -> None: + self.driver = driver + self.wait = WebDriverWait( + driver, + timeout=10, + poll_frequency=2, + ignored_exceptions=[ + NoSuchElementException, + StaleElementReferenceException, + ElementNotInteractableException, + ], + ) + + def element(self, locator: tuple) -> WebElement: + self.wait.until(lambda driver: driver.find_element(*locator)) + return self.driver.find_element(*locator) + + def elements(self, locator: tuple) -> list[WebElement]: + return self.driver.find_elements(*locator) + + def hover(self, locator: tuple) -> None: + element = self.element(locator) + ActionChains(self.driver).move_to_element(element).perform() + + def click(self, locator: tuple) -> None: + element = self.element(locator) + element.click() + + def type(self, locator: tuple, value: str) -> None: + element = self.element(locator) + element.clear() + element.send_keys(value) + + def text(self, locator: tuple) -> str: + element = self.element(locator) + return element.text + + +class LoadableComponent: + def load(self): + raise NotImplementedError("Subclasses must implement this method") + + def is_loaded(self): + raise NotImplementedError("Subclasses must implement this method") + + def get(self): + if not self.is_loaded(): + self.load() + if not self.is_loaded(): + raise Exception("Page not loaded properly.") + return self + + +class TodoPage(LoadableComponent): + url = "https://todomvc.com/examples/react/dist/" + + new_todo_by = (By.CSS_SELECTOR, "input.new-todo") + count_todo_left_by = (By.CSS_SELECTOR, "span.todo-count") + todo_items_by = (By.CSS_SELECTOR, "ul.todo-list>li") + + view_all_by = (By.LINK_TEXT, "All") + view_active_by = (By.LINK_TEXT, "Active") + view_completed_by = (By.LINK_TEXT, "Completed") + + toggle_all_by = (By.CSS_SELECTOR, "input.toggle-all") + clear_completed_by = (By.CSS_SELECTOR, "button.clear-completed") + + @staticmethod + def build_todo_by(s: str) -> tuple: + p = f"//li[.//label[contains(text(), '{s}')]]" + return By.XPATH, p + + @staticmethod + def build_todo_item_label_by(s: str) -> tuple: + p = f"//label[contains(text(), '{s}')]" + return By.XPATH, p + + @staticmethod + def build_todo_item_toggle_by(s: str) -> tuple: + by, using = TodoPage.build_todo_item_label_by(s) + p = f"{using}/../input[@class='toggle']" + return by, p + + @staticmethod + def build_todo_item_delete_by(s: str) -> tuple: + by, using = TodoPage.build_todo_item_label_by(s) + p = f"{using}/../button[@class='destroy']" + return by, p + + def build_count_todo_left(self, count: int) -> str: + if count == 1: + return "1 item left!" + else: + return f"{count} items left!" + + def __init__(self, driver): + self.driver = driver + self.bot = ActionBot(driver) + + def load(self): + self.driver.get(self.url) + + def is_loaded(self): + try: + WebDriverWait(self.driver, 10).until(EC.visibility_of_element_located(self.new_todo_by)) + return True + except: + return False + + # business domain below + def count_todo_items_left(self) -> str: + return self.bot.text(self.count_todo_left_by) + + def todo_count(self) -> int: + return len(self.bot.elements(self.todo_items_by)) + + def new_todo(self, s: str): + self.bot.type(self.new_todo_by, s + "\n") + + def toggle_todo(self, s: str): + self.bot.click(self.build_todo_item_toggle_by(s)) + + def hover_todo(self, s: str) -> None: + self.bot.hover(self.build_todo_by(s)) + + def delete_todo(self, s: str): + self.hover_todo(s) + self.bot.click(self.build_todo_item_delete_by(s)) + + def clear_completed_todo(self): + self.bot.click(self.clear_completed_by) + + def toggle_all_todo(self): + self.bot.click(self.toggle_all_by) + + def view_all_todo(self): + self.bot.click(self.view_all_by) + + def view_active_todo(self): + self.bot.click(self.view_active_by) + + def view_completed_todo(self): + self.bot.click(self.view_completed_by) + + +@pytest.fixture +def page(chrome_driver) -> TodoPage: + driver = chrome_driver + return TodoPage(driver).get() + + +class TestTodoPage: + def test_new_todo(self, page: TodoPage): + assert page.todo_count() == 0 + page.new_todo("aaa") + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + def test_todo_toggle(self, page: TodoPage): + s = "aaa" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + page.toggle_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(0) + + page.toggle_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + def test_todo_delete(self, page: TodoPage): + s1 = "aaa" + s2 = "bbb" + page.new_todo(s1) + page.new_todo(s2) + assert page.count_todo_items_left() == page.build_count_todo_left(2) + + page.delete_todo(s1) + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + page.delete_todo(s2) + assert page.todo_count() == 0 + + def test_new_100_todo(self, page: TodoPage): + for i in range(100): + s = f"ToDo{i}" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(100) + + def test_toggle_all_todo(self, page: TodoPage): + for i in range(10): + s = f"ToDo{i}" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(10) + assert page.todo_count() == 10 + + page.toggle_all_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(0) + assert page.todo_count() == 10 + + page.toggle_all_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(10) + assert page.todo_count() == 10 + + def test_clear_completed_todo(self, page: TodoPage): + for i in range(10): + s = f"ToDo{i}" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(10) + assert page.todo_count() == 10 + + for i in range(5): + s = f"ToDo{i}" + page.toggle_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(5) + assert page.todo_count() == 10 + + page.clear_completed_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(5) + assert page.todo_count() == 5 + + def test_view_todo(self, page: TodoPage): + for i in range(10): + s = f"ToDo{i}" + page.new_todo(s) + for i in range(4): + s = f"ToDo{i}" + page.toggle_todo(s) + + page.view_all_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(6) + assert page.todo_count() == 10 + + page.view_active_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(6) + assert page.todo_count() == 6 + + page.view_completed_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(6) + assert page.todo_count() == 4 From 06cb3e8ab0139951e91dfedccea0a78dafe6cbc1 Mon Sep 17 00:00:00 2001 From: Peter Song Date: Thu, 26 Sep 2024 18:44:40 +0800 Subject: [PATCH 05/11] Update design_strategies.zh-cn.md --- .../test_practices/design_strategies.zh-cn.md | 285 ++++++++++++++++++ 1 file changed, 285 insertions(+) diff --git a/website_and_docs/content/documentation/test_practices/design_strategies.zh-cn.md b/website_and_docs/content/documentation/test_practices/design_strategies.zh-cn.md index a4c82b251a5c..eaa1c084b314 100644 --- a/website_and_docs/content/documentation/test_practices/design_strategies.zh-cn.md +++ b/website_and_docs/content/documentation/test_practices/design_strategies.zh-cn.md @@ -390,3 +390,288 @@ public class ActionBot { Once these abstractions have been built and duplication in your tests identified, it's possible to layer PageObjects on top of bots. + +## Example + +一个用例 使用 `python + pytest + selenium` 实现了设计策略 "**Action Bot**, **Loadable Component** +和 **Page Object**". + +A `pytest` fixture `chrome_driver`. + +```python +import pytest +from selenium import webdriver +from selenium.common import ( + ElementNotInteractableException, + NoSuchElementException, + StaleElementReferenceException, +) +from selenium.webdriver import ActionChains +from selenium.webdriver.common.by import By +from selenium.webdriver.remote.webelement import WebElement +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait + + +@pytest.fixture(scope="function") +def chrome_driver(): + with webdriver.Chrome() as driver: + driver.set_window_size(1024, 768) + driver.implicitly_wait(0.5) + yield driver +``` + +"**Action Bot**" implementation. + +```python +class ActionBot: + def __init__(self, driver) -> None: + self.driver = driver + self.wait = WebDriverWait( + driver, + timeout=10, + poll_frequency=2, + ignored_exceptions=[ + NoSuchElementException, + StaleElementReferenceException, + ElementNotInteractableException, + ], + ) + + def element(self, locator: tuple) -> WebElement: + self.wait.until(lambda driver: driver.find_element(*locator)) + return self.driver.find_element(*locator) + + def elements(self, locator: tuple) -> list[WebElement]: + return self.driver.find_elements(*locator) + + def hover(self, locator: tuple) -> None: + element = self.element(locator) + ActionChains(self.driver).move_to_element(element).perform() + + def click(self, locator: tuple) -> None: + element = self.element(locator) + element.click() + + def type(self, locator: tuple, value: str) -> None: + element = self.element(locator) + element.clear() + element.send_keys(value) + + def text(self, locator: tuple) -> str: + element = self.element(locator) + return element.text +``` + +"**Loadable Component** definition. + +```python +class LoadableComponent: + def load(self): + raise NotImplementedError("Subclasses must implement this method") + + def is_loaded(self): + raise NotImplementedError("Subclasses must implement this method") + + def get(self): + if not self.is_loaded(): + self.load() + if not self.is_loaded(): + raise Exception("Page not loaded properly.") + return self +``` + +"**Loadable Component** and **Page Object**" implementation. + +```python +class TodoPage(LoadableComponent): + url = "https://todomvc.com/examples/react/dist/" + + new_todo_by = (By.CSS_SELECTOR, "input.new-todo") + count_todo_left_by = (By.CSS_SELECTOR, "span.todo-count") + todo_items_by = (By.CSS_SELECTOR, "ul.todo-list>li") + + view_all_by = (By.LINK_TEXT, "All") + view_active_by = (By.LINK_TEXT, "Active") + view_completed_by = (By.LINK_TEXT, "Completed") + + toggle_all_by = (By.CSS_SELECTOR, "input.toggle-all") + clear_completed_by = (By.CSS_SELECTOR, "button.clear-completed") + + @staticmethod + def build_todo_by(s: str) -> tuple: + p = f"//li[.//label[contains(text(), '{s}')]]" + return By.XPATH, p + + @staticmethod + def build_todo_item_label_by(s: str) -> tuple: + p = f"//label[contains(text(), '{s}')]" + return By.XPATH, p + + @staticmethod + def build_todo_item_toggle_by(s: str) -> tuple: + by, using = TodoPage.build_todo_item_label_by(s) + p = f"{using}/../input[@class='toggle']" + return by, p + + @staticmethod + def build_todo_item_delete_by(s: str) -> tuple: + by, using = TodoPage.build_todo_item_label_by(s) + p = f"{using}/../button[@class='destroy']" + return by, p + + def build_count_todo_left(self, count: int) -> str: + if count == 1: + return "1 item left!" + else: + return f"{count} items left!" + + def __init__(self, driver): + self.driver = driver + self.bot = ActionBot(driver) + + def load(self): + self.driver.get(self.url) + + def is_loaded(self): + try: + WebDriverWait(self.driver, 10).until( + EC.visibility_of_element_located(self.new_todo_by) + ) + return True + except: + return False + + # business domain below + def count_todo_items_left(self) -> str: + return self.bot.text(self.count_todo_left_by) + + def todo_count(self) -> int: + return len(self.bot.elements(self.todo_items_by)) + + def new_todo(self, s: str): + self.bot.type(self.new_todo_by, s + "\n") + + def toggle_todo(self, s: str): + self.bot.click(self.build_todo_item_toggle_by(s)) + + def hover_todo(self, s: str) -> None: + self.bot.hover(self.build_todo_by(s)) + + def delete_todo(self, s: str): + self.hover_todo(s) + self.bot.click(self.build_todo_item_delete_by(s)) + + def clear_completed_todo(self): + self.bot.click(self.clear_completed_by) + + def toggle_all_todo(self): + self.bot.click(self.toggle_all_by) + + def view_all_todo(self): + self.bot.click(self.view_all_by) + + def view_active_todo(self): + self.bot.click(self.view_active_by) + + def view_completed_todo(self): + self.bot.click(self.view_completed_by) +``` + +Test cases implementation with `pytest`. + +```python +@pytest.fixture +def page(chrome_driver) -> TodoPage: + driver = chrome_driver + return TodoPage(driver).get() + + +class TestTodoPage: + def test_new_todo(self, page: TodoPage): + assert page.todo_count() == 0 + page.new_todo("aaa") + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + def test_todo_toggle(self, page: TodoPage): + s = "aaa" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + page.toggle_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(0) + + page.toggle_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + def test_todo_delete(self, page: TodoPage): + s1 = "aaa" + s2 = "bbb" + page.new_todo(s1) + page.new_todo(s2) + assert page.count_todo_items_left() == page.build_count_todo_left(2) + + page.delete_todo(s1) + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + page.delete_todo(s2) + assert page.todo_count() == 0 + + def test_new_100_todo(self, page: TodoPage): + for i in range(100): + s = f"ToDo{i}" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(100) + + def test_toggle_all_todo(self, page: TodoPage): + for i in range(10): + s = f"ToDo{i}" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(10) + assert page.todo_count() == 10 + + page.toggle_all_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(0) + assert page.todo_count() == 10 + + page.toggle_all_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(10) + assert page.todo_count() == 10 + + def test_clear_completed_todo(self, page: TodoPage): + for i in range(10): + s = f"ToDo{i}" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(10) + assert page.todo_count() == 10 + + for i in range(5): + s = f"ToDo{i}" + page.toggle_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(5) + assert page.todo_count() == 10 + + page.clear_completed_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(5) + assert page.todo_count() == 5 + + def test_view_todo(self, page: TodoPage): + for i in range(10): + s = f"ToDo{i}" + page.new_todo(s) + for i in range(4): + s = f"ToDo{i}" + page.toggle_todo(s) + + page.view_all_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(6) + assert page.todo_count() == 10 + + page.view_active_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(6) + assert page.todo_count() == 6 + + page.view_completed_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(6) + assert page.todo_count() == 4 +``` \ No newline at end of file From 71a5f39629ead50818b94976aa34769f72bf0e8e Mon Sep 17 00:00:00 2001 From: Peter Song Date: Thu, 26 Sep 2024 18:47:41 +0800 Subject: [PATCH 06/11] Update design_strategies.ja.md --- .../test_practices/design_strategies.ja.md | 286 +++++++++++++++++- 1 file changed, 285 insertions(+), 1 deletion(-) diff --git a/website_and_docs/content/documentation/test_practices/design_strategies.ja.md b/website_and_docs/content/documentation/test_practices/design_strategies.ja.md index 5ef10220466c..861dab630904 100644 --- a/website_and_docs/content/documentation/test_practices/design_strategies.ja.md +++ b/website_and_docs/content/documentation/test_practices/design_strategies.ja.md @@ -367,4 +367,288 @@ public class ActionBot { } ``` -これらの抽象化が構築され、テストでの重複が特定されると、ボットの上にPageObjectsを階層化することができます。 \ No newline at end of file +これらの抽象化が構築され、テストでの重複が特定されると、ボットの上にPageObjectsを階層化することができます。 + +## Example + +**Action Bot**、**Loadable Component**、および **Page Object** を実装した `python + pytest + selenium` の例です。 + +A `pytest` fixture `chrome_driver`. + +```python +import pytest +from selenium import webdriver +from selenium.common import ( + ElementNotInteractableException, + NoSuchElementException, + StaleElementReferenceException, +) +from selenium.webdriver import ActionChains +from selenium.webdriver.common.by import By +from selenium.webdriver.remote.webelement import WebElement +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait + + +@pytest.fixture(scope="function") +def chrome_driver(): + with webdriver.Chrome() as driver: + driver.set_window_size(1024, 768) + driver.implicitly_wait(0.5) + yield driver +``` + +"**Action Bot**" implementation. + +```python +class ActionBot: + def __init__(self, driver) -> None: + self.driver = driver + self.wait = WebDriverWait( + driver, + timeout=10, + poll_frequency=2, + ignored_exceptions=[ + NoSuchElementException, + StaleElementReferenceException, + ElementNotInteractableException, + ], + ) + + def element(self, locator: tuple) -> WebElement: + self.wait.until(lambda driver: driver.find_element(*locator)) + return self.driver.find_element(*locator) + + def elements(self, locator: tuple) -> list[WebElement]: + return self.driver.find_elements(*locator) + + def hover(self, locator: tuple) -> None: + element = self.element(locator) + ActionChains(self.driver).move_to_element(element).perform() + + def click(self, locator: tuple) -> None: + element = self.element(locator) + element.click() + + def type(self, locator: tuple, value: str) -> None: + element = self.element(locator) + element.clear() + element.send_keys(value) + + def text(self, locator: tuple) -> str: + element = self.element(locator) + return element.text +``` + +"**Loadable Component** definition. + +```python +class LoadableComponent: + def load(self): + raise NotImplementedError("Subclasses must implement this method") + + def is_loaded(self): + raise NotImplementedError("Subclasses must implement this method") + + def get(self): + if not self.is_loaded(): + self.load() + if not self.is_loaded(): + raise Exception("Page not loaded properly.") + return self +``` + +"**Loadable Component** and **Page Object**" implementation. + +```python +class TodoPage(LoadableComponent): + url = "https://todomvc.com/examples/react/dist/" + + new_todo_by = (By.CSS_SELECTOR, "input.new-todo") + count_todo_left_by = (By.CSS_SELECTOR, "span.todo-count") + todo_items_by = (By.CSS_SELECTOR, "ul.todo-list>li") + + view_all_by = (By.LINK_TEXT, "All") + view_active_by = (By.LINK_TEXT, "Active") + view_completed_by = (By.LINK_TEXT, "Completed") + + toggle_all_by = (By.CSS_SELECTOR, "input.toggle-all") + clear_completed_by = (By.CSS_SELECTOR, "button.clear-completed") + + @staticmethod + def build_todo_by(s: str) -> tuple: + p = f"//li[.//label[contains(text(), '{s}')]]" + return By.XPATH, p + + @staticmethod + def build_todo_item_label_by(s: str) -> tuple: + p = f"//label[contains(text(), '{s}')]" + return By.XPATH, p + + @staticmethod + def build_todo_item_toggle_by(s: str) -> tuple: + by, using = TodoPage.build_todo_item_label_by(s) + p = f"{using}/../input[@class='toggle']" + return by, p + + @staticmethod + def build_todo_item_delete_by(s: str) -> tuple: + by, using = TodoPage.build_todo_item_label_by(s) + p = f"{using}/../button[@class='destroy']" + return by, p + + def build_count_todo_left(self, count: int) -> str: + if count == 1: + return "1 item left!" + else: + return f"{count} items left!" + + def __init__(self, driver): + self.driver = driver + self.bot = ActionBot(driver) + + def load(self): + self.driver.get(self.url) + + def is_loaded(self): + try: + WebDriverWait(self.driver, 10).until( + EC.visibility_of_element_located(self.new_todo_by) + ) + return True + except: + return False + + # business domain below + def count_todo_items_left(self) -> str: + return self.bot.text(self.count_todo_left_by) + + def todo_count(self) -> int: + return len(self.bot.elements(self.todo_items_by)) + + def new_todo(self, s: str): + self.bot.type(self.new_todo_by, s + "\n") + + def toggle_todo(self, s: str): + self.bot.click(self.build_todo_item_toggle_by(s)) + + def hover_todo(self, s: str) -> None: + self.bot.hover(self.build_todo_by(s)) + + def delete_todo(self, s: str): + self.hover_todo(s) + self.bot.click(self.build_todo_item_delete_by(s)) + + def clear_completed_todo(self): + self.bot.click(self.clear_completed_by) + + def toggle_all_todo(self): + self.bot.click(self.toggle_all_by) + + def view_all_todo(self): + self.bot.click(self.view_all_by) + + def view_active_todo(self): + self.bot.click(self.view_active_by) + + def view_completed_todo(self): + self.bot.click(self.view_completed_by) +``` + +Test cases implementation with `pytest`. + +```python +@pytest.fixture +def page(chrome_driver) -> TodoPage: + driver = chrome_driver + return TodoPage(driver).get() + + +class TestTodoPage: + def test_new_todo(self, page: TodoPage): + assert page.todo_count() == 0 + page.new_todo("aaa") + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + def test_todo_toggle(self, page: TodoPage): + s = "aaa" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + page.toggle_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(0) + + page.toggle_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + def test_todo_delete(self, page: TodoPage): + s1 = "aaa" + s2 = "bbb" + page.new_todo(s1) + page.new_todo(s2) + assert page.count_todo_items_left() == page.build_count_todo_left(2) + + page.delete_todo(s1) + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + page.delete_todo(s2) + assert page.todo_count() == 0 + + def test_new_100_todo(self, page: TodoPage): + for i in range(100): + s = f"ToDo{i}" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(100) + + def test_toggle_all_todo(self, page: TodoPage): + for i in range(10): + s = f"ToDo{i}" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(10) + assert page.todo_count() == 10 + + page.toggle_all_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(0) + assert page.todo_count() == 10 + + page.toggle_all_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(10) + assert page.todo_count() == 10 + + def test_clear_completed_todo(self, page: TodoPage): + for i in range(10): + s = f"ToDo{i}" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(10) + assert page.todo_count() == 10 + + for i in range(5): + s = f"ToDo{i}" + page.toggle_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(5) + assert page.todo_count() == 10 + + page.clear_completed_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(5) + assert page.todo_count() == 5 + + def test_view_todo(self, page: TodoPage): + for i in range(10): + s = f"ToDo{i}" + page.new_todo(s) + for i in range(4): + s = f"ToDo{i}" + page.toggle_todo(s) + + page.view_all_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(6) + assert page.todo_count() == 10 + + page.view_active_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(6) + assert page.todo_count() == 6 + + page.view_completed_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(6) + assert page.todo_count() == 4 +``` \ No newline at end of file From 4fa310f2a2ce17910dd8c5957800f46a04975b32 Mon Sep 17 00:00:00 2001 From: Peter Song Date: Thu, 26 Sep 2024 18:48:11 +0800 Subject: [PATCH 07/11] Update design_strategies.pt-br.md --- .../test_practices/design_strategies.pt-br.md | 286 ++++++++++++++++++ 1 file changed, 286 insertions(+) diff --git a/website_and_docs/content/documentation/test_practices/design_strategies.pt-br.md b/website_and_docs/content/documentation/test_practices/design_strategies.pt-br.md index d2174f34adaf..5f7eb146f524 100644 --- a/website_and_docs/content/documentation/test_practices/design_strategies.pt-br.md +++ b/website_and_docs/content/documentation/test_practices/design_strategies.pt-br.md @@ -375,3 +375,289 @@ public class ActionBot { ``` Once these abstractions have been built and duplication in your tests identified, it's possible to layer PageObjects on top of bots. + + +## Example + +An example of `python + pytest + selenium` +which implemented "**Action Bot**, **Loadable Component** and **Page Object**". + +A `pytest` fixture `chrome_driver`. + +```python +import pytest +from selenium import webdriver +from selenium.common import ( + ElementNotInteractableException, + NoSuchElementException, + StaleElementReferenceException, +) +from selenium.webdriver import ActionChains +from selenium.webdriver.common.by import By +from selenium.webdriver.remote.webelement import WebElement +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait + + +@pytest.fixture(scope="function") +def chrome_driver(): + with webdriver.Chrome() as driver: + driver.set_window_size(1024, 768) + driver.implicitly_wait(0.5) + yield driver +``` + +"**Action Bot**" implementation. + +```python +class ActionBot: + def __init__(self, driver) -> None: + self.driver = driver + self.wait = WebDriverWait( + driver, + timeout=10, + poll_frequency=2, + ignored_exceptions=[ + NoSuchElementException, + StaleElementReferenceException, + ElementNotInteractableException, + ], + ) + + def element(self, locator: tuple) -> WebElement: + self.wait.until(lambda driver: driver.find_element(*locator)) + return self.driver.find_element(*locator) + + def elements(self, locator: tuple) -> list[WebElement]: + return self.driver.find_elements(*locator) + + def hover(self, locator: tuple) -> None: + element = self.element(locator) + ActionChains(self.driver).move_to_element(element).perform() + + def click(self, locator: tuple) -> None: + element = self.element(locator) + element.click() + + def type(self, locator: tuple, value: str) -> None: + element = self.element(locator) + element.clear() + element.send_keys(value) + + def text(self, locator: tuple) -> str: + element = self.element(locator) + return element.text +``` + +"**Loadable Component** definition. + +```python +class LoadableComponent: + def load(self): + raise NotImplementedError("Subclasses must implement this method") + + def is_loaded(self): + raise NotImplementedError("Subclasses must implement this method") + + def get(self): + if not self.is_loaded(): + self.load() + if not self.is_loaded(): + raise Exception("Page not loaded properly.") + return self +``` + +"**Loadable Component** and **Page Object**" implementation. + +```python +class TodoPage(LoadableComponent): + url = "https://todomvc.com/examples/react/dist/" + + new_todo_by = (By.CSS_SELECTOR, "input.new-todo") + count_todo_left_by = (By.CSS_SELECTOR, "span.todo-count") + todo_items_by = (By.CSS_SELECTOR, "ul.todo-list>li") + + view_all_by = (By.LINK_TEXT, "All") + view_active_by = (By.LINK_TEXT, "Active") + view_completed_by = (By.LINK_TEXT, "Completed") + + toggle_all_by = (By.CSS_SELECTOR, "input.toggle-all") + clear_completed_by = (By.CSS_SELECTOR, "button.clear-completed") + + @staticmethod + def build_todo_by(s: str) -> tuple: + p = f"//li[.//label[contains(text(), '{s}')]]" + return By.XPATH, p + + @staticmethod + def build_todo_item_label_by(s: str) -> tuple: + p = f"//label[contains(text(), '{s}')]" + return By.XPATH, p + + @staticmethod + def build_todo_item_toggle_by(s: str) -> tuple: + by, using = TodoPage.build_todo_item_label_by(s) + p = f"{using}/../input[@class='toggle']" + return by, p + + @staticmethod + def build_todo_item_delete_by(s: str) -> tuple: + by, using = TodoPage.build_todo_item_label_by(s) + p = f"{using}/../button[@class='destroy']" + return by, p + + def build_count_todo_left(self, count: int) -> str: + if count == 1: + return "1 item left!" + else: + return f"{count} items left!" + + def __init__(self, driver): + self.driver = driver + self.bot = ActionBot(driver) + + def load(self): + self.driver.get(self.url) + + def is_loaded(self): + try: + WebDriverWait(self.driver, 10).until( + EC.visibility_of_element_located(self.new_todo_by) + ) + return True + except: + return False + + # business domain below + def count_todo_items_left(self) -> str: + return self.bot.text(self.count_todo_left_by) + + def todo_count(self) -> int: + return len(self.bot.elements(self.todo_items_by)) + + def new_todo(self, s: str): + self.bot.type(self.new_todo_by, s + "\n") + + def toggle_todo(self, s: str): + self.bot.click(self.build_todo_item_toggle_by(s)) + + def hover_todo(self, s: str) -> None: + self.bot.hover(self.build_todo_by(s)) + + def delete_todo(self, s: str): + self.hover_todo(s) + self.bot.click(self.build_todo_item_delete_by(s)) + + def clear_completed_todo(self): + self.bot.click(self.clear_completed_by) + + def toggle_all_todo(self): + self.bot.click(self.toggle_all_by) + + def view_all_todo(self): + self.bot.click(self.view_all_by) + + def view_active_todo(self): + self.bot.click(self.view_active_by) + + def view_completed_todo(self): + self.bot.click(self.view_completed_by) +``` + +Test cases implementation with `pytest`. + +```python +@pytest.fixture +def page(chrome_driver) -> TodoPage: + driver = chrome_driver + return TodoPage(driver).get() + + +class TestTodoPage: + def test_new_todo(self, page: TodoPage): + assert page.todo_count() == 0 + page.new_todo("aaa") + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + def test_todo_toggle(self, page: TodoPage): + s = "aaa" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + page.toggle_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(0) + + page.toggle_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + def test_todo_delete(self, page: TodoPage): + s1 = "aaa" + s2 = "bbb" + page.new_todo(s1) + page.new_todo(s2) + assert page.count_todo_items_left() == page.build_count_todo_left(2) + + page.delete_todo(s1) + assert page.count_todo_items_left() == page.build_count_todo_left(1) + + page.delete_todo(s2) + assert page.todo_count() == 0 + + def test_new_100_todo(self, page: TodoPage): + for i in range(100): + s = f"ToDo{i}" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(100) + + def test_toggle_all_todo(self, page: TodoPage): + for i in range(10): + s = f"ToDo{i}" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(10) + assert page.todo_count() == 10 + + page.toggle_all_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(0) + assert page.todo_count() == 10 + + page.toggle_all_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(10) + assert page.todo_count() == 10 + + def test_clear_completed_todo(self, page: TodoPage): + for i in range(10): + s = f"ToDo{i}" + page.new_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(10) + assert page.todo_count() == 10 + + for i in range(5): + s = f"ToDo{i}" + page.toggle_todo(s) + assert page.count_todo_items_left() == page.build_count_todo_left(5) + assert page.todo_count() == 10 + + page.clear_completed_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(5) + assert page.todo_count() == 5 + + def test_view_todo(self, page: TodoPage): + for i in range(10): + s = f"ToDo{i}" + page.new_todo(s) + for i in range(4): + s = f"ToDo{i}" + page.toggle_todo(s) + + page.view_all_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(6) + assert page.todo_count() == 10 + + page.view_active_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(6) + assert page.todo_count() == 6 + + page.view_completed_todo() + assert page.count_todo_items_left() == page.build_count_todo_left(6) + assert page.todo_count() == 4 +``` \ No newline at end of file From 2e94b422b2f085336d068fa53e6654823c5b537f Mon Sep 17 00:00:00 2001 From: Peter Song Date: Thu, 26 Sep 2024 18:49:59 +0800 Subject: [PATCH 08/11] small update --- .../documentation/test_practices/design_strategies.en.md | 3 +-- .../documentation/test_practices/design_strategies.pt-br.md | 3 +-- .../documentation/test_practices/design_strategies.zh-cn.md | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/website_and_docs/content/documentation/test_practices/design_strategies.en.md b/website_and_docs/content/documentation/test_practices/design_strategies.en.md index 2934d25ff94f..acdd3cf71ecc 100644 --- a/website_and_docs/content/documentation/test_practices/design_strategies.en.md +++ b/website_and_docs/content/documentation/test_practices/design_strategies.en.md @@ -378,8 +378,7 @@ Once these abstractions have been built and duplication in your tests identified ## Example -An example of `python + pytest + selenium` -which implemented "**Action Bot**, **Loadable Component** and **Page Object**". +An example of `python + pytest + selenium` which implemented "**Action Bot**, **Loadable Component** and **Page Object**". A `pytest` fixture `chrome_driver`. diff --git a/website_and_docs/content/documentation/test_practices/design_strategies.pt-br.md b/website_and_docs/content/documentation/test_practices/design_strategies.pt-br.md index 5f7eb146f524..824e81669b0f 100644 --- a/website_and_docs/content/documentation/test_practices/design_strategies.pt-br.md +++ b/website_and_docs/content/documentation/test_practices/design_strategies.pt-br.md @@ -379,8 +379,7 @@ Once these abstractions have been built and duplication in your tests identified ## Example -An example of `python + pytest + selenium` -which implemented "**Action Bot**, **Loadable Component** and **Page Object**". +An example of `python + pytest + selenium` which implemented "**Action Bot**, **Loadable Component** and **Page Object**". A `pytest` fixture `chrome_driver`. diff --git a/website_and_docs/content/documentation/test_practices/design_strategies.zh-cn.md b/website_and_docs/content/documentation/test_practices/design_strategies.zh-cn.md index eaa1c084b314..f138b73cec96 100644 --- a/website_and_docs/content/documentation/test_practices/design_strategies.zh-cn.md +++ b/website_and_docs/content/documentation/test_practices/design_strategies.zh-cn.md @@ -393,8 +393,7 @@ identified, it's possible to layer PageObjects on top of bots. ## Example -一个用例 使用 `python + pytest + selenium` 实现了设计策略 "**Action Bot**, **Loadable Component** -和 **Page Object**". +一个用例 使用 `python + pytest + selenium` 实现了设计策略 "**Action Bot**, **Loadable Component** 和 **Page Object**". A `pytest` fixture `chrome_driver`. From 34048f1becd5d8ea9d1e324545afd8fd0ba7042a Mon Sep 17 00:00:00 2001 From: Peter Song Date: Tue, 25 Mar 2025 09:41:55 +0800 Subject: [PATCH 09/11] update "deisgn_strategies.en.md" to utilize `codeblock` --- .../test_practices/design_strategies.en.md | 291 ++---------------- 1 file changed, 27 insertions(+), 264 deletions(-) diff --git a/website_and_docs/content/documentation/test_practices/design_strategies.en.md b/website_and_docs/content/documentation/test_practices/design_strategies.en.md index 3cbd9d083fe8..f83a773869d2 100644 --- a/website_and_docs/content/documentation/test_practices/design_strategies.en.md +++ b/website_and_docs/content/documentation/test_practices/design_strategies.en.md @@ -449,280 +449,43 @@ An example of `python + pytest + selenium` which implemented "**Action Bot**, ** A `pytest` fixture `chrome_driver`. -```python -import pytest -from selenium import webdriver -from selenium.common import ( - ElementNotInteractableException, - NoSuchElementException, - StaleElementReferenceException, -) -from selenium.webdriver import ActionChains -from selenium.webdriver.common.by import By -from selenium.webdriver.remote.webelement import WebElement -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.ui import WebDriverWait - - -@pytest.fixture(scope="function") -def chrome_driver(): - with webdriver.Chrome() as driver: - driver.set_window_size(1024, 768) - driver.implicitly_wait(0.5) - yield driver -``` +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L6-L26" >}} +{{< /tab >}} +{{< /tabpane >}} + "**Action Bot**" implementation. -```python -class ActionBot: - def __init__(self, driver) -> None: - self.driver = driver - self.wait = WebDriverWait( - driver, - timeout=10, - poll_frequency=2, - ignored_exceptions=[ - NoSuchElementException, - StaleElementReferenceException, - ElementNotInteractableException, - ], - ) - - def element(self, locator: tuple) -> WebElement: - self.wait.until(lambda driver: driver.find_element(*locator)) - return self.driver.find_element(*locator) - - def elements(self, locator: tuple) -> list[WebElement]: - return self.driver.find_elements(*locator) - - def hover(self, locator: tuple) -> None: - element = self.element(locator) - ActionChains(self.driver).move_to_element(element).perform() - - def click(self, locator: tuple) -> None: - element = self.element(locator) - element.click() - - def type(self, locator: tuple, value: str) -> None: - element = self.element(locator) - element.clear() - element.send_keys(value) - - def text(self, locator: tuple) -> str: - element = self.element(locator) - return element.text -``` +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L28-L65" >}} +{{< /tab >}} +{{< /tabpane >}} -"**Loadable Component** definition. -```python -class LoadableComponent: - def load(self): - raise NotImplementedError("Subclasses must implement this method") +"**Loadable Component** definition. - def is_loaded(self): - raise NotImplementedError("Subclasses must implement this method") +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L67-L80" >}} +{{< /tab >}} +{{< /tabpane >}} - def get(self): - if not self.is_loaded(): - self.load() - if not self.is_loaded(): - raise Exception("Page not loaded properly.") - return self -``` "**Loadable Component** and **Page Object**" implementation. -```python -class TodoPage(LoadableComponent): - url = "https://todomvc.com/examples/react/dist/" - - new_todo_by = (By.CSS_SELECTOR, "input.new-todo") - count_todo_left_by = (By.CSS_SELECTOR, "span.todo-count") - todo_items_by = (By.CSS_SELECTOR, "ul.todo-list>li") - - view_all_by = (By.LINK_TEXT, "All") - view_active_by = (By.LINK_TEXT, "Active") - view_completed_by = (By.LINK_TEXT, "Completed") - - toggle_all_by = (By.CSS_SELECTOR, "input.toggle-all") - clear_completed_by = (By.CSS_SELECTOR, "button.clear-completed") - - @staticmethod - def build_todo_by(s: str) -> tuple: - p = f"//li[.//label[contains(text(), '{s}')]]" - return By.XPATH, p - - @staticmethod - def build_todo_item_label_by(s: str) -> tuple: - p = f"//label[contains(text(), '{s}')]" - return By.XPATH, p - - @staticmethod - def build_todo_item_toggle_by(s: str) -> tuple: - by, using = TodoPage.build_todo_item_label_by(s) - p = f"{using}/../input[@class='toggle']" - return by, p - - @staticmethod - def build_todo_item_delete_by(s: str) -> tuple: - by, using = TodoPage.build_todo_item_label_by(s) - p = f"{using}/../button[@class='destroy']" - return by, p - - def build_count_todo_left(self, count: int) -> str: - if count == 1: - return "1 item left!" - else: - return f"{count} items left!" - - def __init__(self, driver): - self.driver = driver - self.bot = ActionBot(driver) - - def load(self): - self.driver.get(self.url) - - def is_loaded(self): - try: - WebDriverWait(self.driver, 10).until( - EC.visibility_of_element_located(self.new_todo_by) - ) - return True - except: - return False - - # business domain below - def count_todo_items_left(self) -> str: - return self.bot.text(self.count_todo_left_by) - - def todo_count(self) -> int: - return len(self.bot.elements(self.todo_items_by)) - - def new_todo(self, s: str): - self.bot.type(self.new_todo_by, s + "\n") - - def toggle_todo(self, s: str): - self.bot.click(self.build_todo_item_toggle_by(s)) - - def hover_todo(self, s: str) -> None: - self.bot.hover(self.build_todo_by(s)) - - def delete_todo(self, s: str): - self.hover_todo(s) - self.bot.click(self.build_todo_item_delete_by(s)) - - def clear_completed_todo(self): - self.bot.click(self.clear_completed_by) - - def toggle_all_todo(self): - self.bot.click(self.toggle_all_by) - - def view_all_todo(self): - self.bot.click(self.view_all_by) - - def view_active_todo(self): - self.bot.click(self.view_active_by) - - def view_completed_todo(self): - self.bot.click(self.view_completed_by) -``` +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L82-L172" >}} +{{< /tab >}} +{{< /tabpane >}} Test cases implementation with `pytest`. -```python -@pytest.fixture -def page(chrome_driver) -> TodoPage: - driver = chrome_driver - return TodoPage(driver).get() - - -class TestTodoPage: - def test_new_todo(self, page: TodoPage): - assert page.todo_count() == 0 - page.new_todo("aaa") - assert page.count_todo_items_left() == page.build_count_todo_left(1) - - def test_todo_toggle(self, page: TodoPage): - s = "aaa" - page.new_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(1) - - page.toggle_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(0) - - page.toggle_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(1) - - def test_todo_delete(self, page: TodoPage): - s1 = "aaa" - s2 = "bbb" - page.new_todo(s1) - page.new_todo(s2) - assert page.count_todo_items_left() == page.build_count_todo_left(2) - - page.delete_todo(s1) - assert page.count_todo_items_left() == page.build_count_todo_left(1) - - page.delete_todo(s2) - assert page.todo_count() == 0 - - def test_new_100_todo(self, page: TodoPage): - for i in range(100): - s = f"ToDo{i}" - page.new_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(100) - - def test_toggle_all_todo(self, page: TodoPage): - for i in range(10): - s = f"ToDo{i}" - page.new_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(10) - assert page.todo_count() == 10 - - page.toggle_all_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(0) - assert page.todo_count() == 10 - - page.toggle_all_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(10) - assert page.todo_count() == 10 - - def test_clear_completed_todo(self, page: TodoPage): - for i in range(10): - s = f"ToDo{i}" - page.new_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(10) - assert page.todo_count() == 10 - - for i in range(5): - s = f"ToDo{i}" - page.toggle_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(5) - assert page.todo_count() == 10 - - page.clear_completed_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(5) - assert page.todo_count() == 5 - - def test_view_todo(self, page: TodoPage): - for i in range(10): - s = f"ToDo{i}" - page.new_todo(s) - for i in range(4): - s = f"ToDo{i}" - page.toggle_todo(s) - - page.view_all_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(6) - assert page.todo_count() == 10 - - page.view_active_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(6) - assert page.todo_count() == 6 - - page.view_completed_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(6) - assert page.todo_count() == 4 -``` +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L174-" >}} +{{< /tab >}} +{{< /tabpane >}} From 1f076a5fc165db133e2916c98a32502df5ab3c5f Mon Sep 17 00:00:00 2001 From: Peter Song Date: Tue, 25 Mar 2025 09:52:31 +0800 Subject: [PATCH 10/11] 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" --- .../test_practices/design_strategies.ja.md | 292 ++---------------- .../test_practices/design_strategies.pt-br.md | 291 ++--------------- .../test_practices/design_strategies.zh-cn.md | 291 ++--------------- 3 files changed, 81 insertions(+), 793 deletions(-) diff --git a/website_and_docs/content/documentation/test_practices/design_strategies.ja.md b/website_and_docs/content/documentation/test_practices/design_strategies.ja.md index 9197a4b0ef2f..1a523d2cc3ec 100644 --- a/website_and_docs/content/documentation/test_practices/design_strategies.ja.md +++ b/website_and_docs/content/documentation/test_practices/design_strategies.ja.md @@ -441,281 +441,43 @@ public class ActionBot { A `pytest` fixture `chrome_driver`. -```python -import pytest -from selenium import webdriver -from selenium.common import ( - ElementNotInteractableException, - NoSuchElementException, - StaleElementReferenceException, -) -from selenium.webdriver import ActionChains -from selenium.webdriver.common.by import By -from selenium.webdriver.remote.webelement import WebElement -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.ui import WebDriverWait - - -@pytest.fixture(scope="function") -def chrome_driver(): - with webdriver.Chrome() as driver: - driver.set_window_size(1024, 768) - driver.implicitly_wait(0.5) - yield driver -``` +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L6-L26" >}} +{{< /tab >}} +{{< /tabpane >}} + "**Action Bot**" implementation. -```python -class ActionBot: - def __init__(self, driver) -> None: - self.driver = driver - self.wait = WebDriverWait( - driver, - timeout=10, - poll_frequency=2, - ignored_exceptions=[ - NoSuchElementException, - StaleElementReferenceException, - ElementNotInteractableException, - ], - ) - - def element(self, locator: tuple) -> WebElement: - self.wait.until(lambda driver: driver.find_element(*locator)) - return self.driver.find_element(*locator) - - def elements(self, locator: tuple) -> list[WebElement]: - return self.driver.find_elements(*locator) - - def hover(self, locator: tuple) -> None: - element = self.element(locator) - ActionChains(self.driver).move_to_element(element).perform() - - def click(self, locator: tuple) -> None: - element = self.element(locator) - element.click() - - def type(self, locator: tuple, value: str) -> None: - element = self.element(locator) - element.clear() - element.send_keys(value) - - def text(self, locator: tuple) -> str: - element = self.element(locator) - return element.text -``` +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L28-L65" >}} +{{< /tab >}} +{{< /tabpane >}} -"**Loadable Component** definition. -```python -class LoadableComponent: - def load(self): - raise NotImplementedError("Subclasses must implement this method") +"**Loadable Component** definition. - def is_loaded(self): - raise NotImplementedError("Subclasses must implement this method") +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L67-L80" >}} +{{< /tab >}} +{{< /tabpane >}} - def get(self): - if not self.is_loaded(): - self.load() - if not self.is_loaded(): - raise Exception("Page not loaded properly.") - return self -``` "**Loadable Component** and **Page Object**" implementation. -```python -class TodoPage(LoadableComponent): - url = "https://todomvc.com/examples/react/dist/" - - new_todo_by = (By.CSS_SELECTOR, "input.new-todo") - count_todo_left_by = (By.CSS_SELECTOR, "span.todo-count") - todo_items_by = (By.CSS_SELECTOR, "ul.todo-list>li") - - view_all_by = (By.LINK_TEXT, "All") - view_active_by = (By.LINK_TEXT, "Active") - view_completed_by = (By.LINK_TEXT, "Completed") - - toggle_all_by = (By.CSS_SELECTOR, "input.toggle-all") - clear_completed_by = (By.CSS_SELECTOR, "button.clear-completed") - - @staticmethod - def build_todo_by(s: str) -> tuple: - p = f"//li[.//label[contains(text(), '{s}')]]" - return By.XPATH, p - - @staticmethod - def build_todo_item_label_by(s: str) -> tuple: - p = f"//label[contains(text(), '{s}')]" - return By.XPATH, p - - @staticmethod - def build_todo_item_toggle_by(s: str) -> tuple: - by, using = TodoPage.build_todo_item_label_by(s) - p = f"{using}/../input[@class='toggle']" - return by, p - - @staticmethod - def build_todo_item_delete_by(s: str) -> tuple: - by, using = TodoPage.build_todo_item_label_by(s) - p = f"{using}/../button[@class='destroy']" - return by, p - - def build_count_todo_left(self, count: int) -> str: - if count == 1: - return "1 item left!" - else: - return f"{count} items left!" - - def __init__(self, driver): - self.driver = driver - self.bot = ActionBot(driver) - - def load(self): - self.driver.get(self.url) - - def is_loaded(self): - try: - WebDriverWait(self.driver, 10).until( - EC.visibility_of_element_located(self.new_todo_by) - ) - return True - except: - return False - - # business domain below - def count_todo_items_left(self) -> str: - return self.bot.text(self.count_todo_left_by) - - def todo_count(self) -> int: - return len(self.bot.elements(self.todo_items_by)) - - def new_todo(self, s: str): - self.bot.type(self.new_todo_by, s + "\n") - - def toggle_todo(self, s: str): - self.bot.click(self.build_todo_item_toggle_by(s)) - - def hover_todo(self, s: str) -> None: - self.bot.hover(self.build_todo_by(s)) - - def delete_todo(self, s: str): - self.hover_todo(s) - self.bot.click(self.build_todo_item_delete_by(s)) - - def clear_completed_todo(self): - self.bot.click(self.clear_completed_by) - - def toggle_all_todo(self): - self.bot.click(self.toggle_all_by) - - def view_all_todo(self): - self.bot.click(self.view_all_by) - - def view_active_todo(self): - self.bot.click(self.view_active_by) - - def view_completed_todo(self): - self.bot.click(self.view_completed_by) -``` +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L82-L172" >}} +{{< /tab >}} +{{< /tabpane >}} Test cases implementation with `pytest`. -```python -@pytest.fixture -def page(chrome_driver) -> TodoPage: - driver = chrome_driver - return TodoPage(driver).get() - - -class TestTodoPage: - def test_new_todo(self, page: TodoPage): - assert page.todo_count() == 0 - page.new_todo("aaa") - assert page.count_todo_items_left() == page.build_count_todo_left(1) - - def test_todo_toggle(self, page: TodoPage): - s = "aaa" - page.new_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(1) - - page.toggle_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(0) - - page.toggle_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(1) - - def test_todo_delete(self, page: TodoPage): - s1 = "aaa" - s2 = "bbb" - page.new_todo(s1) - page.new_todo(s2) - assert page.count_todo_items_left() == page.build_count_todo_left(2) - - page.delete_todo(s1) - assert page.count_todo_items_left() == page.build_count_todo_left(1) - - page.delete_todo(s2) - assert page.todo_count() == 0 - - def test_new_100_todo(self, page: TodoPage): - for i in range(100): - s = f"ToDo{i}" - page.new_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(100) - - def test_toggle_all_todo(self, page: TodoPage): - for i in range(10): - s = f"ToDo{i}" - page.new_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(10) - assert page.todo_count() == 10 - - page.toggle_all_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(0) - assert page.todo_count() == 10 - - page.toggle_all_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(10) - assert page.todo_count() == 10 - - def test_clear_completed_todo(self, page: TodoPage): - for i in range(10): - s = f"ToDo{i}" - page.new_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(10) - assert page.todo_count() == 10 - - for i in range(5): - s = f"ToDo{i}" - page.toggle_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(5) - assert page.todo_count() == 10 - - page.clear_completed_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(5) - assert page.todo_count() == 5 - - def test_view_todo(self, page: TodoPage): - for i in range(10): - s = f"ToDo{i}" - page.new_todo(s) - for i in range(4): - s = f"ToDo{i}" - page.toggle_todo(s) - - page.view_all_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(6) - assert page.todo_count() == 10 - - page.view_active_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(6) - assert page.todo_count() == 6 - - page.view_completed_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(6) - assert page.todo_count() == 4 -``` - +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L174-" >}} +{{< /tab >}} +{{< /tabpane >}} diff --git a/website_and_docs/content/documentation/test_practices/design_strategies.pt-br.md b/website_and_docs/content/documentation/test_practices/design_strategies.pt-br.md index e6f5d7aed42f..a0e9a9f27dc0 100644 --- a/website_and_docs/content/documentation/test_practices/design_strategies.pt-br.md +++ b/website_and_docs/content/documentation/test_practices/design_strategies.pt-br.md @@ -449,280 +449,43 @@ An example of `python + pytest + selenium` which implemented "**Action Bot**, ** A `pytest` fixture `chrome_driver`. -```python -import pytest -from selenium import webdriver -from selenium.common import ( - ElementNotInteractableException, - NoSuchElementException, - StaleElementReferenceException, -) -from selenium.webdriver import ActionChains -from selenium.webdriver.common.by import By -from selenium.webdriver.remote.webelement import WebElement -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.ui import WebDriverWait - - -@pytest.fixture(scope="function") -def chrome_driver(): - with webdriver.Chrome() as driver: - driver.set_window_size(1024, 768) - driver.implicitly_wait(0.5) - yield driver -``` +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L6-L26" >}} +{{< /tab >}} +{{< /tabpane >}} + "**Action Bot**" implementation. -```python -class ActionBot: - def __init__(self, driver) -> None: - self.driver = driver - self.wait = WebDriverWait( - driver, - timeout=10, - poll_frequency=2, - ignored_exceptions=[ - NoSuchElementException, - StaleElementReferenceException, - ElementNotInteractableException, - ], - ) - - def element(self, locator: tuple) -> WebElement: - self.wait.until(lambda driver: driver.find_element(*locator)) - return self.driver.find_element(*locator) - - def elements(self, locator: tuple) -> list[WebElement]: - return self.driver.find_elements(*locator) - - def hover(self, locator: tuple) -> None: - element = self.element(locator) - ActionChains(self.driver).move_to_element(element).perform() - - def click(self, locator: tuple) -> None: - element = self.element(locator) - element.click() - - def type(self, locator: tuple, value: str) -> None: - element = self.element(locator) - element.clear() - element.send_keys(value) - - def text(self, locator: tuple) -> str: - element = self.element(locator) - return element.text -``` +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L28-L65" >}} +{{< /tab >}} +{{< /tabpane >}} -"**Loadable Component** definition. -```python -class LoadableComponent: - def load(self): - raise NotImplementedError("Subclasses must implement this method") +"**Loadable Component** definition. - def is_loaded(self): - raise NotImplementedError("Subclasses must implement this method") +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L67-L80" >}} +{{< /tab >}} +{{< /tabpane >}} - def get(self): - if not self.is_loaded(): - self.load() - if not self.is_loaded(): - raise Exception("Page not loaded properly.") - return self -``` "**Loadable Component** and **Page Object**" implementation. -```python -class TodoPage(LoadableComponent): - url = "https://todomvc.com/examples/react/dist/" - - new_todo_by = (By.CSS_SELECTOR, "input.new-todo") - count_todo_left_by = (By.CSS_SELECTOR, "span.todo-count") - todo_items_by = (By.CSS_SELECTOR, "ul.todo-list>li") - - view_all_by = (By.LINK_TEXT, "All") - view_active_by = (By.LINK_TEXT, "Active") - view_completed_by = (By.LINK_TEXT, "Completed") - - toggle_all_by = (By.CSS_SELECTOR, "input.toggle-all") - clear_completed_by = (By.CSS_SELECTOR, "button.clear-completed") - - @staticmethod - def build_todo_by(s: str) -> tuple: - p = f"//li[.//label[contains(text(), '{s}')]]" - return By.XPATH, p - - @staticmethod - def build_todo_item_label_by(s: str) -> tuple: - p = f"//label[contains(text(), '{s}')]" - return By.XPATH, p - - @staticmethod - def build_todo_item_toggle_by(s: str) -> tuple: - by, using = TodoPage.build_todo_item_label_by(s) - p = f"{using}/../input[@class='toggle']" - return by, p - - @staticmethod - def build_todo_item_delete_by(s: str) -> tuple: - by, using = TodoPage.build_todo_item_label_by(s) - p = f"{using}/../button[@class='destroy']" - return by, p - - def build_count_todo_left(self, count: int) -> str: - if count == 1: - return "1 item left!" - else: - return f"{count} items left!" - - def __init__(self, driver): - self.driver = driver - self.bot = ActionBot(driver) - - def load(self): - self.driver.get(self.url) - - def is_loaded(self): - try: - WebDriverWait(self.driver, 10).until( - EC.visibility_of_element_located(self.new_todo_by) - ) - return True - except: - return False - - # business domain below - def count_todo_items_left(self) -> str: - return self.bot.text(self.count_todo_left_by) - - def todo_count(self) -> int: - return len(self.bot.elements(self.todo_items_by)) - - def new_todo(self, s: str): - self.bot.type(self.new_todo_by, s + "\n") - - def toggle_todo(self, s: str): - self.bot.click(self.build_todo_item_toggle_by(s)) - - def hover_todo(self, s: str) -> None: - self.bot.hover(self.build_todo_by(s)) - - def delete_todo(self, s: str): - self.hover_todo(s) - self.bot.click(self.build_todo_item_delete_by(s)) - - def clear_completed_todo(self): - self.bot.click(self.clear_completed_by) - - def toggle_all_todo(self): - self.bot.click(self.toggle_all_by) - - def view_all_todo(self): - self.bot.click(self.view_all_by) - - def view_active_todo(self): - self.bot.click(self.view_active_by) - - def view_completed_todo(self): - self.bot.click(self.view_completed_by) -``` +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L82-L172" >}} +{{< /tab >}} +{{< /tabpane >}} Test cases implementation with `pytest`. -```python -@pytest.fixture -def page(chrome_driver) -> TodoPage: - driver = chrome_driver - return TodoPage(driver).get() - - -class TestTodoPage: - def test_new_todo(self, page: TodoPage): - assert page.todo_count() == 0 - page.new_todo("aaa") - assert page.count_todo_items_left() == page.build_count_todo_left(1) - - def test_todo_toggle(self, page: TodoPage): - s = "aaa" - page.new_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(1) - - page.toggle_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(0) - - page.toggle_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(1) - - def test_todo_delete(self, page: TodoPage): - s1 = "aaa" - s2 = "bbb" - page.new_todo(s1) - page.new_todo(s2) - assert page.count_todo_items_left() == page.build_count_todo_left(2) - - page.delete_todo(s1) - assert page.count_todo_items_left() == page.build_count_todo_left(1) - - page.delete_todo(s2) - assert page.todo_count() == 0 - - def test_new_100_todo(self, page: TodoPage): - for i in range(100): - s = f"ToDo{i}" - page.new_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(100) - - def test_toggle_all_todo(self, page: TodoPage): - for i in range(10): - s = f"ToDo{i}" - page.new_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(10) - assert page.todo_count() == 10 - - page.toggle_all_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(0) - assert page.todo_count() == 10 - - page.toggle_all_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(10) - assert page.todo_count() == 10 - - def test_clear_completed_todo(self, page: TodoPage): - for i in range(10): - s = f"ToDo{i}" - page.new_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(10) - assert page.todo_count() == 10 - - for i in range(5): - s = f"ToDo{i}" - page.toggle_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(5) - assert page.todo_count() == 10 - - page.clear_completed_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(5) - assert page.todo_count() == 5 - - def test_view_todo(self, page: TodoPage): - for i in range(10): - s = f"ToDo{i}" - page.new_todo(s) - for i in range(4): - s = f"ToDo{i}" - page.toggle_todo(s) - - page.view_all_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(6) - assert page.todo_count() == 10 - - page.view_active_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(6) - assert page.todo_count() == 6 - - page.view_completed_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(6) - assert page.todo_count() == 4 -``` \ No newline at end of file +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L174-" >}} +{{< /tab >}} +{{< /tabpane >}} diff --git a/website_and_docs/content/documentation/test_practices/design_strategies.zh-cn.md b/website_and_docs/content/documentation/test_practices/design_strategies.zh-cn.md index 9d55b8960a49..82681ce2c3fd 100644 --- a/website_and_docs/content/documentation/test_practices/design_strategies.zh-cn.md +++ b/website_and_docs/content/documentation/test_practices/design_strategies.zh-cn.md @@ -463,280 +463,43 @@ identified, it's possible to layer PageObjects on top of bots. A `pytest` fixture `chrome_driver`. -```python -import pytest -from selenium import webdriver -from selenium.common import ( - ElementNotInteractableException, - NoSuchElementException, - StaleElementReferenceException, -) -from selenium.webdriver import ActionChains -from selenium.webdriver.common.by import By -from selenium.webdriver.remote.webelement import WebElement -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.ui import WebDriverWait - - -@pytest.fixture(scope="function") -def chrome_driver(): - with webdriver.Chrome() as driver: - driver.set_window_size(1024, 768) - driver.implicitly_wait(0.5) - yield driver -``` +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L6-L26" >}} +{{< /tab >}} +{{< /tabpane >}} + "**Action Bot**" implementation. -```python -class ActionBot: - def __init__(self, driver) -> None: - self.driver = driver - self.wait = WebDriverWait( - driver, - timeout=10, - poll_frequency=2, - ignored_exceptions=[ - NoSuchElementException, - StaleElementReferenceException, - ElementNotInteractableException, - ], - ) - - def element(self, locator: tuple) -> WebElement: - self.wait.until(lambda driver: driver.find_element(*locator)) - return self.driver.find_element(*locator) - - def elements(self, locator: tuple) -> list[WebElement]: - return self.driver.find_elements(*locator) - - def hover(self, locator: tuple) -> None: - element = self.element(locator) - ActionChains(self.driver).move_to_element(element).perform() - - def click(self, locator: tuple) -> None: - element = self.element(locator) - element.click() - - def type(self, locator: tuple, value: str) -> None: - element = self.element(locator) - element.clear() - element.send_keys(value) - - def text(self, locator: tuple) -> str: - element = self.element(locator) - return element.text -``` +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L28-L65" >}} +{{< /tab >}} +{{< /tabpane >}} -"**Loadable Component** definition. -```python -class LoadableComponent: - def load(self): - raise NotImplementedError("Subclasses must implement this method") +"**Loadable Component** definition. - def is_loaded(self): - raise NotImplementedError("Subclasses must implement this method") +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L67-L80" >}} +{{< /tab >}} +{{< /tabpane >}} - def get(self): - if not self.is_loaded(): - self.load() - if not self.is_loaded(): - raise Exception("Page not loaded properly.") - return self -``` "**Loadable Component** and **Page Object**" implementation. -```python -class TodoPage(LoadableComponent): - url = "https://todomvc.com/examples/react/dist/" - - new_todo_by = (By.CSS_SELECTOR, "input.new-todo") - count_todo_left_by = (By.CSS_SELECTOR, "span.todo-count") - todo_items_by = (By.CSS_SELECTOR, "ul.todo-list>li") - - view_all_by = (By.LINK_TEXT, "All") - view_active_by = (By.LINK_TEXT, "Active") - view_completed_by = (By.LINK_TEXT, "Completed") - - toggle_all_by = (By.CSS_SELECTOR, "input.toggle-all") - clear_completed_by = (By.CSS_SELECTOR, "button.clear-completed") - - @staticmethod - def build_todo_by(s: str) -> tuple: - p = f"//li[.//label[contains(text(), '{s}')]]" - return By.XPATH, p - - @staticmethod - def build_todo_item_label_by(s: str) -> tuple: - p = f"//label[contains(text(), '{s}')]" - return By.XPATH, p - - @staticmethod - def build_todo_item_toggle_by(s: str) -> tuple: - by, using = TodoPage.build_todo_item_label_by(s) - p = f"{using}/../input[@class='toggle']" - return by, p - - @staticmethod - def build_todo_item_delete_by(s: str) -> tuple: - by, using = TodoPage.build_todo_item_label_by(s) - p = f"{using}/../button[@class='destroy']" - return by, p - - def build_count_todo_left(self, count: int) -> str: - if count == 1: - return "1 item left!" - else: - return f"{count} items left!" - - def __init__(self, driver): - self.driver = driver - self.bot = ActionBot(driver) - - def load(self): - self.driver.get(self.url) - - def is_loaded(self): - try: - WebDriverWait(self.driver, 10).until( - EC.visibility_of_element_located(self.new_todo_by) - ) - return True - except: - return False - - # business domain below - def count_todo_items_left(self) -> str: - return self.bot.text(self.count_todo_left_by) - - def todo_count(self) -> int: - return len(self.bot.elements(self.todo_items_by)) - - def new_todo(self, s: str): - self.bot.type(self.new_todo_by, s + "\n") - - def toggle_todo(self, s: str): - self.bot.click(self.build_todo_item_toggle_by(s)) - - def hover_todo(self, s: str) -> None: - self.bot.hover(self.build_todo_by(s)) - - def delete_todo(self, s: str): - self.hover_todo(s) - self.bot.click(self.build_todo_item_delete_by(s)) - - def clear_completed_todo(self): - self.bot.click(self.clear_completed_by) - - def toggle_all_todo(self): - self.bot.click(self.toggle_all_by) - - def view_all_todo(self): - self.bot.click(self.view_all_by) - - def view_active_todo(self): - self.bot.click(self.view_active_by) - - def view_completed_todo(self): - self.bot.click(self.view_completed_by) -``` +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L82-L172" >}} +{{< /tab >}} +{{< /tabpane >}} Test cases implementation with `pytest`. -```python -@pytest.fixture -def page(chrome_driver) -> TodoPage: - driver = chrome_driver - return TodoPage(driver).get() - - -class TestTodoPage: - def test_new_todo(self, page: TodoPage): - assert page.todo_count() == 0 - page.new_todo("aaa") - assert page.count_todo_items_left() == page.build_count_todo_left(1) - - def test_todo_toggle(self, page: TodoPage): - s = "aaa" - page.new_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(1) - - page.toggle_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(0) - - page.toggle_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(1) - - def test_todo_delete(self, page: TodoPage): - s1 = "aaa" - s2 = "bbb" - page.new_todo(s1) - page.new_todo(s2) - assert page.count_todo_items_left() == page.build_count_todo_left(2) - - page.delete_todo(s1) - assert page.count_todo_items_left() == page.build_count_todo_left(1) - - page.delete_todo(s2) - assert page.todo_count() == 0 - - def test_new_100_todo(self, page: TodoPage): - for i in range(100): - s = f"ToDo{i}" - page.new_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(100) - - def test_toggle_all_todo(self, page: TodoPage): - for i in range(10): - s = f"ToDo{i}" - page.new_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(10) - assert page.todo_count() == 10 - - page.toggle_all_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(0) - assert page.todo_count() == 10 - - page.toggle_all_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(10) - assert page.todo_count() == 10 - - def test_clear_completed_todo(self, page: TodoPage): - for i in range(10): - s = f"ToDo{i}" - page.new_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(10) - assert page.todo_count() == 10 - - for i in range(5): - s = f"ToDo{i}" - page.toggle_todo(s) - assert page.count_todo_items_left() == page.build_count_todo_left(5) - assert page.todo_count() == 10 - - page.clear_completed_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(5) - assert page.todo_count() == 5 - - def test_view_todo(self, page: TodoPage): - for i in range(10): - s = f"ToDo{i}" - page.new_todo(s) - for i in range(4): - s = f"ToDo{i}" - page.toggle_todo(s) - - page.view_all_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(6) - assert page.todo_count() == 10 - - page.view_active_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(6) - assert page.todo_count() == 6 - - page.view_completed_todo() - assert page.count_todo_items_left() == page.build_count_todo_left(6) - assert page.todo_count() == 4 -``` \ No newline at end of file +{{< tabpane text=true >}} +{{< tab header="Python" >}} +{{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L174-" >}} +{{< /tab >}} +{{< /tabpane >}} From 6120c3a471d751b0a65dc213444b6332262a7f16 Mon Sep 17 00:00:00 2001 From: Peter Song Date: Mon, 14 Apr 2025 10:58:22 +0800 Subject: [PATCH 11/11] Add the other languages in the codeblock elements. So that readers can see where examples are missing and for ease of contributions. --- .../test_practices/design_strategies.en.md | 59 +++++++++++++++++- .../test_practices/design_strategies.ja.md | 60 ++++++++++++++++++- .../test_practices/design_strategies.pt-br.md | 58 +++++++++++++++++- .../test_practices/design_strategies.zh-cn.md | 59 +++++++++++++++++- 4 files changed, 224 insertions(+), 12 deletions(-) diff --git a/website_and_docs/content/documentation/test_practices/design_strategies.en.md b/website_and_docs/content/documentation/test_practices/design_strategies.en.md index f83a773869d2..67fe79d0fe00 100644 --- a/website_and_docs/content/documentation/test_practices/design_strategies.en.md +++ b/website_and_docs/content/documentation/test_practices/design_strategies.en.md @@ -445,14 +445,25 @@ Once these abstractions have been built and duplication in your tests identified ## Example +{{< tabpane text=true >}} +{{< tab header="Python" >}} + An example of `python + pytest + selenium` which implemented "**Action Bot**, **Loadable Component** and **Page Object**". A `pytest` fixture `chrome_driver`. -{{< tabpane text=true >}} -{{< tab header="Python" >}} {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L6-L26" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} @@ -462,6 +473,16 @@ A `pytest` fixture `chrome_driver`. {{< tab header="Python" >}} {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L28-L65" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} @@ -471,6 +492,16 @@ A `pytest` fixture `chrome_driver`. {{< tab header="Python" >}} {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L67-L80" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} @@ -480,12 +511,34 @@ A `pytest` fixture `chrome_driver`. {{< tab header="Python" >}} {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L82-L172" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} -Test cases implementation with `pytest`. +Test cases implementation. {{< tabpane text=true >}} {{< tab header="Python" >}} +Test cases implementation with `pytest`. + {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L174-" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} diff --git a/website_and_docs/content/documentation/test_practices/design_strategies.ja.md b/website_and_docs/content/documentation/test_practices/design_strategies.ja.md index 1a523d2cc3ec..70dfe5463726 100644 --- a/website_and_docs/content/documentation/test_practices/design_strategies.ja.md +++ b/website_and_docs/content/documentation/test_practices/design_strategies.ja.md @@ -437,14 +437,25 @@ public class ActionBot { ## Example +{{< tabpane text=true >}} +{{< tab header="Python" >}} + **Action Bot**、**Loadable Component**、および **Page Object** を実装した `python + pytest + selenium` の例です。 A `pytest` fixture `chrome_driver`. -{{< tabpane text=true >}} -{{< tab header="Python" >}} {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L6-L26" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} @@ -454,6 +465,16 @@ A `pytest` fixture `chrome_driver`. {{< tab header="Python" >}} {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L28-L65" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} @@ -463,6 +484,16 @@ A `pytest` fixture `chrome_driver`. {{< tab header="Python" >}} {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L67-L80" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} @@ -472,12 +503,35 @@ A `pytest` fixture `chrome_driver`. {{< tab header="Python" >}} {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L82-L172" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} -Test cases implementation with `pytest`. +Test cases implementation. {{< tabpane text=true >}} {{< tab header="Python" >}} + +Test cases implementation with `pytest`. + {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L174-" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} diff --git a/website_and_docs/content/documentation/test_practices/design_strategies.pt-br.md b/website_and_docs/content/documentation/test_practices/design_strategies.pt-br.md index a0e9a9f27dc0..e3c3304ba781 100644 --- a/website_and_docs/content/documentation/test_practices/design_strategies.pt-br.md +++ b/website_and_docs/content/documentation/test_practices/design_strategies.pt-br.md @@ -445,14 +445,24 @@ Once these abstractions have been built and duplication in your tests identified ## Example +{{< tabpane text=true >}} +{{< tab header="Python" >}} An example of `python + pytest + selenium` which implemented "**Action Bot**, **Loadable Component** and **Page Object**". A `pytest` fixture `chrome_driver`. -{{< tabpane text=true >}} -{{< tab header="Python" >}} {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L6-L26" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} @@ -462,6 +472,16 @@ A `pytest` fixture `chrome_driver`. {{< tab header="Python" >}} {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L28-L65" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} @@ -471,6 +491,16 @@ A `pytest` fixture `chrome_driver`. {{< tab header="Python" >}} {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L67-L80" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} @@ -480,12 +510,34 @@ A `pytest` fixture `chrome_driver`. {{< tab header="Python" >}} {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L82-L172" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} -Test cases implementation with `pytest`. +Test cases implementation. {{< tabpane text=true >}} {{< tab header="Python" >}} +Test cases implementation with `pytest`. + {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L174-" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} diff --git a/website_and_docs/content/documentation/test_practices/design_strategies.zh-cn.md b/website_and_docs/content/documentation/test_practices/design_strategies.zh-cn.md index 82681ce2c3fd..fec4a9272052 100644 --- a/website_and_docs/content/documentation/test_practices/design_strategies.zh-cn.md +++ b/website_and_docs/content/documentation/test_practices/design_strategies.zh-cn.md @@ -457,16 +457,27 @@ public class ActionBot { Once these abstractions have been built and duplication in your tests identified, it's possible to layer PageObjects on top of bots. + ## Example +{{< tabpane text=true >}} +{{< tab header="Python" >}} 一个用例 使用 `python + pytest + selenium` 实现了设计策略 "**Action Bot**, **Loadable Component** 和 **Page Object**". A `pytest` fixture `chrome_driver`. -{{< tabpane text=true >}} -{{< tab header="Python" >}} {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L6-L26" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} @@ -476,6 +487,16 @@ A `pytest` fixture `chrome_driver`. {{< tab header="Python" >}} {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L28-L65" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} @@ -485,6 +506,16 @@ A `pytest` fixture `chrome_driver`. {{< tab header="Python" >}} {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L67-L80" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} @@ -494,12 +525,34 @@ A `pytest` fixture `chrome_driver`. {{< tab header="Python" >}} {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L82-L172" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}} -Test cases implementation with `pytest`. +Test cases implementation. {{< tabpane text=true >}} {{< tab header="Python" >}} +Test cases implementation with `pytest`. + {{< gh-codeblock path="/examples/python/tests/design_strategy/using_best_practice.py#L174-" >}} {{< /tab >}} +{{< tab header="Java" >}} +{{< /tab >}} +{{< tab header="CSharp" >}} +{{< /tab >}} +{{< tab header="Ruby" >}} +{{< /tab >}} +{{< tab header="JavaScript" >}} +{{< /tab >}} +{{< tab header="Kotlin" >}} +{{< /tab >}} {{< /tabpane >}}