Есть отличная удаленная работа для php+codeception+jenkins+allure+docker спецов. 100% remote! Присоединиться к проекту

Ищу пример простого микрофреймворк и примера паттерна PageObject

pytest
python
selenium
Теги: #<Tag:0x00007f7b69eb2058> #<Tag:0x00007f7b69eb1db0> #<Tag:0x00007f7b69eb1ae0>

(Gukobrist) #1

Есть небольшой опыт использования selemium, pytest, nosetests, behave

Вы не могли бы привести пример небольшого, с вашей точки зрения, эталонного микрофреймворка для тестирование?

Пример - любой сайт имеет шапку, меню и подвал - элементы, которые одинаковы на каждой странице.

Подскажите, как построить паттерн pageobject используя только selenium и например pytest, без сторонних библиотек.

Пример - локаторы находятся в отдельном файле locators.py (вообще можно ли так?), описание страницы (pageobject) в отдельном файле pageobject.py, тесты в отдельном файле test.py, настройка драйвера (или как это правильно называется) в отдельном файле common.py.

Для примера можно хоть 1 поле с 1 кнопкой сделать.

Я очень хочу разобраться, но везде уже столько на*уеверчено, всяких сторонних библиотек и прочего мусора. Мне бы увидеть основы и разобраться в них. Если кто-то может помочь буду вам очень благодарен.


(Gukobrist) #2

Хорошо. Я попробую ответить на свой вопрос сам. А вы меня поправьте если можете.


(Sined1993) #3

Возможно вам поможет эта тема:


Или это:


(Gukobrist) #4

Спасибо большое.


(Gukobrist) #5

А вы не подскажите, что такое фикстуры и что такое инстансы? Я правильно понимаю, что инстанс это некоторое состояние, а фикстура это состояние фиксирует.

Например нам нужно тестировать поиск - что будет фикстурой, а что будет инстанс?

Фикстура это просто фиксация состояния, а состояние это например - браузер chrome, размер окна maximize_window и т.д.?

Я просто нигде не могу найти точного определения.


(Roy Obenon) #6

Фикстура - это декоратор, декоратор - это например функция, которая принимает на вход (функцию, класс, метод, любой объект, хотя если речь идет об изменении его то immutable не подойдут)(короче это паттерн) ± переменные и выдает какой то результат, при желание можно подробней ознакомится что такое декоратор. В контексте тестов мы создаем функция декорируя её фикстурой:

@pytest.fixture()
def browser(request):
      br = request.config.getoption('--browser')
      return BrowserFactory(br)

и в каждом тесте мы просто юзаем фикстуру browser получая инстанс нужого нам драйвера (соответственно передав нужный параметр --browser chrome в строку запуска теста). Обрамляя функцию в фикстуру заменяет аналоги beforeTest, beforeClass, beforeMethod прекондишенов например в Java. Фикстура проделает свою работу перед началом теста и если нужно то и после теста, в документации всё есть!

На счет инстанса в контексте питона, то в питоне инстанс это ВСЁ)) это экземпляр, в питоне всё является объектом. Чтобы говорить о инстансе (в питоне) нужно понимать вообще его объектную модель. так что к тестам это не имеет в данном случае ни какого отношения, если я правильно понял суть вопросов выше))))


(Gukobrist) #7

Мне кажется вы правильно поняли мой вопрос. Спасибо. А как могли бы выглядеть фикстуры например для таких сценариев:

Мы тестируем логин форму посты - позитивный сценарий. Предусловие - мы не залогинены. Вводим в поля логин и пароль, жмем кнопку “Вход”. Ожидаемый результат - заголовок страницы сменился на “Входящие письма”.

Мы тестируем логин форму, кнопку выхода из аккаунта. Предусловие - пользователь залогинен под (тестовые входные данные). Нажимаем кнопку “Выход”. Ожидаемый результат - получаем nitification “Вы вышли из аккаунта”.

Если есть такой пример конечно. Спасибо.


(Roy Obenon) #8

Должна быть фикстура по созданию пользователя, все остальные шаги исключительно в тестах!

@pytest.fixture
def temporary_account():
      login, password = тут код для создание пользователя
      yield login, password
      код удаления юзера

Если часто встречается в тестах “Предусловие - пользователь залогинен под (тестовые входные данные).” вынести его с метод-степ и юзать в тесте.

Забыл добавить на счет этого

не юзайте это, потом это превращается в файл-простыню и в итоге КАША, локаторы должны находится в классе с пейджой.


(Gukobrist) #9

Короче я в рамках обучения решил реализовать тестирование формы логина, а затем какой-нибудь пользовательский сценарий - например вход в почту и отправка письма через почтовый сервис mail.ru.

В первую очередь необходимо создать базовый файл с базовым классом, я назвал его base.py но мне кажется это какое-то неверное название.

Этот пример я тут на форуме нашел.

Класс SeleniumWrapper это обертка для одного состояния. Оно запускает вебдрайвер chrome в полное окно и переходит на страницу mailru

from selenium import webdriver


class SeleniumWrapper:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(SeleniumWrapper, cls).__new__(cls, *args, **kwargs)
        return cls._instance

    def connect(self, host="https://mail.ru/"):
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        self.base_url = host
        return self.driver

Но я все равно не до конца въезжаю. Что например такое _instance = None и функция def new


(Roy Obenon) #10

это реализация синглетона для этого класса)


(Gukobrist) #13

Смотрите. Я написал тест в одном файле. Я так делал раньше, но раньше я еще использовал библиотеку unittest и nosetests. Я хочу разобраться именно в связке чистого pytest и чистого selenium на основе паттерна проектирования тестов PageObject. Помогите пожалуйста мне запустить данный тест, а так разнести все данные, как этого требуется для паттерна.

Разнести я имею ввиду, что судя по описаниям и представлениям, есть некий файл в котором хранятся только элементы страницы и методы связанные с этими элементами, а в других файлах пишутся только тестовые сценарии, а в третьем настройки самого вебдрайвера, инстансов и фикстур (вот тут я пока не понимаю о чем говорю, хотя стараюсь).

test_login.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

hostname = 'https://mail.ru/'
login = 'fbs_test1203@mail.ru'
password = '1Passw0rd'

class MainPageMail(unittest.TestCase):

    def setUp(self):
        self.driver = webdriver.Chrome()


    def the_login_test(self):

        driver = self.driver
        wait = WebDriverWait(driver, 100)
        driver.get(hostname)

        login_field = driver.find_element(By.XPATH, value="//input[@id='mailbox:login']")
        password_field = driver.find_element(By.XPATH, value="//input[@id='mailbox:password']")
        submit_button = driver.find_element(By.XPATH, value="//input[@value='Войти']")

        wait.until(EC.element_to_be_clickable((login_field)))
        login_field.send_keys(login)
        wait.until(EC.element_to_be_clickable((password_field)))
        password_field.send_keys(password)
        wait.until(EC.element_to_be_clickable((submit_button)))
        submit_button.click()

    def tearDown(self):
        self.driver.quit()

Запускается так nosetests test_login.py
Если запускаю через pytest то вообще ничего не работает.


(Roy Obenon) #14

https://docs.pytest.org/en/latest/goodpractices.html#test-discovery то всё четко и ясно написано


(Gukobrist) #15

Блин, я кажется начинаю понимать. В одном файле мы описываем отдельно ключевые объекты страницы - это могут быть кнопки, поля, ссылки и еще какие-то вещи. Каждая функция - это метод позволяющий обратиться к этому элементу.

Затем отдельно пишется файл с тестом. В этом файле используются как раз выше описанные методы для обращения к тому или иному элементу на странице. Выстраивание последовательных цепочек из этих методов - это и есть наши тестовые случаи или тестовые сценарии.

А вот что обычно еще за файл один, его еще common называют или типа того. Что там содержится.


(Gukobrist) #16

Блин, я теперь не могу понять, как переделать данный тест, чтобы он запускался под pytest. не хочет брать его(

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait

hostname = 'https://mail.ru/'
login = 'fbs_test1203@mail.ru'
password = '1Passw0rd'

class MainPageMail(unittest.TestCase):

    def setUp(self):
        self.driver = webdriver.Chrome()


    def the_login_test(self):

        driver = self.driver
        wait = WebDriverWait(driver, 100)
        driver.get(hostname)
        assert "Mail.Ru" in driver.title

        login_field = driver.find_element(By.XPATH, value="//input[@id='mailbox:login']")
        password_field = driver.find_element(By.XPATH, value="//input[@id='mailbox:password']")
        submit_button = driver.find_element(By.XPATH, value="//input[@value='Войти']")

        login_field.send_keys(login)
        password_field.send_keys(password)
        submit_button.click()

    def tearDown(self):
        self.driver.quit()

(Дмитрий Золкин) #17

Зачем наследоваться от unittest. Вы же используете pytest. И название класса должно быть с Test
TestMainPageMail. Вроде так…Могу ошибаться и файлик где код теста по-моему должен тоже быть вида test_mainpagemail- например и метод класса (TestMainPageMail) должен быть test_login

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait

hostname = 'https://mail.ru/'
login = 'fbs_test1203@mail.ru'
password = '1Passw0rd'

class TestMainPageMail():

    def setup_class(self):
        self.driver = webdriver.Chrome()


    def test_the_login(self):

        driver = self.driver
        wait = WebDriverWait(driver, 100)
        driver.get(hostname)
        assert "Mail.Ru" in driver.title

        login_field = driver.find_element(By.XPATH, value="//input[@id='mailbox:login']")
        password_field = driver.find_element(By.XPATH, value="//input[@id='mailbox:password']")
        submit_button = driver.find_element(By.XPATH, value="//input[@value='Войти']")

        login_field.send_keys(login)
        password_field.send_keys(password)
        submit_button.click()

    def teardown_class(self):
        self.driver.quit()

Так должно вроде заработать


(Gukobrist) #18

Сделал все как вы сказали. Pytest не хочет запускать мой тест test_login.py

Запускаю так

(myvenv) user@user-desktop:~/Projects/Autotests$ pytest test_login.py

Выдает

============================= test session starts ==============================
platform linux -- Python 3.5.2, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
rootdir: /home/user/Projects/Autotests, inifile:
collected 0 items                                                              

========================= no tests ran in 0.04 seconds =========================

Мой код.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait


hostname = 'https://mail.ru/'
login = 'fbs_test1203@mail.ru'
password = '1Passw0rd'

class TestMainPageMail():

    def setUp(self):
        self.driver = webdriver.Chrome(executable_path=r'/home/user/Projects/Autotests/myvenv/bin/chromedriver')

    def the_login_test(self):

        driver = self.driver
        wait = WebDriverWait(driver, 100)
        driver.get(hostname)
        assert "Mail.Ru" in driver.title
        print(driver.title)

        login_field = driver.find_element(By.XPATH, value="//input[@id='mailbox:login']")
        password_field = driver.find_element(By.XPATH, value="//input[@id='mailbox:password']")
        submit_button = driver.find_element(By.XPATH, value="//input[@value='Войти']")

        login_field.send_keys(login)
        password_field.send_keys(password)
        submit_button.click()
        assert "Входящие" in driver.title
        print(driver.title)

    def tearDown(self):
        self.driver.quit()

(Gukobrist) #19

Блин, это какой-то все бред. Нет, я понимаю, что там что-то важное, но я читаю и ничерта не понимаю. Отвратительная документация. Я пробовал читать несколько раз и ничерта не понятно. Примеры бы хоть нормальные привели.


(Дмитрий Золкин) #20
# coding = utf-8
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait


hostname = 'https://mail.ru/'
login = 'fbs_test1203@mail.ru'
password = '1Passw0rd'

class TestMainPageMail():

    def setup_class(self):
        self.driver = webdriver.Firefox()

    def test_login(self):

        driver = self.driver
        wait = WebDriverWait(driver, 100)
        driver.get(hostname)
        assert "Mail.Ru" in driver.title
        print(driver.title)

        login_field = driver.find_element(By.XPATH, value="//input[@id='mailbox:login']")
        password_field = driver.find_element(By.XPATH, value="//input[@id='mailbox:password']")
        submit_button = driver.find_element(By.XPATH, value="//input[@value='Входящие']")

        login_field.send_keys(login)
        password_field.send_keys(password)
        submit_button.click()
        assert "Входящие" in driver.title
        print(driver.title)

    def teardown_class(self):
        self.driver.quit()

Изменил метод на
def setup_class(self):
self.driver = webdriver.Firefox()

def teardown_class(self):
self.driver.quit()

def test_login(self): --Начинается со слова test

Ну и драйвер я Firefox поставил нет Chrome у меня

Пишу на 2.7 python pytest 2.9.0, но думаю у Вас тоже должно запуститься
Смотрите внимательно вы не сделали все как я сказал))


(Gukobrist) #21

Короче пытаюсь переписать под pytest, но сталкиваюсь с тем, что нужно разнести селекторы и описание элементов и тесты в разные места. Натолкните на мысль пожалуйста, как быть?

conftest.py

import pytest
from selenium import webdriver

@pytest.yield_fixture(scope='session')
def browser():
    driver = webdriver.Chrome('/home/user/Projects/Autotests/myvenv/bin/chromedriver')
    driver.maximize_window()
    yield driver
    driver.quit()

test_login_form.py

from selenium.webdriver.common.by import By
import time

hostname = 'https://mail.ru/'
login = 'fbs_test1203@mail.ru'
password = '1Passw0rd'


def test_open_main_page(browser):

    login_field = browser.find_element(By.XPATH, value="//input[@id='mailbox:login']")
    password_field = browser.find_element(By.XPATH, value="//input[@id='mailbox:password']")
    submit_button = browser.find_element(By.XPATH, value="//input[@value='Войти']")

    browser.get(hostname)
    assert "Mail.Ru" in browser.title

    time.sleep(5)

    login_field.send_keys(login)
    password_field.send_keys(password)
    submit_button.click()

    assert "Входящие" in browser.title

Вот я чувствую что не хватает файла, на потипу main_page.py в котором бы лежали селекторы и методы для каждого элемента.


(Gukobrist) #23

Короче, благодаря тебе мой друг, мне удалось сделать свою первую фикстуру и сделать Первый простой тест. Я добавил туда неявные ожидания. Очень благодарен.

conftest.py

import pytest
from selenium import webdriver

@pytest.yield_fixture(scope='session')
def driver():
    driver = webdriver.Chrome()
    driver.maximize_window()
    yield driver
    driver.quit()

test_login_form.py

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

hostname = 'https://mail.ru/'
login = 'fbs_test1203@mail.ru'
password = '1Passw0rd'

"""
Тест на заполнение формы корректными данными. Тест является пройденным при проверке на наличии фразы "Входящие"
в заголовке Title после нажатия кнопки Войти.
"""
def test_input_login_password_correct(driver):
    driver.get(hostname)
    wait = WebDriverWait(driver, 10)

    assert "Mail.Ru" in driver.title

    login_field = driver.find_element(By.XPATH, value="//input[@id='mailbox:login']")
    password_field = driver.find_element(By.XPATH, value="//input[@id='mailbox:password']")
    submit_button = driver.find_element(By.XPATH, value="//input[@value='Войти']")

    login_field.send_keys(login)
    password_field.send_keys(password)
    submit_button.click()
    try:
        element = wait.until(EC.title_contains("Входящие"))
    finally:
        assert "Входящие" in driver.title

Но вопрос остается открытым. Я все еще ищу примеры как вынести локаторы, желательно в отдельный класс отдельной страницы, желательно в отдельный файл) В общем не понимаю пока как организовать pageobject.