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/introduction/css_practice/data_type/task.md b/introduction/css_practice/data_type/task.md index 24f57e8..bf2be24 100644 --- a/introduction/css_practice/data_type/task.md +++ b/introduction/css_practice/data_type/task.md @@ -1,5 +1,5 @@

Selector with data-type attribute

-Open page https://suninjuly.github.io/css-tasks/custom-attribute . +Open the page https://suninjuly.github.io/css-tasks/custom-attribute . Try to write a selector that will find the element with the description text for the card with the first cat. Use the data-type attribute. 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/test_frameworks/pytest_configuration_params/task_localizations/__init__.py b/page_object/what_is_po/about code style/__init__.py similarity index 100% rename from test_frameworks/pytest_configuration_params/task_localizations/__init__.py rename to page_object/what_is_po/about code style/__init__.py 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/test_frameworks/pytest_configuration_params/task_localizations/tests/__init__.py b/page_object/what_is_po/code style in tests/__init__.py similarity index 100% rename from test_frameworks/pytest_configuration_params/task_localizations/tests/__init__.py rename to page_object/what_is_po/code style in tests/__init__.py 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/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.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/test_frameworks/pytest_configuration_params/__init__.py b/test_frameworks/pytest_configuration_params/__init__.py new file mode 100644 index 0000000..e69de29 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 0000000..72bfbd4 Binary files /dev/null and b/test_frameworks/pytest_configuration_params/running tests for different localizations/languages.png differ 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_localization/__init__.py b/test_frameworks/pytest_configuration_params/task_localization/__init__.py new file mode 100644 index 0000000..e69de29 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