From 457906702908c2451567466fdc10b37a2930ed35 Mon Sep 17 00:00:00 2001 From: "iulia.liakh" Date: Fri, 21 Jul 2023 18:13:03 +0200 Subject: [PATCH 1/3] add first lesson for fourth section --- course-info.yaml | 1 + .../first_tests_with_po/lesson-info.yaml | 2 + page_object/section-info.yaml | 3 + .../what_is_po/about code style/__init__.py | 0 .../what_is_po/about code style/main.py | 19 ++++++ .../about code style/task-info.yaml | 6 ++ .../what_is_po/about code style/task.md | 42 ++++++++++++ .../code style in tests/__init__.py | 0 .../what_is_po/code style in tests/main.py | 3 + .../code style in tests/task-info.yaml | 7 ++ .../what_is_po/code style in tests/task.md | 27 ++++++++ page_object/what_is_po/code style/__init__.py | 0 page_object/what_is_po/code style/main.py | 3 + .../what_is_po/code style/task-info.yaml | 7 ++ page_object/what_is_po/code style/task.md | 32 +++++++++ .../what_is_po/first_page_object/__init__.py | 0 .../what_is_po/first_page_object/steps.py | 28 ++++++++ .../first_page_object/task-info.yaml | 21 ++++++ .../what_is_po/first_page_object/task.md | 13 ++++ .../what_is_po/first_page_object/task.py | 14 ++++ .../first_page_object/tests/__init__.py | 0 .../first_page_object/tests/test_task.py | 64 ++++++++++++++++++ page_object/what_is_po/lesson-info.yaml | 8 +++ page_object/what_is_po/task4/__init__.py | 0 page_object/what_is_po/task4/main.py | 17 +++++ page_object/what_is_po/task4/task-info.yaml | 7 ++ page_object/what_is_po/task4/task.md | 65 +++++++++++++++++++ .../what_is_po/why page object/__init__.py | 0 .../what_is_po/why page object/main.py | 6 ++ .../what_is_po/why page object/task-info.yaml | 6 ++ .../what_is_po/why page object/task.md | 33 ++++++++++ .../task_waiting_for_text/task-info.yaml | 2 +- .../task_waiting_for_text/task.md | 2 +- .../task_waiting_for_text/task.py | 2 + 34 files changed, 438 insertions(+), 2 deletions(-) create mode 100644 page_object/first_tests_with_po/lesson-info.yaml create mode 100644 page_object/section-info.yaml create mode 100644 page_object/what_is_po/about code style/__init__.py create mode 100644 page_object/what_is_po/about code style/main.py create mode 100644 page_object/what_is_po/about code style/task-info.yaml create mode 100644 page_object/what_is_po/about code style/task.md create mode 100644 page_object/what_is_po/code style in tests/__init__.py create mode 100644 page_object/what_is_po/code style in tests/main.py create mode 100644 page_object/what_is_po/code style in tests/task-info.yaml create mode 100644 page_object/what_is_po/code style in tests/task.md create mode 100644 page_object/what_is_po/code style/__init__.py create mode 100644 page_object/what_is_po/code style/main.py create mode 100644 page_object/what_is_po/code style/task-info.yaml create mode 100644 page_object/what_is_po/code style/task.md create mode 100644 page_object/what_is_po/first_page_object/__init__.py create mode 100644 page_object/what_is_po/first_page_object/steps.py create mode 100644 page_object/what_is_po/first_page_object/task-info.yaml create mode 100644 page_object/what_is_po/first_page_object/task.md create mode 100644 page_object/what_is_po/first_page_object/task.py create mode 100644 page_object/what_is_po/first_page_object/tests/__init__.py create mode 100644 page_object/what_is_po/first_page_object/tests/test_task.py create mode 100644 page_object/what_is_po/lesson-info.yaml create mode 100644 page_object/what_is_po/task4/__init__.py create mode 100644 page_object/what_is_po/task4/main.py create mode 100644 page_object/what_is_po/task4/task-info.yaml create mode 100644 page_object/what_is_po/task4/task.md create mode 100644 page_object/what_is_po/why page object/__init__.py create mode 100644 page_object/what_is_po/why page object/main.py create mode 100644 page_object/what_is_po/why page object/task-info.yaml create mode 100644 page_object/what_is_po/why page object/task.md diff --git a/course-info.yaml b/course-info.yaml index 990c473..c4c2f9f 100644 --- a/course-info.yaml +++ b/course-info.yaml @@ -12,3 +12,4 @@ content: - introduction - selenium_methods - test_frameworks +- page_object diff --git a/page_object/first_tests_with_po/lesson-info.yaml b/page_object/first_tests_with_po/lesson-info.yaml new file mode 100644 index 0000000..67735da --- /dev/null +++ b/page_object/first_tests_with_po/lesson-info.yaml @@ -0,0 +1,2 @@ +type: framework +custom_name: "writing first test with page object" diff --git a/page_object/section-info.yaml b/page_object/section-info.yaml new file mode 100644 index 0000000..3b8b160 --- /dev/null +++ b/page_object/section-info.yaml @@ -0,0 +1,3 @@ +content: +- what_is_po +- first_tests_with_po diff --git a/page_object/what_is_po/about code style/__init__.py b/page_object/what_is_po/about code style/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/page_object/what_is_po/about code style/main.py b/page_object/what_is_po/about code style/main.py new file mode 100644 index 0000000..3d3f36e --- /dev/null +++ b/page_object/what_is_po/about code style/main.py @@ -0,0 +1,19 @@ +@pytest.mark.regression +# test out of scope of a class +def test_student_can_see_lesson_name_in_lesson_in_course_after_joining(self, driver): + # lines inside the scope of the test method with indent + page = CoursePromoPage(url=self.course.url, driver=driver) + page.open() + + +class TestLessonNameInCourseForTeacher(): + @pytest.mark.regression + # test inside the class + def test_teacher_can_see_lesson_name_in_lesson_in_course(self, driver): + page = LessonPlayerPage(url=self.lesson_url, driver=driver) + page.open() + try: + # indent for any new scope + dangerous_function() + except: + close_something() \ No newline at end of file diff --git a/page_object/what_is_po/about code style/task-info.yaml b/page_object/what_is_po/about code style/task-info.yaml new file mode 100644 index 0000000..6bd1069 --- /dev/null +++ b/page_object/what_is_po/about code style/task-info.yaml @@ -0,0 +1,6 @@ +type: theory +files: +- name: main.py + visible: true +- name: __init__.py + visible: false diff --git a/page_object/what_is_po/about code style/task.md b/page_object/what_is_po/about code style/task.md new file mode 100644 index 0000000..c3d963f --- /dev/null +++ b/page_object/what_is_po/about code style/task.md @@ -0,0 +1,42 @@ +

Немного о Code Style

+ +

Среди тех, кто регулярно пишет код, +существует определенное соглашение о "стиле кода". +Стиль кода — это всё то, что не относится к его функциональности: +форматирование, имена переменных, функций, констант и так далее. +Python прекрасен тем, что его очень легко читать, +но даже такой простой для понимания язык в своём коде можно превратить +в нечитаемую кашу. Нечитаемая каша опасна тем, что вы не разберетесь +в своем коде уже через пару недель, а другой человек не разберется никогда. +Хорошо написанный код экономит время при починке тестов, +при внедрении нового человека в команду, да и при написании нового кода тоже. +В общем, это очень важная тема, и следует всегда помнить о читабельности кода.

+ +

Мы совсем немного затронули эту тему в предыдущих модулях, а теперь, раз уж мы потихоньку идём в сторону большей абстракции, настало время поговорить об этом чуть более подробно.

+ +

 Отступы

+ +

Отступы являются частью синтаксиса в Python и означают вложенность блока, будь то тело функции условного выражения, цикла, и так далее. Самое важное для нас в будущих шагах, что все функции внутри класса так же должны быть отделены отступом:

+ +
+@pytest.mark.regression
+# test out of scope of a class
+def test_student_can_see_lesson_name_in_lesson_in_course_after_joining(self, driver):
+    # lines inside the scope of the test method with indent
+    page = CoursePromoPage(url=self.course.url, driver=driver)
+    page.open()
+
+
+class TestLessonNameInCourseForTeacher():
+    @pytest.mark.regression
+    # test inside the class
+    def test_teacher_can_see_lesson_name_in_lesson_in_course(self, driver):
+        page = LessonPlayerPage(url=self.lesson_url, driver=driver)
+        page.open()
+        try:
+            # indent for any new scope
+            dangerous_function()
+        except:
+            close_something()
+
+
diff --git a/page_object/what_is_po/code style in tests/__init__.py b/page_object/what_is_po/code style in tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/page_object/what_is_po/code style in tests/main.py b/page_object/what_is_po/code style in tests/main.py new file mode 100644 index 0000000..0a9300d --- /dev/null +++ b/page_object/what_is_po/code style in tests/main.py @@ -0,0 +1,3 @@ +if __name__ == "__main__": + # Write your solution here + pass diff --git a/page_object/what_is_po/code style in tests/task-info.yaml b/page_object/what_is_po/code style in tests/task-info.yaml new file mode 100644 index 0000000..d987e14 --- /dev/null +++ b/page_object/what_is_po/code style in tests/task-info.yaml @@ -0,0 +1,7 @@ +type: theory +custom_name: 'code style in tests ' +files: +- name: main.py + visible: true +- name: __init__.py + visible: false diff --git a/page_object/what_is_po/code style in tests/task.md b/page_object/what_is_po/code style in tests/task.md new file mode 100644 index 0000000..faab4f6 --- /dev/null +++ b/page_object/what_is_po/code style in tests/task.md @@ -0,0 +1,27 @@ +

Code Style в автотестах

+ +

Здесь мы попытались собрать важные принципы написания автотестов: 

+ + + +

Если у вас нет большого опыта в написании кода, в статьях по ссылкам вы можете найти дополнительные рекомендации по оформлению кода.

+ +

Английский язык:

+ +

https://docs.python-guide.org/writing/style/

+ +

https://www.python.org/dev/peps/pep-0008/

diff --git a/page_object/what_is_po/code style/__init__.py b/page_object/what_is_po/code style/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/page_object/what_is_po/code style/main.py b/page_object/what_is_po/code style/main.py new file mode 100644 index 0000000..0a9300d --- /dev/null +++ b/page_object/what_is_po/code style/main.py @@ -0,0 +1,3 @@ +if __name__ == "__main__": + # Write your solution here + pass diff --git a/page_object/what_is_po/code style/task-info.yaml b/page_object/what_is_po/code style/task-info.yaml new file mode 100644 index 0000000..ca3a333 --- /dev/null +++ b/page_object/what_is_po/code style/task-info.yaml @@ -0,0 +1,7 @@ +type: theory +custom_name: "code style: basic principles" +files: +- name: main.py + visible: true +- name: __init__.py + visible: false diff --git a/page_object/what_is_po/code style/task.md b/page_object/what_is_po/code style/task.md new file mode 100644 index 0000000..4f3f498 --- /dev/null +++ b/page_object/what_is_po/code style/task.md @@ -0,0 +1,32 @@ +

Code Style: базовые принципы 

+ +

Имена переменных и функций

+ +

Одним из самых важных аспектов читаемого кода является именование: будь то объявление переменных, описание функций, названия классов и так далее. Очень важно, чтобы все имена, которые вы присваивали сущностям, были осмысленными и отражали реальную суть этого объекта. Избегайте однобуквенных и бессмысленных названий типа var1, x, y, my_function, class2 и так далее. Идеальный код — самодокументируемый, к которому не нужны дополнительные пояснения. Если вы чувствуете, что вам хочется написать поясняющий комментарий, это повод переписать код так, чтобы комментарий не понадобился.

+ +

Обычно внутри каждой компании есть дополнительные внутренние соглашения о том, как именовать переменные, но общие правила в индустрии примерно одинаковые.

+ +

Функции пишутся через_нижнее_подчеркивание:

+ +

def test_guest_can_see_lesson_name_in_lesson_without_course(self, driver):

+ +

Классы пишут с помощью CamelCase:

+ +

class TestLessonNameWithoutCourseForGuest():

+ +

Константы пишут в стиле UPPERCASE:

+ +

MAIN_PAGE = "/catalog"

+ +

Максимальная простота кода

+ +

Здесь нам на помощь приходят известные принципы написания кода DRY (Don't repeat yourself) и KISS (Keep it simple, stupid). 

+ + \ No newline at end of file diff --git a/page_object/what_is_po/first_page_object/__init__.py b/page_object/what_is_po/first_page_object/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/page_object/what_is_po/first_page_object/steps.py b/page_object/what_is_po/first_page_object/steps.py new file mode 100644 index 0000000..e8f620d --- /dev/null +++ b/page_object/what_is_po/first_page_object/steps.py @@ -0,0 +1,28 @@ +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +import math + +from selenium.webdriver.support.wait import WebDriverWait + + +def solve_quiz(browser): + random_value = browser.find_element(By.ID, "input_value").text + answer = (math.log(abs((12 * math.sin(float(random_value)))))) + text_field = browser.find_element(By.ID, "answer") + text_field.send_keys(str(answer)) + browser.find_element(By.ID, "solve").click() + + +def wait_for_price(browser, price): + price_text = WebDriverWait(browser, 15).until( + EC.text_to_be_present_in_element((By.ID, "price"), "$100") + ) + + +def should_be_math_text(browser): + assert browser.find_element(By.ID, "simple_text").text == "Math is real magic!" + + +def book(browser): + button = browser.find_element(By.ID, "book") + button.click() diff --git a/page_object/what_is_po/first_page_object/task-info.yaml b/page_object/what_is_po/first_page_object/task-info.yaml new file mode 100644 index 0000000..b24102e --- /dev/null +++ b/page_object/what_is_po/first_page_object/task-info.yaml @@ -0,0 +1,21 @@ +type: edu +custom_name: "task: writing abstract methods" +files: +- name: task.py + visible: true + placeholders: + - offset: 127 + length: 114 + placeholder_text: '# TODO' +- name: tests/test_task.py + visible: false +- name: __init__.py + visible: false +- name: tests/__init__.py + visible: false +- name: steps.py + visible: true + placeholders: + - offset: 681 + length: 84 + placeholder_text: '# TODO' diff --git a/page_object/what_is_po/first_page_object/task.md b/page_object/what_is_po/first_page_object/task.md new file mode 100644 index 0000000..684bf3d --- /dev/null +++ b/page_object/what_is_po/first_page_object/task.md @@ -0,0 +1,13 @@ +

Task: writing abstract methods

+ + +Now let's rewrite our script from [waiting task](course://selenium_methods/waits_expected_conditions/task_waiting_for_text) +in pytest style and using abstract methods to make test more human-readable. +Firstly, implement methods in step.py, using your code from [waiting task](course://selenium_methods/waits_expected_conditions/task_waiting_for_text) +Then, implement test in task.py using these methods. + +
  • Open the page http://suninjuly.github.io/explicit_wait2.html.
  • +
  • Wait till the cabin rent goes down to $100 (set the wait no lower than 12 seconds).
  • +
  • Click the "Book" button.
  • +
  • Assert an element with text "Math is a real magic!" element is presented
  • +
  • Solve a familiar arithmetical problem
  • \ No newline at end of file diff --git a/page_object/what_is_po/first_page_object/task.py b/page_object/what_is_po/first_page_object/task.py new file mode 100644 index 0000000..70eb311 --- /dev/null +++ b/page_object/what_is_po/first_page_object/task.py @@ -0,0 +1,14 @@ + +from steps import * + +link = "https://suninjuly.github.io/explicit_wait2.html" + + +def wait_test(browser): + browser.get(link) + wait_for_price(browser, "100$") + book(browser) + solve_quiz(browser) + browser.switch_to.alert.accept + + diff --git a/page_object/what_is_po/first_page_object/tests/__init__.py b/page_object/what_is_po/first_page_object/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/page_object/what_is_po/first_page_object/tests/test_task.py b/page_object/what_is_po/first_page_object/tests/test_task.py new file mode 100644 index 0000000..27b24e7 --- /dev/null +++ b/page_object/what_is_po/first_page_object/tests/test_task.py @@ -0,0 +1,64 @@ +import unittest +from selenium import webdriver +import time +import math + +from page_object.what_is_po.first_page_object.task import * +from page_object.what_is_po.first_page_object.steps import * + + +def check(reply): + problem_number = 2408 + minutes_to_delay= 5 + + ts_now = int(time.time()) + ts_past = ts_now - 60*minutes_to_delay + + hashcode_now = math.log(ts_now*problem_number) + hashcode_past = math.log(ts_past*problem_number) + try: + replys = float(reply) + if (replys < hashcode_now and replys > hashcode_past): + return True + elif replys <= hashcode_past: + return 0, "Срок действия кода истек, попробуйте еще раз" + else: + return 0, "Неверный код" + + except ValueError: + return 0, "Неверный формат строки, должно быть число" + + +class TestCase(unittest.TestCase): + def test_script(self): + browser = webdriver.Chrome() + try: + browser.implicitly_wait(10) + wait_test(browser) + finally: + browser.quit() + + def test_wait(self): + browser = webdriver.Chrome() + try: + browser.implicitly_wait(10) + browser.get(link) + wait_for_price(browser, "100$") + book(browser) + should_be_math_text(browser) + solve_quiz(browser) + reply = float(browser.switch_to.alert.text.split(": ")[1]) + self.assertTrue(check(reply)) + finally: + browser.quit() + + def test_wait_negative(self): + browser = webdriver.Chrome() + try: + browser.implicitly_wait(10) + browser.get("https://suninjuly.github.io/explicit_wait3.html") + wait_for_price(browser, "100$") + book(browser) + self.assertRaises(AssertionError, should_be_math_text, browser) + finally: + browser.quit() \ No newline at end of file diff --git a/page_object/what_is_po/lesson-info.yaml b/page_object/what_is_po/lesson-info.yaml new file mode 100644 index 0000000..1fa798b --- /dev/null +++ b/page_object/what_is_po/lesson-info.yaml @@ -0,0 +1,8 @@ +custom_name: what is page object model? +content: +- about code style +- code style +- code style in tests +- task4 +- first_page_object +- why page object diff --git a/page_object/what_is_po/task4/__init__.py b/page_object/what_is_po/task4/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/page_object/what_is_po/task4/main.py b/page_object/what_is_po/task4/main.py new file mode 100644 index 0000000..f99c04b --- /dev/null +++ b/page_object/what_is_po/task4/main.py @@ -0,0 +1,17 @@ +link = "http://selenium1py.pythonanywhere.com/" + + +def test_guest_can_go_to_login_page(browser): + browser.get(link) + login_link = browser.find_element(By.CSS_SELECTOR, "#login_link") + login_link.click() + + +def go_to_login_page(browser): + login_link = browser.find_element(By.CSS_SELECTOR, "#login_link") + login_link.click() + +def test_guest_can_go_to_login_page(browser): + browser.get(link) + go_to_login_page(browser) + diff --git a/page_object/what_is_po/task4/task-info.yaml b/page_object/what_is_po/task4/task-info.yaml new file mode 100644 index 0000000..4a23423 --- /dev/null +++ b/page_object/what_is_po/task4/task-info.yaml @@ -0,0 +1,7 @@ +type: theory +custom_name: "first step towards page object" +files: +- name: main.py + visible: true +- name: __init__.py + visible: false diff --git a/page_object/what_is_po/task4/task.md b/page_object/what_is_po/task4/task.md new file mode 100644 index 0000000..f6b9fe8 --- /dev/null +++ b/page_object/what_is_po/task4/task.md @@ -0,0 +1,65 @@ +

    Что такое Page Object Model?

    + +

    Page Object Model или кратко Page Object — это паттерн программирования, который очень популярен в автоматизации тестирования и является одним из стандартов при автоматизации тестирования веб-продуктов. Это также один из удобных способов структурировать свой код таким образом, чтобы его было удобно поддерживать, менять и работать с ним.

    + +

    Основная идея состоит в том, что каждую страницу веб-приложения можно описать в виде объекта класса. Способы взаимодействия пользователя со страницей можно описать с помощью методов класса. В идеале тест, который будет использовать Page Object, должен описывать бизнес-логику тестового сценария и скрывать Selenium-методы взаимодействия с браузером и страницей. При изменениях в верстке страницы не придется исправлять тесты, связанные с этой страницей. Вместо этого нужно будет поправить только класс, описывающий страницу.

    + +

    То есть здесь применяются те же принципы, что и в разработке: мы хотим повысить читаемость кода и вынести в абстрактные методы все детали. Тесты должны быть просто и понятно написаны, а повторяющиеся куски кода выделены в отдельные функции. В Page Object мы отделяем логику действий, например, авторизовать пользователя, от конкретной реализации (найти поле почты, ввести туда данные, найти поле пароля, ввести туда данные, найти кнопку и т.д.). 

    + +

    Рассмотрим такой простой тест-кейс:

    + +
      +
    1. Открыть главную страницу
    2. +
    3. Перейти на страницу логина
    4. +
    + +

    Ожидаемый результат:

    + +

    Открыта страница логина

    + +

     

    + +

    Давайте посмотрим на кусочек кода теста из предыдущего модуля, который реализует первую часть этого теста:

    + +

    test_main_page.py:

    + +
    link = "http://selenium1py.pythonanywhere.com/"
    +
    +
    +def test_guest_can_go_to_login_page(browser):
    +    browser.get(link)
    +    login_link = browser.find_element(By.CSS_SELECTOR, "#login_link")
    +    login_link.click()
    +
    + +

    Что здесь происходит?

    + +

    Мы открываем ссылку, находим элемент с определенным селектором и нажимаем на этот элемент.

    + +

    Что мы на самом деле имеем в виду семантически?

    + +

    Мы хотим открыть страницу логина. +Давайте выделим это действие в отдельную функцию с понятным названием, пока все в том же файле test_main_page.py :

    + +
    def go_to_login_page(browser):
    +    login_link = browser.find_element(By.CSS_SELECTOR, "#login_link")
    +    login_link.click()
    + +

    и наш тест упрощается:

    + +
    def test_guest_can_go_to_login_page(browser): 
    +   browser.get(link) 
    +   go_to_login_page(browser) 
    + +

    При написании следующих тестов, когда нам понадобится перейти к странице логина с главной страницы, нам не нужно будет копировать этот кусочек кода или писать заново — мы сможем переиспользовать уже написанный метод.

    + +

    Пока что мы только выделили в абстрактный метод логическое действие пользователя, которое можно переиспользовать. +В следующих шагах мы разберем как создать из этого Page Object.

    + +

    Дополнительно про Page Object вы можете почитать здесь:

    + +

    https://github.com/SeleniumHQ/selenium/wiki/PageObjects

    + +

    https://martinfowler.com/bliki/PageObject.html

    + +

    https://medium.com/tech-tajawal/page-object-model-pom-design-pattern-f9588630800b

    \ No newline at end of file diff --git a/page_object/what_is_po/why page object/__init__.py b/page_object/what_is_po/why page object/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/page_object/what_is_po/why page object/main.py b/page_object/what_is_po/why page object/main.py new file mode 100644 index 0000000..f880148 --- /dev/null +++ b/page_object/what_is_po/why page object/main.py @@ -0,0 +1,6 @@ +def test_add_to_cart(browser): + page = ProductPage(url="", browser) # initializing page object + page.open() # opening page in the browser + page.should_be_add_to_cart_button() # asserting button is on the page + page.add_product_to_cart() # pressing button + page.should_be_success_message() # asserting message with text is on the page \ No newline at end of file diff --git a/page_object/what_is_po/why page object/task-info.yaml b/page_object/what_is_po/why page object/task-info.yaml new file mode 100644 index 0000000..6bd1069 --- /dev/null +++ b/page_object/what_is_po/why page object/task-info.yaml @@ -0,0 +1,6 @@ +type: theory +files: +- name: main.py + visible: true +- name: __init__.py + visible: false diff --git a/page_object/what_is_po/why page object/task.md b/page_object/what_is_po/why page object/task.md new file mode 100644 index 0000000..a08456c --- /dev/null +++ b/page_object/what_is_po/why page object/task.md @@ -0,0 +1,33 @@ +

    Почему именно Page Object?

    + +

    Можно, конечно, хранить всю логику наших тестов в одном файле под каким-нибудь условным названием steps.py, и для начала это уже неплохо. Но если мы тестируем большой веб-продукт с множеством разных состояний и переходов, этот файл может разрастись до огромных размеров, и найти в нем нужный метод будет непросто. Еще бывают ситуации, когда на разных страницах логически один и тот же метод имеет разную реализацию. Например, у нашего интернет-магазина может быть метод "добавить в корзину". Но пользователь может добавлять товар в корзину как со страницы каталога, так и со страницы самого товара. 

    + +

    Было бы удобно выделить все методы, которые логически относятся к одной веб-странице в нашем продукте, в отдельный класс в нашем коде. Отсюда и название Page Object — это абстрактный объект, который содержит в себе методы для работы с конкретной веб-страницей. 

    + +

    Важно! Обычно методы у Page Object бывают двух типов: сделать что-то и проверить что-то.

    + +

    Рассмотрим страницу товара в интернет магазине http://selenium1py.pythonanywhere.com/catalogue/the-shellcoders-handbook_209/.

    + +

    Какие могут быть методы у Page Object, ассоциированного с такой страницей? Запишем основные сценарии: 

    + + + +

    Обратите внимание, что все проверки у нас тоже становятся отдельными методами. В самом тест-кейсе не остается никаких вспомогательных слов типа assert, только описание шагов. Прямо как в нашей тестовой документации.  

    + +

    Тесты будут выглядеть примерно так:

    + +
    def test_add_to_cart(browser):
    +    page = ProductPage(url="", browser)   # initializing page object
    +    page.open()                           # opening page in the browser
    +    page.should_be_add_to_cart_button()   # asserting button is on the page
    +    page.add_product_to_cart()            # pressing button
    +    page.should_be_success_message()      # asserting message with text is on the page
    +
    + +Таким образом, тесты становятся более абстрактными и понятными. А все детали реализации абстрактных методов скрыты внутри методов Page Object, и при необходимости могут быть переиспользованы в разных тестах. \ No newline at end of file diff --git a/selenium_methods/waits_expected_conditions/task_waiting_for_text/task-info.yaml b/selenium_methods/waits_expected_conditions/task_waiting_for_text/task-info.yaml index 0fbb05f..804068d 100644 --- a/selenium_methods/waits_expected_conditions/task_waiting_for_text/task-info.yaml +++ b/selenium_methods/waits_expected_conditions/task_waiting_for_text/task-info.yaml @@ -4,7 +4,7 @@ files: - name: task.py visible: true placeholders: - - offset: 261 + - offset: 263 length: 565 placeholder_text: '# TODO' - name: tests/test_task.py diff --git a/selenium_methods/waits_expected_conditions/task_waiting_for_text/task.md b/selenium_methods/waits_expected_conditions/task_waiting_for_text/task.md index 266190b..f91c619 100644 --- a/selenium_methods/waits_expected_conditions/task_waiting_for_text/task.md +++ b/selenium_methods/waits_expected_conditions/task_waiting_for_text/task.md @@ -8,7 +8,7 @@
  • Open the page http://suninjuly.github.io/explicit_wait2.html.
  • Wait till the cabin rent goes down to $100 (set the wait no lower than 12 seconds).
  • Click the "Book" button.
  • -
  • Solve a familiar arithmetical problem (use the previously written code) and submit the solution.
  • +
  • Solve a familiar arithmetical problem (use the previously written code)
  • To find the moment when the rent drops to $100, use the text_to_be_present_in_element method from the expected_conditions library.

    diff --git a/selenium_methods/waits_expected_conditions/task_waiting_for_text/task.py b/selenium_methods/waits_expected_conditions/task_waiting_for_text/task.py index 424e24a..483edab 100644 --- a/selenium_methods/waits_expected_conditions/task_waiting_for_text/task.py +++ b/selenium_methods/waits_expected_conditions/task_waiting_for_text/task.py @@ -6,6 +6,8 @@ link = "https://suninjuly.github.io/explicit_wait2.html" + + def wait_test(browser): def solve_quiz(browser): random_value = browser.find_element(By.ID, "input_value").text From 1c7d05af53a555fe1e4ad0590131124c55033fbd Mon Sep 17 00:00:00 2001 From: "Iulia.Liakh" Date: Fri, 28 Jul 2023 17:51:08 +0200 Subject: [PATCH 2/3] add final task for third section --- .../clicking_elements_task/task-info.yaml | 2 +- .../task_waiting_for_text/task-info.yaml | 2 +- .../task_waiting_for_text/task.py | 2 - .../{task_localizations => }/__init__.py | 0 .../lesson-info.yaml | 2 +- .../languages.png | Bin 0 -> 38600 bytes .../task.md | 6 ++- .../tests => task_localization}/__init__.py | 0 .../task_localization/conftest.py | 36 ++++++++++++++++++ .../task_localization/task-info.yaml | 26 +++++++++++++ .../task_localization/task.md | 19 +++++++++ .../task_localization/test_items.py | 11 ++++++ .../task_localization/test_items_hidden.py | 16 ++++++++ .../task_localization/tests/__init__.py | 0 .../task_localization/tests/test_task.py | 17 +++++++++ .../task_localizations/task-info.yaml | 10 ----- .../task_localizations/task.py | 3 -- .../task_localizations/tests/test_task.py | 9 ----- .../tests/test_task.py | 2 +- .../fixtures_value_task/tests/test_task.py | 2 +- .../task_scopes/task-info.yaml | 23 ++++++----- 21 files changed, 145 insertions(+), 43 deletions(-) rename test_frameworks/pytest_configuration_params/{task_localizations => }/__init__.py (100%) create mode 100644 test_frameworks/pytest_configuration_params/running tests for different localizations/languages.png rename test_frameworks/pytest_configuration_params/{task_localizations/tests => task_localization}/__init__.py (100%) create mode 100644 test_frameworks/pytest_configuration_params/task_localization/conftest.py create mode 100644 test_frameworks/pytest_configuration_params/task_localization/task-info.yaml create mode 100644 test_frameworks/pytest_configuration_params/task_localization/task.md create mode 100644 test_frameworks/pytest_configuration_params/task_localization/test_items.py create mode 100644 test_frameworks/pytest_configuration_params/task_localization/test_items_hidden.py create mode 100644 test_frameworks/pytest_configuration_params/task_localization/tests/__init__.py create mode 100644 test_frameworks/pytest_configuration_params/task_localization/tests/test_task.py delete mode 100644 test_frameworks/pytest_configuration_params/task_localizations/task-info.yaml delete mode 100644 test_frameworks/pytest_configuration_params/task_localizations/task.py delete mode 100644 test_frameworks/pytest_configuration_params/task_localizations/tests/test_task.py diff --git a/selenium_methods/basic_methods/clicking_elements_task/task-info.yaml b/selenium_methods/basic_methods/clicking_elements_task/task-info.yaml index 9fdc540..713cbf8 100644 --- a/selenium_methods/basic_methods/clicking_elements_task/task-info.yaml +++ b/selenium_methods/basic_methods/clicking_elements_task/task-info.yaml @@ -5,7 +5,7 @@ files: visible: true placeholders: - offset: 198 - length: 414 + length: 424 placeholder_text: '# TODO' - name: tests/test_task.py visible: false diff --git a/selenium_methods/waits_expected_conditions/task_waiting_for_text/task-info.yaml b/selenium_methods/waits_expected_conditions/task_waiting_for_text/task-info.yaml index 804068d..0fbb05f 100644 --- a/selenium_methods/waits_expected_conditions/task_waiting_for_text/task-info.yaml +++ b/selenium_methods/waits_expected_conditions/task_waiting_for_text/task-info.yaml @@ -4,7 +4,7 @@ files: - name: task.py visible: true placeholders: - - offset: 263 + - offset: 261 length: 565 placeholder_text: '# TODO' - name: tests/test_task.py diff --git a/selenium_methods/waits_expected_conditions/task_waiting_for_text/task.py b/selenium_methods/waits_expected_conditions/task_waiting_for_text/task.py index 483edab..424e24a 100644 --- a/selenium_methods/waits_expected_conditions/task_waiting_for_text/task.py +++ b/selenium_methods/waits_expected_conditions/task_waiting_for_text/task.py @@ -6,8 +6,6 @@ link = "https://suninjuly.github.io/explicit_wait2.html" - - def wait_test(browser): def solve_quiz(browser): random_value = browser.find_element(By.ID, "input_value").text diff --git a/test_frameworks/pytest_configuration_params/task_localizations/__init__.py b/test_frameworks/pytest_configuration_params/__init__.py similarity index 100% rename from test_frameworks/pytest_configuration_params/task_localizations/__init__.py rename to test_frameworks/pytest_configuration_params/__init__.py diff --git a/test_frameworks/pytest_configuration_params/lesson-info.yaml b/test_frameworks/pytest_configuration_params/lesson-info.yaml index 043a349..b537c2b 100644 --- a/test_frameworks/pytest_configuration_params/lesson-info.yaml +++ b/test_frameworks/pytest_configuration_params/lesson-info.yaml @@ -8,4 +8,4 @@ content: - parameters from commandline - pytest plugins - running tests for different localizations -- task_localizations +- task_localization diff --git a/test_frameworks/pytest_configuration_params/running tests for different localizations/languages.png b/test_frameworks/pytest_configuration_params/running tests for different localizations/languages.png new file mode 100644 index 0000000000000000000000000000000000000000..72bfbd4f556a67a3c735036573e9c3f9656062b0 GIT binary patch literal 38600 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 literal 0 HcmV?d00001 diff --git a/test_frameworks/pytest_configuration_params/running tests for different localizations/task.md b/test_frameworks/pytest_configuration_params/running tests for different localizations/task.md index c1a319f..c54a08d 100644 --- a/test_frameworks/pytest_configuration_params/running tests for different localizations/task.md +++ b/test_frameworks/pytest_configuration_params/running tests for different localizations/task.md @@ -4,7 +4,9 @@

    In one of our previous steps, we've already run automated tests for various languages. We used parametrization with different links, but such an approach is hard to scale up onto a large number of tests. Let's make the server decide what interface language to use depending on the browser data. The browser sends the information about the user's language via requests to the server, indicating the accept-language parameter in the header. If the server receives a request with the header {accept-language: ru, en}, it will display the Russian-language site interface. If Russian is not supported, the next language in the list will be used – in our case, English. That is actually similar to defining the preferable language in your browser's settings: 

    -

    +![languages.png](languages.png) + +See more: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language

    To choose the browser's language with WebDriver, use the Options class and the add_experimental_option method, like in the example below:

    @@ -22,4 +24,4 @@ fp.set_preference("intl.accept_languages", user_language) browser = webdriver.Firefox(firefox_profile=fp) -

    You can add different arguments to the webdriver.Chrome or webdriver.Firefox constructor, which expands the opportunities for testing your web applications: tou can define a proxy server for network traffic control or start different browser versions, indicating the local path to the browser file. We expect that later you will need these options and that you will be able to find the respective settings.

    \ No newline at end of file +

    You can add different arguments to the webdriver.Chrome or webdriver.Firefox constructor, which expands the opportunities for testing your web applications: tou can define a proxy server for network traffic control or start different browser versions, indicating the local path to the browser file. We expect that later you will need these options and that you will be able to find the respective settings.

    diff --git a/test_frameworks/pytest_configuration_params/task_localizations/tests/__init__.py b/test_frameworks/pytest_configuration_params/task_localization/__init__.py similarity index 100% rename from test_frameworks/pytest_configuration_params/task_localizations/tests/__init__.py rename to test_frameworks/pytest_configuration_params/task_localization/__init__.py diff --git a/test_frameworks/pytest_configuration_params/task_localization/conftest.py b/test_frameworks/pytest_configuration_params/task_localization/conftest.py new file mode 100644 index 0000000..5827407 --- /dev/null +++ b/test_frameworks/pytest_configuration_params/task_localization/conftest.py @@ -0,0 +1,36 @@ +import pytest +from selenium import webdriver +from selenium.webdriver.chrome.options import Options + + +def pytest_addoption(parser): + parser.addoption('--browser_name', action='store', default="chrome", + help="Choose browser: chrome or firefox") + parser.addoption('--language', action='store', default="en", + help="Choose language: en, ru, es") + + +@pytest.fixture(scope="function") +def browser(request): + browser_name = request.config.getoption("browser_name") + browser = None + user_language = request.config.getoption("language") + if browser_name == "chrome": + + options = Options() + options.add_experimental_option('prefs', {'intl.accept_languages': user_language}) + + print("\nstart chrome browser for test..") + browser = webdriver.Chrome(options=options) + browser.implicitly_wait(10) + + elif browser_name == "firefox": + print("\nstart firefox browser for test..") + fp = webdriver.FirefoxProfile() + fp.set_preference("intl.accept_languages", user_language) + browser = webdriver.Firefox(firefox_profile=fp) + else: + raise pytest.UsageError("--browser_name should be chrome or firefox") + yield browser + print("\nquit browser..") + browser.quit() diff --git a/test_frameworks/pytest_configuration_params/task_localization/task-info.yaml b/test_frameworks/pytest_configuration_params/task_localization/task-info.yaml new file mode 100644 index 0000000..a558f99 --- /dev/null +++ b/test_frameworks/pytest_configuration_params/task_localization/task-info.yaml @@ -0,0 +1,26 @@ +type: edu +files: +- name: test_items.py + visible: true + placeholders: + - offset: 223 + length: 144 + placeholder_text: '# TODO' +- name: tests/test_task.py + visible: false +- name: __init__.py + visible: false +- name: tests/__init__.py + visible: false +- name: conftest.py + visible: false + placeholders: + - offset: 101 + length: 287 + placeholder_text: '# TODO - add parameters for command-line' + - offset: 391 + length: 890 + placeholder_text: "# TODO - add browser fixture, that starts browser with options\ + \ got from command-line" +- name: test_items_hidden.py + visible: false diff --git a/test_frameworks/pytest_configuration_params/task_localization/task.md b/test_frameworks/pytest_configuration_params/task_localization/task.md new file mode 100644 index 0000000..97688ee --- /dev/null +++ b/test_frameworks/pytest_configuration_params/task_localization/task.md @@ -0,0 +1,19 @@ +

    Задание: запуск автотестов для разных языков интерфейса

    + +

    Мы хотим, чтобы разрабатываемый нами интернет-магазин работал одинаково хорошо для пользователей из любой страны. +Чтобы убедиться в работоспособности решения с поддержкой разных языков, +мы планируем запускать набор автотестов для каждого языка. +Вам как разработчику автотестов нужно реализовать решение, которое позволит запускать автотесты для разных языков пользователей, +передавая нужный язык в командной строке при запуске тестов.

    + +
      +
    1. Добавьте в файл conftest.py обработчик, который считывает из командной строки параметр language.
    2. +
    3. Реализуйте в файле conftest.py логику запуска браузера с указанным языком пользователя. Браузер должен объявляться в фикстуре browser и передаваться в тест как параметр.
    4. +
    5. В файл test_items.py напишите тест со следующим сценарием: + +
        - Найти кнопку добавление в корзину
      +
        - Проверить что после нажатия на кнопку появляется элемент, сигнализирующий об успешном добавлении в корзину
    6. +
    7. Тест должен запускаться с параметром language следующей командой: +
      +pytest --language=es test_items.py
      + и проходить успешно. Достаточно, чтобы код работал только для браузера Сhrome.
    8. diff --git a/test_frameworks/pytest_configuration_params/task_localization/test_items.py b/test_frameworks/pytest_configuration_params/task_localization/test_items.py new file mode 100644 index 0000000..1b5f5dd --- /dev/null +++ b/test_frameworks/pytest_configuration_params/task_localization/test_items.py @@ -0,0 +1,11 @@ +import pytest +from selenium.webdriver.common.by import By + +@pytest.fixture +def link(): + return "http://selenium1py.pythonanywhere.com/catalogue/coders-at-work_207/" + +def test_is_have_button_add_to_basket(browser, link): + browser.get(link) + browser.find_element(By.CLASS_NAME, "btn-add-to-basket").click() + browser.find_element(By.CLASS_NAME, "alertinner") diff --git a/test_frameworks/pytest_configuration_params/task_localization/test_items_hidden.py b/test_frameworks/pytest_configuration_params/task_localization/test_items_hidden.py new file mode 100644 index 0000000..1a86a50 --- /dev/null +++ b/test_frameworks/pytest_configuration_params/task_localization/test_items_hidden.py @@ -0,0 +1,16 @@ +import pytest + +from test_frameworks.pytest_configuration_params.task_localization import test_items +from selenium.webdriver.common.by import By + + +@pytest.mark.xfail(strict=True) +def test_negative(browser): + test_items.test_is_have_button_add_to_basket(browser, + "http://selenium1py.pythonanywhere.com/en-gb/catalogue/the-cathedral-the-bazaar_190/") + + +def test_french(browser): + test_items.test_is_have_button_add_to_basket(browser, + "http://selenium1py.pythonanywhere.com/catalogue/coders-at-work_207/") + assert browser.find_element(By.CLASS_NAME, "btn-add-to-basket").text == "Ajouter au panier" diff --git a/test_frameworks/pytest_configuration_params/task_localization/tests/__init__.py b/test_frameworks/pytest_configuration_params/task_localization/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test_frameworks/pytest_configuration_params/task_localization/tests/test_task.py b/test_frameworks/pytest_configuration_params/task_localization/tests/test_task.py new file mode 100644 index 0000000..3de1c7b --- /dev/null +++ b/test_frameworks/pytest_configuration_params/task_localization/tests/test_task.py @@ -0,0 +1,17 @@ +import sys +import unittest + +import pytest +from pytest import ExitCode + + +class TestCase(unittest.TestCase): + def test_espanol(self): + assert pytest.main(["--language=es", "test_items.py"]) == ExitCode.OK + + def test_french(self): + assert pytest.main(["--language=fr", "test_items.py"]) == ExitCode.OK + + def test_negative(self): + assert pytest.main(["--language=fr", "test_items_hidden.py"]) == ExitCode.OK + diff --git a/test_frameworks/pytest_configuration_params/task_localizations/task-info.yaml b/test_frameworks/pytest_configuration_params/task_localizations/task-info.yaml deleted file mode 100644 index 9b47fb4..0000000 --- a/test_frameworks/pytest_configuration_params/task_localizations/task-info.yaml +++ /dev/null @@ -1,10 +0,0 @@ -type: edu -files: -- name: task.py - visible: true -- name: tests/test_task.py - visible: false -- name: __init__.py - visible: false -- name: tests/__init__.py - visible: false diff --git a/test_frameworks/pytest_configuration_params/task_localizations/task.py b/test_frameworks/pytest_configuration_params/task_localizations/task.py deleted file mode 100644 index 98ceac9..0000000 --- a/test_frameworks/pytest_configuration_params/task_localizations/task.py +++ /dev/null @@ -1,3 +0,0 @@ -# todo: replace this with an actual task -def sum(a, b): - return a + b diff --git a/test_frameworks/pytest_configuration_params/task_localizations/tests/test_task.py b/test_frameworks/pytest_configuration_params/task_localizations/tests/test_task.py deleted file mode 100644 index 9dbaea9..0000000 --- a/test_frameworks/pytest_configuration_params/task_localizations/tests/test_task.py +++ /dev/null @@ -1,9 +0,0 @@ -import unittest - -from task import sum - - -# todo: replace this with an actual test -class TestCase(unittest.TestCase): - def test_add(self): - self.assertEqual(sum(1, 2), 3, msg="adds 1 + 2 to equal 3") diff --git a/test_frameworks/pytest_fixtures/fixtures_finalizing_task/tests/test_task.py b/test_frameworks/pytest_fixtures/fixtures_finalizing_task/tests/test_task.py index 92cc242..89d32b0 100644 --- a/test_frameworks/pytest_fixtures/fixtures_finalizing_task/tests/test_task.py +++ b/test_frameworks/pytest_fixtures/fixtures_finalizing_task/tests/test_task.py @@ -33,7 +33,7 @@ def check(reply): class TestCase(unittest.TestCase): def test_script(self): - pytest.main(["task.py"]) + pytest.main(["test_items.py"]) assert not os.path.isfile(filename) diff --git a/test_frameworks/pytest_fixtures/fixtures_value_task/tests/test_task.py b/test_frameworks/pytest_fixtures/fixtures_value_task/tests/test_task.py index b3bbe4d..5e3df7e 100644 --- a/test_frameworks/pytest_fixtures/fixtures_value_task/tests/test_task.py +++ b/test_frameworks/pytest_fixtures/fixtures_value_task/tests/test_task.py @@ -34,7 +34,7 @@ def check(reply): class TestCase(unittest.TestCase): def test_script(self): try: - pytest.main(["task.py"]) + pytest.main(["test_items.py"]) os.rename(filename, newname) os.rename(newname, filename) # Raise error if the file has opened diff --git a/test_frameworks/pytest_fixtures/task_scopes/task-info.yaml b/test_frameworks/pytest_fixtures/task_scopes/task-info.yaml index 1c57d86..6b2a515 100644 --- a/test_frameworks/pytest_fixtures/task_scopes/task-info.yaml +++ b/test_frameworks/pytest_fixtures/task_scopes/task-info.yaml @@ -4,20 +4,17 @@ files: - name: task.py visible: true placeholders: - - offset: 186 - length: 114 - placeholder_text: '# TODO' - - offset: 317 - length: 7 + - offset: 422 + length: 24 placeholder_text: '# TODO' - - offset: 437 - length: 8 + - offset: 489 + length: 26 + placeholder_text: '#TODO' + - offset: 611 + length: 40 placeholder_text: '# TODO' - - offset: 504 - length: 10 - placeholder_text: '# TODO' - - offset: 626 - length: 24 + - offset: 302 + length: 23 placeholder_text: '# TODO' - name: tests/test_task.py visible: false @@ -25,5 +22,7 @@ files: visible: false - name: tests/__init__.py visible: false +- name: test_task.py + visible: false - name: answer.txt visible: true From 320949304a035044282f598845cecccef5bafa80 Mon Sep 17 00:00:00 2001 From: stephen-hero <78870893+stephen-hero@users.noreply.github.com> Date: Wed, 9 Aug 2023 14:55:34 +0300 Subject: [PATCH 3/3] Update task.md language checked --- introduction/finding_elements/find_child_element/task.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/introduction/finding_elements/find_child_element/task.md b/introduction/finding_elements/find_child_element/task.md index fa45414..a19781f 100644 --- a/introduction/finding_elements/find_child_element/task.md +++ b/introduction/finding_elements/find_child_element/task.md @@ -1,8 +1,8 @@

      Task: search of descendant

      -

      Open the page http://suninjuly.github.io/cats.html. Open the developer console, the Elements tab. Write a minimal sufficient CSS selector that will find the serious cat picture (Serious cat). For finding the appropriate element in the picture folder, use the parent element div.col-sm-4 together with the pseudo-class  :nth-child(n) to choose the n-th element, as well as the picture selector based on the img tag. When writing the selector, don't use the > symbol, as this is a task of finding a descendant without using child elements. To solve it, the above-mentioned selectors must be enough.

      +

      Open the page http://suninjuly.github.io/cats.html. Open the developer console, the Elements tab. Write a minimal sufficient CSS selector that will find the serious cat picture (Serious cat). For finding the appropriate element in the picture folder, use the parent element div.col-sm-4 together with the pseudo-class  :nth-child(n) to choose the n-th element, as well as a picture selector based on the img tag. When writing the selector, don't use the > symbol, as this is a task of finding a descendant without using child elements. To solve it, the above-mentioned selectors must be enough.

      -

      Please do not use selectors generated by the browser when clicking the "Copy CSS selector" button or by extensions. They often build the whole path starting from the body, and that is a very unstable selector; writing such selectors in your code is a bad practice. In case of a slightest modification in the page structure, all your selectors will lose relevance. 

      +

      Please do not use selectors generated by the browser when clicking the "Copy CSS selector" button or those generated by extensions. They often build the whole path starting from the body, and that is a very unstable selector; writing such selectors in your code is a bad practice. In case of a slightest modification in the page structure, all your selectors will lose relevance. 

      Do not use square brackets – constructions like [class="stepic"].