Я привёл пример - тест не на открытие, а на закрытие. При выборе значения дропдаун может и закроется, а вот при клике вне дропдауна нет. Это разные сценарии. Возможно вы и правы насчёт порога вхождения - сложно судить, так как это вещь субъективная. В любом случае придётся вникать откуда что приходит и какие методы доступны через объект. А уж нажать шорткат в IDE и увидеть что там внутри того или иного метода творится не выходя из класса дело секундное.
Та отлично похоливарили ну а вот такие методы предпочитаю делать только по надобности и это точно буду паблик методы, которые могут/будут использоваться для комплексного бизнес сценария
Существующий WebElement - это реализация selenium’a. Мне нужен был враппер, чтобы делать кэширование и отложенную инициализацию. Кроме того, хотелось спрятать низкоуровневые вызовы и предоставить высокоуровневый API. Если для работы с дефолтными полями и кнопками эти методы совпадают, то для сложных структур, типа листов, таблиц, деревьев, а также кастомных комбобоксов и других элементов, низкоуровневая логика будет весьма значительна в коде.
В декораторы можно все UI не загонять, а описывать их как проперти внутри класса (что посути и делает декоратор).
Насколько кэш ускоряет тесты? Я не вижу проблем его добавить в Webium, но непонятно зачем. А отложенная инициализация - это как раз и есть то, как Webium работает.
код не понравился
Как в POM делается проверка на то, есть ли элемент?
По поводу сложных структур. Find — Webium 1.2.1 documentation - ну вот оно и есть. А зачем прятать низкоуровневый API WebElementа? При желании можно не дать его вызывать при наследовании конечно.
@Igor,
Я вечером напишу, что именно мне не нравится в webium (глобальный объект драйвера, проброс наружу низкоуровневых объектов, is_element_present как самостоятельная сущность и проч.) и отвечу на ваши вопросы и опишу, почему на мой взгляд POM удобнее. К сожалению сейчас нет возможности.
Доброго вечера, к сожалению вчера был не лучший день для код ревью
Почему я предпочел написать POM, а не использовать webium для UI-автотестов.
В целом код webium’a плохо документирован и додумывать за авторов совершенно не хочется. Это резко снижает желание юзать стороннюю либу и контрибьютить в нее, исправляя ошибки и добавляя новую функциональность. Проще сделаю свое под себя, а заодно прокачаться в построении архитектуры фреймворка (POM делался в свободное время).
Архитектура webium’a на мой взгляд не полноценна. Сразу начинается с декларирования страницы (Welcome to Webium’s documentation! — Webium 1.2.1 documentation). Хотя страница - это лишь часть объекта-агрегатора страниц - приложения. Я считаю, что правильно выстраивать цепочку зависимостей Application → Pages → UI-elements. То что для работы с Application используется браузер - об этом у автоматизатора должно быть минимум знаний (лишь указать при инициализации application’a, какой браузер использовать, кое-какие настройки для него, URL доступа: pom/base.py at master · schipiga/pom · GitHub). Дальше должно быть управление только через сущность application (по этой причине кстати POM не поддерживает работу с несколькими страницами браузера. Я согласен, что это недоработка, т.к. многооконность неотъемлема при тестировании, и будет имплементирована позже когда остро понадобится).
Implicit_wait, который использует webium. На мой взгляд - это одна из опасных вещей, которая есть в selenium’e. Implicit_wait работает всегда. Eсли нужно вдруг получить значение сразу н-р для element.is_present, приходится его отключать. Это порождает н-р такой код webium/no_implicitly_wait.py at master · wgnet/webium · GitHub. По-моему, куда логичнее и явнее по умолчанию возвращать результат вызова метода сразу, без неявного ожидания. А методы элемента, перед которыми требуется ожидание, нужно оборачивать в декоратор, который будет устанавливать время ожидания перед вызовом метода и сбрасывать после. Да, это приводит к дополнительным запросам к selenium’у, но ничего в этом страшного нет, селениум от этого не умрет, на скорость тестов это несильно повлияет. Стоящий прирост скорости дает параллельность тестов, а не микрооптимизации. Стабильность и явность поведения тестов важнее, время автоматизатора дороже времени выполнения автотеста.
Чтобы управлять implicit_wait, webium’у требуется из любого места (из любого UI-объекта) иметь доступ к webdriver-объекту, и поэтому в webuim есть глобальный объект webium/driver.py at master · wgnet/webium · GitHub. Это плохо, но по-другому нельзя заимплементить, либо везде пробрасывать webdriver дополнительным объектом, что в коде выглядеть будет совсем некрасиво. Вот чтобы избежать этой глобализации в POM реализован собственный механизм ожидания: pom/base.py at master · schipiga/pom · GitHub. Кроме того благодаря этому, реализован также механизм ожидания отсутствия UI-элемента: pom/base.py at master · schipiga/pom · GitHub, что бывает весьма востребовано в тестах. При этом объект webdriver’a не достается, как туз из рукава, потому что про него знать не нужно. (P.S. Ошибаюсь, UI-Элементы в POM знают про webdriver, т.к. он нужен для action_chains: pom/base.py at master · schipiga/pom · GitHub, но только он извлекается не через глобальный объект)
Webium использует implicit_wait, что приводит к появлению кода webium/base_page.py at master · wgnet/webium · GitHub. is_element_present - представлена не просто как самостоятельная функция, но ей даже уделена целая глава в документации, чтобы описать ее возможности и стратегии поведения с различными аргументами. Помоему высокоуровневый инструмент не должен заниматься тем, чтобы отдавать наружу порцию низкоуровневых операций. Если нужен low-level - нужно использовать webdriver напрямую. Код is_element_present не документирован, а разбираться в логике его стратегии мне как стороннему разработчику, честно совершенно не хочется. Такая избыточность кода с if, внутренними функциями и try-except’ами сразу наводит на мысль, что там что-то не так. Кроме того, implicit_wait ждет не тогда, когда элемент отобразиться, а когда появится в DOM’e, поэтому в webium появляется такой код webium/base_page.py at master · wgnet/webium · GitHub в попытке дать как можно больше гибкости. POM смотрит на проблему is_present по-другому: если элемент отображен - значит он есть, нет - значит нет (неважно в DOM’e он или нет) - также смотрит пользователь, а POM для user e2e-сценариев. И пока ни в одном тесте не возникло необходимости сделать по-другому.
Webium декларирует UI-элементы, как проперти класса (не объекта!).
Во-первых это заставляет делать Find дескриптором, что по-моему весьма странно для UI-элемента, знать что он должен быть дескриптором. UI-элемент должен знать только то, что связано с ним как с частью Page, а не то, что он должен быть дескриптором, потому что так задумали разработчики.
Во-вторых, в методе _search_elementwebium/find.py at master · wgnet/webium · GitHub даже разбираться не хочется, какая логика там имплементирована, кажется что весьма сложная, лучше бы разработчики снабдили его подробной документацией - как никак ядро поиска. Вот это вот присвоение вообще выносит мозг webium/find.py at master · wgnet/webium · GitHub - подменять ссылку на класс - как минимум нужно подробно комментировать зачем, как максимум не делать никогда.
В-третьих, судя по всему про потокобезопасный запуск тестов говорить не стоит в webium’e: если два параллельных теста из разных потоков будут работать с одним элементом одной страницы, при том что Find создает атрибут класса, а не объекта, скорее всего это приведет к сайд-эффектам. К примеру, насколько я знаю, при конкурентном запуске в webium/find.py at master · wgnet/webium · GitHub точно будет происходить перезапись объектов.
P.S. или н-р код webium/find.py at master · wgnet/webium · GitHub видимо в качестве context’a может выступать только page, но об этом здесь ни слова.
В отличие от webium, POM гарантирует потокобезопасность, поскольку для каждой инстанциированной страницы каждый UI-элемент будет уникальным благодаря коду pom/base.py at master · schipiga/pom · GitHub.
В основном это все, можно еще прицепиться к тому, что в wait перехватывается WebDriverException, что весьма странно и не снабжено комментами. А также к тому, что webium не умеет работать со сложными структурами, типа таблиц, что в моем проекте весьма критично.
В POM’е мне не нравится как пришлось реализовать кэширование: pom/base.py at master · schipiga/pom · GitHub. Но пока это некритично - нужно будет, буду думать как сделать проще или красивее. Кстати по поводу кэширования - его задача не столько обеспечить быстроту, сколько стабильность засчет перезагрузки кэша и повторного выполнения действий в случае, если элемент был перестроен в DOM’e в момент выполнения действий, что привело к исключению. Кэш дает прирост скорости в случаях, если нужно в течение некоторого времени опрашивать один и тот же UI-элемент, который должен измениться в результате асинхронных действий. Насколько прирост - не проверял, эта идея была заимствована из openstack/horizon, уверен без нужды бы его не стали делать.
Также в POM’e мне не нравится, как написан модуль pom/fields.py at master · schipiga/pom · GitHub. Это потому что я заигрался с setter’ом и не учел проблем с наследованием. С будущем setter планируется заменить на обычную функцию.
“Некоторые страницы имеют по несколько десятков полей. IDE не найдёт потом эти аттрибуты у класса.” Это проблема IDE, с т.з. питона все корректно. Н-р насколько я знаю, популярный pycharm до сих пор не может нормально осилить pytest. Не отказываться же теперь от pytest’a из-за этого, другие тестовые фреймворки рядом с ним нервно курят за углом.
В целом я считаю, что код POM написан чище, понятнее, целостнее и проще, чем webium.
Прочитал основную тему и наискосок все обсуждения ниже и так и не понял, почему я работая с изоморфным фреймворком на фронте, адаптивом и сторами js приложения с малым временем жизни, поллингами на появление динамического контента на странице - должен или захочу юзать эту штуку.
Классический implicit_wait нигде не использую. Фактически, есть webdriverwait(driver, timeout).until(condition(element)) который с Catberry, Angular, React работает отлично.Этот метод оборачивается удобными методами get_element() get_elements() (представим, что мы завели некий BaseSeleniumPage пейджобжект, в котором реализовали методы, которые применимы для любой вебстраницы) которые уже в рамках какого-то бизнес метода на определенной пейдже-потомка дергаются.
Храню ли я хэндлы на объекты страницы? Нет. Каждый раз происходит поиск по css или xpath - по мере вызова pageobject метода. А зачем мне хранить их? Зачем заводить кеши? Большую часть времени в тестах занимает серверный рендеринг, а не клиентский (т.е. всякие кейсы когда мы ищем не эл-т на странице, а его измененный атрибут - это мелочи по сравнению с тем, что классическая static html страница перегружается).
Т.е. тесты сейчас работают быстро, правильно с динамическими страницами. Может, дело в упрощении написания кода? Но для меня чем меньше импортов кода (а значит, и потенциально кода, который будет ломаться и за который ты не отвечаешь) - тем лучше.
@Sergei_Chipiga спасибо за код-ревью. Причину написания собственных библиотек ради исправления фатального недостатка других я принимаю, хотя и не разделяю.
Я не уговариваю использовать Webium, просто хотелось бы где-то уточнить те или иные выводы.
должно быть управление только через сущность application
Если хочется, то можно сгрузить все страницы в одно место. Webium в этом никак не ограничивает.
Implicit_wait, который использует webium
Он отключается одной настройкой, если он не нужен.
гарантирует потокобезопасность
Это сомнительная фича для python’a ввиду наличия GIL. Тесты параллелятся на уровне процессов, а не потоков, как в Java.
webium не умеет работать со сложными структурами, типа таблиц, что в моем проекте весьма критично
Webium создавался для работы со сложными структурами. Это прямой fork идей HtmlElements на python.
Это проблема IDE, с т.з. питона все корректно.
Если IDE не распознаёт мой код, то страдаю я, а не IDE. И к сожалению, это моя проблема, а не IDE.
Да GIL есть, и тем не менее параллельность потоков в питоне работает вполне приемлемо для всевозможных операций ввода вывода, в том числе сетевых запросов - это будет реально быстрее чем в один поток. GIL влияет если вы попытаетесь параллелить н-р числовые вычисления, тут профита особого не будет, скорее скорость даже замедлится из-за переключения контекста, насколько я знаю.
Код автотестов скидывать я тут не буду (NDA, все дела), но могу более подробно описать, как все разруливалось. Вопрос в том, что конкретно из откровений ожидаете увидеть: это не Selenide, Это просто чистый селениум и обертки над ним, который решает свою задачу. Сайт для примера: flamp.ru
Про GIL тестам знать незачем. Питон поддерживает многопоточность (плохо или хорошо - другой вопрос), webium написан так, что код не потокобезопасный. От того, что тестовые фреймворки делают параллельность не через многопоточность, а через процессы - это не делает код webium’a лучше или надежнее. К примеру, если я захочу использовать webium как средство автоматизации, для каких-либо своих задач, где будет многопоточность, то работать он не будет правильно. По-моему либа не должен полагаться на то, что не реализовано сейчас в других инструментах ее использующих. Она вообще про них ничего не должна знать. Конкретно для webium’a на мой взгляд правильно писать в документации, что не предназначен для многопоточного выполнения кода.
P.S. Кстати проблемы могут возникнуть даже в однопоточном режиме, когда один писатель изменит дескриптор, потом второй писатель изменит этот же дескриптор, потом первый попытается опять провести действие с этим дескриптором, и жестко обломается. Такое выглядит маловероятным, но чем маловероятнее ситуация, тем сложнее отловить будет баг.
если я захочу использовать webium как средство автоматизации, для каких-либо своих задач, где будет многопоточность, то работать он не будет правильно
Если я захочу использовать молоток в качестве отвёртки, то он будет работать неправильно - более чем согласен. Инструмент делался для тех задач, для которых он был нужен.
один писатель изменит дескриптор, потом второй писатель изменит этот же дескриптор
Не понимаю, о чём речь. Писатель - это человек, пишущий код? В процессе выполнения кода дескрипторы никто не меняет.
По-моему некорректное сравнение. Webium позиционируется как инструмент для автоматизации UI-действий через selenium. Я хочу его использовать для автоматизации рутины с UI, и хочу это выполнить в несколько потоков, в каждом из которых будет запущен браузер и на одной и тоже же странице проведены некоторые действия с UI-элементами, н-р в 2 потока заполняется и отправляется форма, и проч. Webium получается не подходит, несмотря на все свои вкусности. Нужно искать или изобретать что-то другое.
Тут трудно спорить, этот аргумент подходит для любого кода, в том числе любого скрипта который был написан здесь и сейчас. Если инструмент решает задачу здесь и сейчас - то фактически он делает то, что от него требуется.
Писатель - это объект, изменяющий (записывающий новые свойства) в другой объект. Когда в коде запрашивается объект-дескриптор, производится изменение его свойства self.context = obj. В принципе кажется, что в однопоточном режиме 'это присвоение не приводит к потенциальным сайд-эффектам, потому что с self.context вроде никаких апдейтов не делается в других местах. И думаю, что и в многопоточном режиме их не должно быть (опять же кто знает. P.S. все таки кажется, что параллельно не будет надежности, т.к. вызов _search_element может занять время, а в это же время self.context будет перезаписан в другом потоке), но мне кажется, что выглядит это очень неявно за счет return self._target_element и того как и где оно вычисляется.