Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

QA Fest 2019. Андрей Солнцев. Selenide для профи

23 views

Published on

Если вы устали от примитивных "Hello World" примеров и хотите знать больше про кишки Selenide и подводные камни, приходите на этот доклад. Обсудим параллельный запуск, рулы и листенеры, трюки с помощью JavaScript и прокси и всё такое.
А ещё лучше, если вы сами предложите свою тему. Повлияй на доклад!

Published in: Education
  • Be the first to comment

  • Be the first to like this

QA Fest 2019. Андрей Солнцев. Selenide для профи

  1. 1. Selenide для профи Andrei Solntsev twitter.com/asolntsev
  2. 2. 1. Алгоритм Селенида 2. Пэджъ объжекты 3. Свой вебдрайвер 4. Несколько драйверов 5. Прокси 6. Трюки с JavaScript 7. Самопальные команды План
  3. 3. UI Test @Test public void userCanLogin() { open("http://localhost:8080/login"); $(By.name("user.name")).setValue("john"); $("#submit").click(); $(".menu").shouldHave(text("Hello, John!")); }
  4. 4. UI Test @Test public void userCanLogin() { open("http://localhost:8080/login"); $(By.name("user.name")).setValue("john"); $("#submit").click(); $(".menu").shouldHave(text("Hello, John!")); }
  5. 5. UI Test @Test public void userCanLogin() { open("http://localhost:8080/login"); $(By.name("user.name")).setValue("john"); $("#submit").click(); $(".menu").shouldHave(text("Hello, John!")); }
  6. 6. UI Test @Test public void userCanLogin() { open("http://localhost:8080/login"); $(By.name("user.name")).setValue("john"); $("#submit").click(); $(".menu").shouldHave(text("Hello, John!")); }
  7. 7. UI Test @Test public void userCanLogin() { open("http://localhost:8080/login"); $(By.name("user.name")).setValue("john"); $("#submit").click(); $(".menu").shouldHave(text("Hello, John!")); }
  8. 8. Главный секрет: Selenide очень простой
  9. 9. Каждый из вас может это сделать. НО незачем!
  10. 10. Каждый может построить такую дорогу через болото НО нафига?
  11. 11. Самый Главный Алгоритм void click() { }
  12. 12. Самый Главный Алгоритм void click() { do { } while (прошло меньше N секунд); }
  13. 13. Самый Главный Алгоритм void click() { do { try { } catch (Exception e) { } } while (прошло меньше N секунд); }
  14. 14. Самый Главный Алгоритм void click() { do { try { webdriver.findElement().click(); } catch (Exception e) { } } while (прошло меньше N секунд); }
  15. 15. Самый Главный Алгоритм void click() { do { try { webdriver.findElement().click(); } catch (Exception e) { sleep(100 мс) } } while (прошло меньше N секунд); }
  16. 16. Дьявол в деталях void click() { do { try { webdriver.findElement().click(); } catch (Exception e) { sleep(100 мс) } } while (прошло меньше N секунд); } Какие ошибки ловить? ● Некоторые требуют мгновенного падения
  17. 17. Дьявол в деталях void click() { do { try { webdriver.findElement().click(); } catch (Exception e) { sleep(100 мс) } } while (прошло меньше N секунд); } А сколько тут ждать? ● Для коллекций надо ждать больше?
  18. 18. Дьявол в деталях void click() { do { try { webdriver.findElement().click(); } catch (Exception e) { sleep(100 мс) } } while (прошло меньше N секунд); return ???; } Результат ок или не ок? ● Например, $.shouldNot(exist);
  19. 19. Дьявол в деталях void click() { do { try { webdriver.findElement().click(); } catch (Exception e) { sleep(100 мс) } } while (прошло меньше N секунд); return …; } В какой момент делать скриншот?
  20. 20. Пэдж обжекты
  21. 21. Selenide пэдж обжект public class GooglePage { public void search(String query) { $(By.name(“q”)) .val(queue) .pressEnter(); } } ● Фактори не нужны. ● Аннотации не нужны. ● Просто программируй!
  22. 22. Можно с полем By public class GooglePage { private By query = By.name(“q”); public void search(String query) { $(query) .val(queue) .pressEnter(); } } @Test { var page = new GooglePage(); }
  23. 23. Можно с полем SelenideElement public class GooglePage { private SelenideElement query = $(By.name(“q”)); public void search(String query) { query .val(queue) .pressEnter(); } } @Test { var page = page(GooglePage.class); }
  24. 24. Селенидовский контейнер public class ТипаHtmlElementsPage { SelenideElement container = $(“....”); SelenideElement username = container.$(“...”); SelenideElement password = container.$(“...”); public void login(String un, String pwd) { username.val(un); password.val(pwd); } И контейнеры не нужны!
  25. 25. Есть и контейнеры class ТипаHtmlElementsPage { @FindBy(id = "status") StatusBlock status; } class StatusBlock extends ElementsContainer { @FindBy(className = "username") SelenideElement username; @FindBy(className = "username") SelenideElement username; } Повторюсь: Я не уверен, что это нужно.
  26. 26. Нав игац ия по DO Mу
  27. 27. Поиск родителей ● $.parent() ● $.closest(“table”)
  28. 28. Поиск родителей и детей ● $.parent() ● $.closest(“table”) $(“table td[data-foo=’bar’]”) .closest(“table”) .find(“tr”, 4) .find(byText(“КЛЕТКО”) .click();
  29. 29. Свой вебдрайвер
  30. 30. Есть два способа: 1. setWebDriver 2. WebdriverProvider
  31. 31. Есть два способа: setWebDriver WebdriverProvider Не рекомендую! Рекомендую ● Сам думай, когда закрыть ● Заботишься только о том, КАК открыть браузер
  32. 32. setWebDriver @Before public void setUp() { this.browser = new ChromeDriver(........); WebDriverRunner.setWebDriver(browser); } @After public void tearDown() { WebDriverRunner.closeWebDriver(); browser.quit();
  33. 33. WebdriverProvider @Before public void setUp() { Configuration.browser = MyWDProvider.class.getName(); open(“https://google.com”); } @After public void tearDown() { // ничего не нужно }
  34. 34. WebdriverProvider static class MyWDProvider implements WebDriverProvider { @Override public WebDriver createDriver(DesiredCapabilities сapabilities) { return new ChromeDriver(desiredCapabilities); } }
  35. 35. WebdriverProvider static class MyWDProvider implements WebDriverProvider { @Override public WebDriver createDriver(DesiredCapabilities сapabilities) { ChromeOptions options = new ChromeOptions(); options.setHeadless(true); options.addArguments("--proxy-bypass-list=<-loopback>"); options.merge(desiredCapabilities); return new ChromeDriver(options); } }
  36. 36. Два браузера
  37. 37. Два браузера Selenide хранит один браузер на поток. Для параллельных тестов.
  38. 38. Два браузера Поток 1: open(“/user”); $(“a”).should(appear); Поток 2: open(“/admin”); $(“#active-users”).should(...);
  39. 39. А вот это низя: Поток 1: Configuration.timeout = 200; open(“/user”); $(“a”).should(appear); Поток 2: Configuration.timeout = 9999999; open(“/admin”); $(“#active-users”).should(...);
  40. 40. @Test public void userLogin() { open(“http://some.site.com”); $(“#username”).val(“petja”).pressEnter(); $(“#name”).shouldHave(text(“Hello, Petja!”); } И так низя:
  41. 41. @Test public void userLogin() { open(“http://some.site.com”); $(“#username”).val(“petja”).pressEnter(); $(“#name”).shouldHave(text(“Hello, Petja!”); open(“http://some.site.com/admin”); $(“.block[data-username=’petja’]”).click(); refresh(); $(“#name”).shouldHave(text(“Goodbye, Petja!”); } И так низя:
  42. 42. @Test public void userLogin() { var b1 = new SelenideDriver(); b1.open(“http://some.site.com”); b1.$(“#username”).val(“petja”).pressEnter(); b1.$(“#name”).shouldHave(text(“Hello, Petja!”); var b2 = new SelenideDriver(); b2.open(“http://some.site.com/admin”); b2.$(“.block[data-username=’petja’]”).click(); b1.$(“#name”).shouldHave(text(“Goodbye, Petja!”); Selenide 5.0
  43. 43. var b1 = new SelenideDriver( new SelenideConfig() .timeout(6000) .browser("chrome") .headless(true) ); var b2 = new SelenideDriver( new SelenideConfig() .timeout(90_000) .browser("firefox") .proxyEnabled(true) ); Selenide 5.0
  44. 44. Как будто бы всё хорошо?
  45. 45. @Test public void userLogin() { var b1 = open(“http://some.site.com”); b1.$(“#username”).val(“petja”).pressEnter(); b1.$(“#name”).shouldHave(text(“Hello, Petja!”); var b2 = open(“http://some.site.com/admin”); $(“.block[data-username=’petja’]”).click(); b1.refresh(); b1.$(“#name”).shouldHave(text(“Goodbye, Petja!”); } Проблемка!
  46. 46. Прокси
  47. 47. $.download() @Test public void userCanViewTerminalDetailsAsPDF() throws FileNotFoundException { … File detailsAsPdf = $("#print-pdf").download(); }
  48. 48. $.download() @Test public void userCanViewTerminalDetailsAsPDF() throws FileNotFoundException { File detailsAsPdf = $("#print-pdf").download(); PDF pdf = new PDF(pdfFile); assertThat(pdf, containsText("PP028004")); assertThat(pdf, containsText("Торговый терминал")); assertThat(pdf, containsTextCaseInsensitive("VeriFone VX 810")); } Между прочим, github.com/codeborne/pdf-test github.com/codeborne/xls-test
  49. 49. $.download() Есть два варианта: Configuration.fileDownload = HTTPGET; Configuration.fileDownload = PROXY;
  50. 50. Для $.download... Selenide запускает свой прокси-сервер. Он умеет: ● Скачивать файлы ● Подменять заголовки ● Инжектить JS код на страницу ● И т.д. и т.п.
  51. 51. Пример прокси: @Before public void setUp() { Configuration.proxyEnabled = true; open("https://google.com"); }
  52. 52. @Test void canAddInterceptorsToProxyServer() { SelenideProxyServer proxy = getSelenideProxy(); proxy.addRequestFilter("proxy-usages.request", (request, contents, messageInfo) -> { String url = messageInfo.getUrl(); log(url + "nn" + contents.getTextContents()); return null; }); А тут вообще чума! Можно подменить запрос!
  53. 53. @Test void canAddInterceptorsToProxyServer() { SelenideProxyServer proxy = getSelenideProxy(); proxy.addRequestFilter("proxy-usages.request", ...); proxy.addResponseFilter("proxy-usages.response", (response, contents, messageInfo) -> { String url = messageInfo.getUrl(); log(url + "nn" + contents.getTextContents()); } });
  54. 54. Параллелизация
  55. 55. Люди запускают тесты в N потоков Проблемы: ● Статические переменные ● Одновременный доступ к данным ● синхронизация ● И т.д. ● - ОШИБКИ! Параллелизация
  56. 56. Запускать тесты в N процессов pom.xml: <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <forkCount>3</forkCount> <reuseForks>true</reuseForks> </configuration> </plugin> Гораздо проще -
  57. 57. Запускать тесты в N процессов buld.gradle: test { maxParallelForks = 5 } Гораздо проще -
  58. 58. Трюки с JavaScript эффективность фседозволенность Э - Ф -
  59. 59. Выбрать дату void setDateByName(String name, String date) { executeJavaScript( String.format("$('[name="%s"]').val('%s')", name, date) ); } @Test { setDateByName("recurrent.startDate", "16.01.2009"); }
  60. 60. Выбрать опцию в bootstrap select void selectBS(WebElement select, String value) { executeJavaScript( "$(arguments[0]).val(arguments[1]).trigger('change')", select, value); } @Test { selectBS($(By.name("operationCode")), "11100"); }
  61. 61. Слайдер void setMaxYearlyFee(int value) { executeJavaScript( "$('#sld').data('slider').value[0] = arguments[0];" + "$('#sld').triggerHandler('slide');" ); } @Test { setMaxYearlyFee(100); }
  62. 62. Заигнорить чёртов confirm public static void mockConfirm() { executeJavaScript( "window.confirm = function() {return true;};" ); } Чак Норрис не отвечает “да” в confirm. Это confirm отвечает “да”.
  63. 63. Контакты в мобильном private void mockCordovaContactsAPI(String number) { executeJavaScript( "window.plugins = {" + " contactNumberPicker: { " + " pick: function(callback) {" + " callback({" + " phoneNumber:"" + number + """ " });}}}"); } @Test { mockCordovaContactsAPI("+79110080075"); }
  64. 64. Кол-во видимых элементов @Test { $$(".offer:visible").shouldHave(size(3)); } @Test { $$(".offer").filter(visible) .shouldHave(size(3)); } Selenium не умеет Это м.б. медленно :(
  65. 65. Кол-во видимых элементов int sizeOf(String cssSelector) { Number count = executeJavaScript( "return $(arguments[0]).length", cssSelector); return count.intValue(); } @Test { assertEquals(1, sizeOf(".offer:visible")); }
  66. 66. Из списка элементов исключить другой список List<String> страныИзЭксельки = ...; List<String> страныСоСтраницы = $$(“td:nth-child(3)”) .excludeWith(text(“text”)) .stream() .map(el -> el.getText()) .collect(toList()); Это м.б. медленно :(
  67. 67. Из списка элементов исключить другой список List<String> страныИзЭксельки = ...; List<String> страныСоСтраницы = executeJavascript( "return Array.from(" + " document.querySelectorAll('td:nth-child(3)')" + ").map(x => x.textContent)"); А вот это быстро
  68. 68. fast set value
  69. 69. Проблема: 1. $.sendKeys() медленный 2. Иногда $.sendKeys() не работает: a. Элемент невидимый b. Маска на поле c. Какой-нибудь хитрый JS d. Какой-нибудь компонент типа DatePicker На помощь приходит JavaScript!
  70. 70. Режим “fast set value” mvn -Dselenide.fastSetValue=true @Before public void setUp() { Configuration.fastSetValue = true; } или
  71. 71. Режим “fast set value” И тогда $.setValue() использует JavaScript который гораздо быстрее!
  72. 72. Ты сам можешь выбирать: $.setValue(“john”) - быстро $.sendKeys(“john”) - если быстро никак ● Автозаполнение ● Автоформатирование ● Хитрые JS обработчики ● JS почему-то не работает ● ...
  73. 73. Самопальные команды
  74. 74. Слайдер void setMaxYearlyFee(WebElement slider, int value) { executeJavaScript(...); } @Test { setMaxYearlyFee($(“#slider”), 100); }
  75. 75. А хочется вот так: @Test { $(“#slider”).setMaxYearlyFee(100); } @Test { setMaxYearlyFee($(“#slider”), 100); }
  76. 76. А ещё круче вот так: @Test { $(“#slider”) .setMaxYearlyFee(100) .setMinYearlyFee(15) .selectFee(50) } Так можно в Kotlin и Groovy Но не в Java
  77. 77. Но можно так: @Test { $(“#slider”) .execute(setMaxYearlyFee(100)) .execute(setMinYearlyFee(15)) .execute(selectFee(50)) } Command<SelenideElement> setMaxYearlyFee(int fee) { return (proxy, locator, args) -> { executeJavaScript(".... " + fee); return proxy; }; } См. https://ru.selenide.org/2019/09/02/selenide-5.3.0/
  78. 78. Selenide и мобильники
  79. 79. Selenide для мобильников (Appium) @Test public void mobileCalculator() { $(By.name("2")).click(); $(By.name("+")).click(); $(By.name("4")).click(); $(By.name("=")).click(); $(By.className("android.widget.EditText")) .shouldHave(text("6")); } Можно без аннотаций: https://github.com/selenide-examples /selenide-appium
  80. 80. Пэдж обжекты и Appium class MobileCalculatorPage { @AndroidFindBy(id = "op_add") @iOSFindBy(id = “op_add”); SelenideElement plus; } А можно и с аннотациями: https://github.com/selenide /selenide-appium
  81. 81. Люди! RTFT! Read the fucking TESTS! github.com / selenide / src / test / java / integration
  82. 82. Вы можете влиять на этот процесс
  83. 83. Андрей Солнцев @asolntsev ru.selenide.org
  84. 84. Спасибо за фотки: 1. https://stories.genvagula.com/my-magical-estonia-500aafd5b2c0 2. https://www.facebook.com/stan.vasilyev 3. https://i-love-tallinn.livejournal.com/306474.html 4. https://www.facebook.com/lyosha.razin 5. https://www.facebook.com/ttrk19/

×