Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ======================================================================================
- обрати внимание что достаточно inbox() вместо atInboxPage()
- ну это уже такое - как тебе больше нравиться
- >>>>>>>> мне нравится когда покороче, но в случае с страницей логина GIVEN().login().ensureLoginAs....
- как то странно выглядит, и если таких страниц будет много, все равно придется приходить к
- единому стандарту и использовать приставку Page, loginPage, так что я сразу уже так и сделал.
- да есть логика... хотя я думаю это как раз тот случай когда не стоит из-за одной только "логин пейджы" - все другие тоже подтасовывать под ее стиль... Мы же в жизни как говорим: "Там на странице логика в гугле - не забудь сделать то то...", Но при этом "Проверь инбокс не пришло ли от меня письом" ;) В общем, смотри как хочешь. Но я бы инбокс таки укоротил :)
- Вот тебе только еще одна идея на будущее... Дело в том що ensureLoggedinAs (заметь logged vs log) штука которая полезная очень часто, почти в каждом тесте.... и в принципе она "достаточно абстрактна и известна пользователю" что-бы ее "не уточнять до login().ensureLoggedIn()", достаточно просто
- GIVEN().ensureLoggedInAs()
- Не знаю, еще не смотрел в код - создал ты такой дополнительный хелпер в Application или нет - но его удобно иметь, для тех тестов где у тебя именно GIVEN().ensureLoggedInAs
- потому что там где WHEN() - то есть ты явно тестируешь логин а не используешь его как прекондишен, там лучше конечно
- WHEN().atLoginPage().loginAs(...)
- оно в идеале еще и так должно быть:
- GIVEN().ensureLoggedOut()
- WHEN().atLoginPage().loginAs(...)
- ;)
- ======================================================================================
- ты должен четко понимать какому стилю ты следуюешь….
- посмотри теперь доклад мой о KISS Pageobjects
- или презентацию пролистай…
- обрати внимание на те слайды где примеры разного стиля для инженеров разного уровня…
- твоя проверка в THEN - в стиле “для sr automation engineer” - ты наружу из обьекта выставляешь его внутреннюю структуру…
- это ок… но ты должен понимать в чем отличие… что ты как бы слегка “нарушаешь” инкапсуляцию если ты расчитываешь что твоими тестами будут новички также пользоваться/дописывать…
- >>>>>>>> почему для sr autoamtion ?
- все мои методы которые можно вызвать - свои, и все они
- возвращают объекты типа Email, Emails, ComposeEmail, etc... т.е. я сами веб элементы
- никогда не вывожу в тест, если ты об этом :) только методы "поведений", а то что они
- звучат как в селениде
- THEN().atInboxPage()
- .emails()
- .shouldHaveSize(1);
- это скорее случайность :)
- >>>>>>>>> Что же ты такой умный :) не написал мне что то в стиле: "Ээээ, я понял о чем ты, но то у меня просто совпали имена так, на самом деле там везде виджеты возвращаются. То ты просто код еще не смотрел наверное, и поэтому не знаешь :)" ;)
- ======================================================================================
- ну и проверять в стиле shouldHaveSize(1).getFirst().shouldHaveTopic() - это длинно… должен быть способ как с проверками exactTexts в селениде…
- когда ты за один раз проверяешь все… и порядок и сайз и тексты...
- >>>>>>>> предлагаешь писать что то типа
- THEN().atInboxPage()
- .emails()
- .shouldHave(size(1));
- ?
- >>>>>>>> не, предлагаю писать:
- THEN().atInboxPage()
- .emails()
- .shouldHaveTopics(topic);
- // и оно должно проверить что топиков ровно один и ровно с таким текстом в списке... ну точно также как и в селениде работает shouldHave(exactTexts(topic)) если бы emails возвращали ElementsCollection а не виджет
- ну можно скорее всего это как то сделать, но зачем? это же усложнит у того же например
- Email контейнера (который одна строка из списка емейлов) всего пара полей, и писать
- кондишены свои чтоб пишущим тесты нужно было их запоминать... ну хз, на текущем уровне
- тестов это точно не нужно как по мне, а вообще... если думаешь что это действительно
- очень нужно и важно, я попробую написать свои кондишены для "виджетов" типа как ты описал)
- >>>>>>>>> не нужно там никакие кондишены писать, у тебя же виджет возвращаемый emails() должен внутри иметь что то типа elements() который вернет как раз ElementsCollection на которой ты сможешь сделать shouldHave(exactTexts(topics))
- по поводу длинности - я чет лишнюю одну проверку добавил, перечитал задание, там оказывается
- нужно проверить только что письмо всего одно, а я еще зачем то и проверку топика туда втулил)
- убрал, теперь осталось только нужное:
- THEN().atInboxPage()
- .emails()
- .shouldHaveSize(1);
- >>>>>>>>>>> не, все правильно ты сделал... проверка написана более абстрактно... Но это же логично что нужно проверить что не только одно письмо нашлось но и то письмо что нужно нашлось а не какое то другое... там условие задания специально слегка неточное... что бы проверить как раз что автоматизатор "мыслит как тестировщик" :)
- ======================================================================================
- =================================================================================================================
- 1. "" @Step
- public static void editEnter(String fromTaskName, String toTaskName) {
- edit(fromTaskName, toTaskName);
- }
- /*
- я б не стала реализовывать такой метод
- ограничилась бы edit-ом
- не вижу особых плюсов в существовании editEnter-а
- */"
- >>>>>>>>> ну для очевидности, у нас есть метод editTab, editClickOutside, а editEnter-а нет?
- сразу возникает вопрос, что это за edit такой непонятный, без пояснения)
- как по мне если писать уже такие разные методы, то пусть они будут похожи)
- >>>>>>> да, консинтеность и очевидность это важно... Все относительно конечно... если для других на проекте было бы неочевидно что понимается под обычным едитом - тогда да... а если очевидно, то можно и забить:) Я бы забил :) Я ленивый... В большинстве тестов я бы хотел иметь полную лаконичность.. И только для особенных случаев использовал бы "уточненные имена", особенно учитывая то, что реально буде только по одному тесту использующему "особые случаи" - ведь они меньше приоритетом...
- И кстати... если уж речь о очевидности то стоит называть как editByEnter - так правильно и понятно, а editEnter конфузит, ибо звучит как редактируемЕнтер ;)
- /*
- ну, допустим, ок - нужен editEnter, раз есть editTab, editClickOutside )
- тогда зачем edit, который ты вызываешь внутри editEnter-а? )
- */"
- >>>>>>>> я сделал реализованные методы editEnter, editTab, etc.
- простой edit оставил для e2e, чтоб не это проще выглядело в тесте,
- а для FT будут использоватся более точные имена методов, в дополнение
- к имени тестового метода, чтоб прям вообще не было никакой непонятки :)
- >>>>>> оооооо, НЕТ!!!!! Вот это плохо... Это совсем не очевидно... Почему существуют два метода которые делаею одно и то же? И чем это е2е отличается от ft? зачем ему особенное укороченное имя?
- Не, это ты перемудрил явно) я даже не знаю как тебе это обьяснить более вразумительно :) Это такой неявный нюанс - которой отображает твой собственный конвеншен - никто не будет на проекте ему следовать, потому что он "придуманный" а не "натуральный" - все закончится тем, что кто где попало будет юзать edit а кто где попало editByEnter :)
- =================================================================================================================
- 2. " @Test
- public void editConfirmClickOutside() {
- given().active("1")
- .atActiveFilter()
- .prepare();
- Tasks.editClickOuside("1", "edited1");
- Tasks.shouldBe("edited1");
- }
- /*
- ты таки не всегда используешь проверку Tasks.itemsLeftShouldBe(...); - даже если это воможно
- а она ведь - уточняет проверки и улучшает покрытие
- и почти не усложняет фиче-тестов
- почему?
- */"
- >>>>>>>> потому что из за одной этой фичи (которая проверена на разных контекстах в достаточном
- количестве вариантов) могут упасть все фича тесты, если включать её проверку куда только
- можно...
- получается тест вроде как тестирует edit, destroy, add....а упали все сразу, да еще и с
- ошибкой should be items left.
- >>>>>>>> да, мужик, замечательный аргумент :) Правильно мыслишь. Но :) Дело в том, что со стороны разработки, это работает не так... Эта фича - каунтера - она будет размазана по коду... Оно не реализована где то отдельно... Она будет разбита на количество "подфич" - привязанных к действиям соответствующим. И в реальной жизни будет так, что где то для какого то действия = девелопер забудет "учесть подфичу каунтера" - и тест к этому действию упадет - и упадет правильно - указывая для какого именно действия "подфича каунтера" не сработала. А другие тесты будут проходить. Возможно если бы ты знал как работает фронтенд, то ты это учел. И кстати именно поэтому, нужно что бы девы писали аксептенс тесты а не автоматизаторы ;) Но ты конечно молодец что подумал об этом нюансе, даже очень :) Ты первый студент из всех кто об этом задумался, даже пускай и оно привело тебя к неверным решениям :)
- =================================================================================================================
- @RunWith(Categories.class)
- @Suite.SuiteClasses({TasksIntegrationFlowTest.class, TasksOperationsAtAllFilterTest.class})
- @Categories.ExcludeCategory(Buggy.class)
- public class FullAcceptanceSuiteTest {
- }
- слушай!!! у меня тут появилась идея, я сам раньше так никогда не делал, но попробуй, может сработает такой вариант:
- @RunWith(Categories.class)
- @Suite.SuiteClasses({AllSuiteTest.class})
- @Categories.ExcludeCategory(Buggy.class)
- public class FullAcceptanceSuiteTest {
- }
- было бы круто если бы оно работало, а если не работает, то можно даже в джеюнит ишью такое завести что бы ребята добавили :)
- =================================================================================================================
- @Step
- public static ItemsLeft itemsLeft() {
- return new ItemsLeft();
- }
- public static class ItemsLeft {
- private static final SelenideElement element = $("#todo-count>strong");
- public void shouldBe(int itemsCount) {
- element.shouldHave(exactText(String.valueOf(itemsCount)));
- }
- }
- вот тудуемвісі - это пример того приложения где виджеты имхо - это перебор :)
- но если ты просто хотел потренироваться - тогда ок:) пускай будет :)
- но тогда если уж "тренироваться" - то наверное стоило бы вынести отдельный виджет Task :)
- да и еще такой нюанс...
- мы нигде в других классах не используем ItemsLeft и это значит - что да, норм держать его внутри класса TodoMvc
- но с другой стороны у нас "запахло Large Class Code Smell"... и типа как "проще" и более понятней, было разложить нашу кучу большую на маленькие в разных местах, а не разложить кучу на кучки по меньше запхать самые маленькие кучки в мешки и положить в мешок с самой больешой кучей внутрь :)
- Ну такое... смотри сам...
- =================================================================================================================
- О твоем waitUntilAjaxComplete
- да такой хелпер нужен... Но, он не "гибко" реализован...
- ты его построил на основе кондишена, в который захардкодил проверку на то что джеквери догрузился и то что количество активных запросов 0, но это не всегда будет помогать...
- иногда при сильно медленном интернете... Оно все равно не будет работать... потому что джеквери то догрузиться, но джаваскрипт который "добавляет операцию добавления задачи к нью туду элементу" либо "реакцию на клик к клиер комплитед" - не успеет "добавиться"... "
- Поэтому стоит создать кондишен "без захардкодженых" скриптов, но иметь возможность их передавать внутрь.
- писать что то типа:
- new WebDriverWait(...).until(jsReturnedTrue("return typeof($) !== 'undefined' && $.active == 0"))
- а когда нужно писать:
- new WebDriverWait(...).until(jsReturnedTrue("return $._data($('#clear-completed').get(0), 'events').hasOwnProperty('click')"))
- все это конечно можно завернуть потом в один хелпер waitUntilAjaxComplete()
- и кстати, вместо new WebDriverWait(getWebDriver(), ...) можно писать Selenide.Wait()
- ;)
- =================================================================================================================
- вижу ты хромдрайвер держишь в проекте :)
- помню Юля уже "жаловалась на тебя" :)
- Ну ок, делаешь как хочешь. Но знай, что этим ты перечеркиваешь весь тот путь которым идет Селениум :) То есть ты как бы "споришь с авторами селениума" :)
- Значешь почему теперь нет FirefoxDriver ? А есть марионет в виде бинарника ?
- Только по одной причине :)
- Что бы не зависеть от исходных сорсов :)
- Юз кейс такой.
- Вот обновился файерфокс, нужен новый драйвер. И вот инженер на проекте просто берет качает новый марионет бинари и ложит его в папучку прописанную в PATH на CI сервере. И все :) А не лезет в сорсы проекта, апдейдит зависимости селениума, который должен был выпустить апдейт к FirefoxDriver, комитит в гит... и только тогда оно попадет на сервер...
- У тебя слегка другой случай но такой в плане того - что тебе что бы обновить драйвер - нужно играться с сорсами... потому что драйвер привязан к сорсам... Технически - это та же "проблема" - от которой авторы селениума хотели уберечь своих пользователей... А ты и дальше ее сам себе "придумываешь" :)
- У тебя конечно наверное есть какие то свои обоснования почему ты так делаешь :) Ну такое ) Делай конечно как хочешь :) Но знай, что это далеко не "бест пректис" :)
- Хотя я и люблю говорить что "there are no best practises, there good practices in context"
- :)
- =================================================================================================================
- Зачем тебе DefaultFail категория с тестом? :)
- Ты конечно шаришь :) Я знаю для чего нужен этот трюк, но хочется услышать от тебя обьяснение :)
- Я просто не знаю, вы с Юлей говорили об этом уже или нет...
- =================================================================================================================
- public class GoogleGmailTest {
- @BeforeClass
- public static void beforeClassSetup() {
- Configuration.browser = "chrome";
- Configuration.timeout = 5_000;
- }
- @Test
- public void loginSendEmailSearchEmailTest() {
- /* либо нужно обрать слово login с имени теста, (потому что сейчас ты логинишься в прекондишене а не степах тест кейса)
- * либо вместо GIVEN() использовать WHEN() ниже там где у тебя логин
- * я за первый вариант :)
- */
- open("http://gmail.com");
- /*
- * почему у тебя open() не есть частью пейджа? на самом деле урл -
- * это такая же часть реализации страници как и html элементы
- * только один наюнс - "относительный урл" а не абсолютный... вот домен - да, он не является.
- * но для этого существует Configuration.baseUrl ;)
- */
- GIVEN().atLoginPage()
- .ensureLoggedAs("", "");
- String topic = "topic" + System.currentTimeMillis();
- WHEN().atInboxPage()
- .composeEmail()
- .withRecipients("[email protected]")
- .withTopic(topic)
- .withMessage("")
- .send();
- AND().atInboxPage()
- .search(topic);
- THEN().atInboxPage()
- .emails()
- .shouldHaveSize(1);
- /* по поводу этой части уже писал выше) что лучше shouldHaveTopics(topic)
- */
- }
- }
- =================================================================================================================
- мне кстати не совсем нравиться твоя идея называть "пакет куда ты складываешь полезняшки" как api...
- оно просто конфузит слегка... к чему АПИ? :)
- причем иногда ты называешь так часть которая относится к тест модели - здесь апи как раз подходит...
- а иногда часть которая как раз "полезняшки" - типа кастомкондишенов и так дале...
- ну такое... не уверен знаю ли я лучшее имя... мы обычно называем как core полезняшки
- еще есть практика называть common
- а тестовую модель я называю model... но в этом случае и api подойдет...
- =================================================================================================================
- public class Application {
- @Step
- public InboxPageObjects atInboxPage() {
- return new InboxPageObjects();
- // мне не совсем понятно почему InboxPageObjects ? почему не InboxPage или Inbox?
- // ты же вот почему то не назвал ApplicationPageObjects?
- // есть такое понятие как контекст. Если он задан то зачем каждый раз напоминать что я Яша Украинец?
- // У меня и так в паспорте написано что я из украины. Я о том что создай один пакет, назови его pageobjects, или как тебе больше нравиться и потом уже незачем повторять в каждом имени класса это PageObjects. И так понятно что оно такое.
- }
- @Step
- public LoginPageObjects atLoginPage() {
- return new LoginPageObjects();
- }
- }
- =================================================================================================================
- public class LoginPageObjects {
- @Step
- public void ensureLoggedAs(String userEmail, String userPassword) {
- // помни о лаконичности... и так очевидно что речь о юзере... зачем это слово повторять в именах параметров?
- enterEmail(userEmail);
- // я бы не стал выносить это в отдельный метод до тех пор пока мне реально не понадобилось бы только ввести имейл, без пароля... если ты "наперед пишешь фреймворк для других" - тогда да, стоит. А так то я стараюсь следовать стратегии premature optimisation is evil. ну тоже... все относительно... смотри сам...
- enterPassword(userPassword);
- $(".cmsg").waitUntil(hidden, 15_000); // во первых плохо хардкодить вейты... лучше напиши Configuration.timeout * 3, что бы ты мог их конфигурировать где нужно...
- // во вторых - зачем этот вейт здесь? степы не должны проверять результат своих действий... сделал дело гуляй смело... следующий степ должен сам уметь подождать чего там ему нужно... ты вот, мне кажется, вообще начал неблагодарное дело... "точечно вейты раставлять", лучше поставть глобальный таймаут равный 15000 и тогда все и так будет работать. когда ты захочешь кликнуть по кнопке компоуз - оно и так подождет 15 секунд пока она появится... Или ты так пробовал и жмейл у тебя все равно фокусы выкидывал?
- Точечно имеет смысл раставлять вейты когда - таких тормознутых мест мало... а жмейл этот один весь сплошной тормоз :) я бы нем лучше глобальный таймаут поставил...
- }
- =================================================================================================================
- public class ComposeEmail {
- з точки зрения английского не правильно
- звучит как команда, а должно быть "noun"
- правильно как то типа NewEmailWindow или что то такое... можно укоротить и до NewEmail
- =================================================================================================================
- public class Emails {
- private final ElementsCollection mails;
- private Emails(ElementsCollection visibleMails) {
- this.mails = visibleMails;
- }
- public static Emails all() {
- return new Emails(
- $$(".Cp tr") // вот этот локатор дублируется, возможно стоит его таки "заDRYить" вынеся его в стическую переменную
- =================================================================================================================
- .filterBy(visible)
- .filterBy(cssClass("zE"))
- возможно при большем количестве мейлов это будет медленно в селениде, и быстрее было бы так:
- filterBy(and("visible and unreaded", visible, cssClass("zE")))
- потому что так оно будет "один раз в цикле пробегаться по всем", а в твоем случае будет два раза пробегаться...
- но точно я не уверен, это нужно тестировать... можешь как раз проверить и сообщить мне :)
- я тогда документацию селениде даже проапдейтю на этот счет :)
- так что сделаешь благое дело для комьюнити :)
- =================================================================================================================
- @Step
- public Email get(int emailNumber) { // c cигнатуры и так понятно что речь идет о Email, зачем в имени параметра дублировать это слово? вот здесь: public Emails shouldHaveSize(int size) { же ты не написал emailsSize ;)
- mails.shouldHaveSize(emailNumber);
- не уверен что это хорошая идея - начинать индексы с единици а не с нуля...
- уж проще научить новичков что в программирования индексы начинаются с нуля, чем потом конфузить более продвинутых инженеров...
- return new Email(mails.get(--emailNumber));
- не делай так... не используй вот эти задротские декременты... они сильно "замудренные", они только конфузят, разве не проще банально emailNumber - 1 ? для тех же новичков - будет проще
- и снова... проще научить новичков что индексы в программировании начинаются с нуля...
- для особо "тупых" (взял в скобки потому что на самом деле это довольно удобно иметь такой метод, он - читабельный) можешь создать еще метод first() ;)
- =================================================================================================================
Advertisement
Add Comment
Please, Sign In to add comment