1 of 76

Тройничок

Selenide�для Web, Android и iOS

Andrei Solntsev

2 of 76

  1. Selenide

2. для web

3. для Android

4. для iOS

5. И всё вместе!

План

3 of 76

Selenide

бесплатный

опен-сорс

лаконичный API

стабильные тесты

@jselenide

selenide.org

4 of 76

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 of 76

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 of 76

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 of 76

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 of 76

UI Test

@Test

public void userCanLogin() {

open("http://localhost:8080/login");

$(By.name("user.name")).setValue("john");

$("#submit").click();

$(".menu").shouldHave(text("Hello, John!"));

}

9 of 76

Selenide

против ОЖИДАНИЙ

10 of 76

Решения для проблем с Ajax:

бедно

многословно

идеально!

1. Implicit wait

3. $("h1").shouldBe(visible);

2. new WebDriverWait(driver, 1000).until(� visibilityOfElementLocatedBy(By.tagName(“h1”)));

11 of 76

Selenide

фишечки

12 of 76

Умные ожидания

Забудь про ожидания!

$(".loading_progress").shouldBe(visible);

$("#menu").shouldHave(text("Привіт, Петро!"));

$(By.name("gender")).shouldNotBe(selected);�

$(By.name("gender")).should(disappear);

13 of 76

Умные ожидания

$(".loading_progress").shouldBe(visible);

$.should*() методы �ждут�до 4 секунд

14 of 76

Умные ожидания

$(".loading_progress").shouldBe(visible);

Таймаут настраивается

mvn -Dselenide.timeout=8000

15 of 76

Сообщения об ошибках

$(“.alert”).shouldNotHave(

text(“Помилка”));

Element should not have text Помилка' {.alert}�Element: '<div class=info alert>Помилка</div>'�Timeout: 4 s.

16 of 76

screenshot + html

Element should be hidden {#gameWin}

Element: '<img class="gameOver" id="gameWin" src="img/thumbs-up.jpeg"></img>'

Screenshot: file:/.../hangman/build/reports/tests/1510751914648.0.png

Page source: file:/.../hangman/build/reports/tests/1510751914648.0.html

Timeout: 4 s.

Debug with pleasure!

17 of 76

Коллекции

$$(“.error”).shouldHave(size(3));

$$ возвращает список элементов:

$ возвращает первый элемент

18 of 76

Коллекции

$$(“#employees tbody tr”).shouldHave(

texts(

“John Belushi”,

“Bruce Willis”,

“John Malkovich”

)�);

19 of 76

Коллекции: фильтрация

$$(“#employees tbody tr”)

.filter(visible)

.shouldHave(size(4));

20 of 76

Навигация

21 of 76

Поиск элементов

$("#loginBtn")

= $(By.id("loginBtn"))

= $(By.className("active"))

= $(By.xpath("//div//h1"))

= $("[name=username]")

$x("//div//h1")

$(By.name("username"))

$(".active")

22 of 76

Поиск по атрибутам

import static com.codeborne.selenide.Selectors.*;

$(by("type", "checkbox"))

$(by("readonly", "readonly"))

$(byTitle("Login form"))

$(byValue("Alert button"))

$(by(“data-test-id”, "alert-button"))

23 of 76

Поиск по атрибутам

$(by(“data-test-id”, "alert-button"))

<button id=”some-id-for-devs”

data-design-id=”some_id_for_css”

data-test-id=”someIdForTests”>

Alert button

</button>

24 of 76

Поиск по тексту

import static com.codeborne.selenide.Selectors.*;

$(byText("Hello, Київ!"))� .shouldBe(visible);

$(withText("ello"))� .shouldHave(text("Hello, Київ!"));

25 of 76

Найди родителей

  • $.parent()
  • $.closest(“table”)

26 of 76

Найди предков и потомков

  • $.parent()
  • $.closest(“table”)

$(“table td[data-foo=’bar’]”)

.closest(“table”)

.find(“tr”, 4)

.find(byText(“THE CELL”)

.click();

27 of 76

28 of 76

Кастомные проверки

$(“h1”).shouldHave(

);

css(“font-size”, “16px”)

public static Condition css(String name, String value) {

return new Condition(“css”) {

public boolean apply(Driver driver, WebElement el) {

return value.equals(el.getCssValue(name));

}

}

}

29 of 76

Кастомные команды

// A helper method

void doubleClick(WebElement element, long delay) {

element.click();

sleep(delay);

element.click();

}

@Test {

doubleClick($(“#slider”), 100);

}

30 of 76

Но было бы клёвее так:

@Test {

$(“#slider”).doubleClick(100);

}

@Test {

doubleClick($(“#slider”), 100);

}

31 of 76

Или ещё клёвее:

@Test {

$(“#slider”)

.doubleClick(100)

.tripleClick(15)

.quadroClick(50)

}

Так можно в Kotlin и Groovy

Но не в Java

32 of 76

Но в Java можно почти так же:

@Test {

$(“#slider”)

.execute(doubleClick(100))

.execute(tripleClick(15))

.execute(quadroClick(50))

}

Command<SelenideElement> doubleClick(long delay) {

return (proxy, locator, args) -> {

proxy.click(); … proxy.click();

return proxy;

};

}

См. https://ru.selenide.org/2019/09/02/selenide-5.3.0/

33 of 76

Браузеры

34 of 76

Selenide и браузеры

java -Dselenide.browser=chrome

java -Dselenide.browser=firefox

java -Dselenide.browser=ie

java -Dselenide.browser=cy.test.MyWebdriver

...

Вариант 1: system property

35 of 76

build.gradle:

task chrome(type: Test) {

systemProperties['selenide.browser'] = 'chrome'

}

task firefox(type: Test) {

systemProperties['selenide.browser'] = 'firefox'

}

task ie(type: Test) {

systemProperties['selenide.browser'] = 'ie'

}

36 of 76

Jenkins

Job 1: ./gradlew chrome

Job 2: ./gradlew firefox

Job 3: ./gradlew ie

  • Хочешь - параллельно,
  • Хочешь - последовательно

37 of 76

Selenide и браузеры

@Before

public void setUp() {

Configuration.browser = “chrome”;

// or Configuration.browser = “ie”;

// or Configuration.browser = “cy.MyWebdriver”;

open(“https://google.com”);

}

Вариант 2: прямо в коде

38 of 76

Что если нужен

кастомный вебдрайвер?

39 of 76

WebdriverProvider

@Before

public void setUp() {

Configuration.browser = MyWDProvider.class.getName();

open(“https://google.com”);

}

40 of 76

WebdriverProvider

static class MyWDProvider implements WebDriverProvider {

@Override

public WebDriver createDriver(DesiredCapabilities сapabilities) {

return new ChromeDriver(capabilities);

}

}

41 of 76

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);

}

}

42 of 76

Об’єкт сторінки

43 of 76

Selenide пэдж обжект

public class GooglePage {

public void search(String query) {

$(By.name(“q”))

.val(queue)

.pressEnter();

}

}

  • Не нужны фактори
  • Не нужны аннотации
  • Просто пиши код!

44 of 76

Внести поле “By

public class GooglePage {

private By query = By.name(“q”);

public void search(String query) {

$(query)

.val(queue)

.pressEnter();

}

}

@Test {

var page = new GooglePage();

}

45 of 76

Вынести поле “SelenideElement

public class GooglePage {

private SelenideElement query = $(By.name(“q”));

public void search(String query) {

query

.val(queue)

.pressEnter();

}

}

46 of 76

Вынести поле @FindBy

public class GooglePage {

@FindBy(name = “q”)

private SelenideElement query;

public void search(String query) {

query

.val(queue)

.pressEnter();

}

}

@Test {

var page = page(GooglePage.class);

}

47 of 76

Selenide

для

мобильников

48 of 76

Эмуляция мобильного браузера

task chrome(type: Test) {

systemProperties['selenide.browser'] = 'chrome'

}

task chrome-mobile(type: Test) {

systemProperties['selenide.browser'] = 'chrome'

systemProperties['chromeoptions.mobileEmulation'] = 'Nexus 5'

}

49 of 76

Тесты для мобильников

Удалённый

  • Appium server
  • Appium Driver
  • Test code отдельно от мобилки

Нативный

  • Espresso
  • XCTest / XCUITest

Тест на Kotlin/Swift

Тест на любом ЯП

50 of 76

Тесты для мобильников

Удалённый

  • Appium server
  • Appium Driver
  • Test code отдельно от мобилки

Нативный

  • Espresso
  • XCTest / XCUITest

Тест на Kotlin/Swift

Тест на любом ЯП

Мы сейчас здесь

51 of 76

Selenide для мобильников (Appium)

  1. Простой:���
  2. Продвинутый:

https://github.com/selenide-examples� /selenide-appium

Без аннотаций

С аннотациями

https://github.com/selenide� /selenide-appium

52 of 76

Selenide для мобильников

Простой вариант:

без аннотаций

53 of 76

@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

54 of 76

Test setup

public class CalculatorTest {

@Before

public void setUp() {

Configuration.startMaximized = false;

Configuration.browserSize = null;

Configuration.browser = MyAndroidDriver.class.getName();

open();

}

}

55 of 76

Webdriver setup: Android

public class MyAndroidDriver implements WebDriverProvider {

@Override

public WebDriver createDriver(DesiredCapabilities caps) {

caps.setCapability(“version”, "4.4.2");

caps.setCapability("automationName", "Appium");

caps.setCapability("platformName", "Android");

caps.setCapability("deviceName", "0123456789ABCDEF");

caps.setCapability(“appPackage”, "com.android.calculator2");

caps.setCapability(“appActivity”, "com.android.calculator2.Calculator");

return new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"), caps);

}

}

56 of 76

Webdriver setup: iOS

public class MyIOSDriver implements WebDriverProvider {

@Override

public WebDriver createDriver(DesiredCapabilities caps) {

caps.setCapability("platformName", "iOS");

caps.setCapability("deviceName", "iPhone 11");

caps.setCapability("platformVersion", "13.0");

caps.setCapability("app", resourcePath("apks/Wikipedia.app"));

caps.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, "16");

capabilities.setCapability("autoAcceptAlerts", true);

return new IOSDriver("http://127.0.0.1:4723/wd/hub", caps);

}

}

57 of 76

Было бы круто �переиспользовать

тестовый код

для Web, Android, iOS!

58 of 76

Selenide для мобильников

Продвинутый:

с аннотациями

59 of 76

Пэдж обжект и Appium

class MobileCalculatorPage {

@FindBy(id = "op_add")

@AndroidFindBy(id = "op-add")

@iOSFindBy(id = “operandAdd”);

private SelenideElement plus;

}

С аннотациями:

https://github.com/selenide� /selenide-appium

60 of 76

Пэдж обжект и Appium

class CalculatorPage {

@AndroidFindBy(id = "op_add")

@iOSFindBy(id = "op_add")

SelenideElement plus;

@AndroidFindBy(id = "eq")

SelenideElement equal;

@AndroidFindBy(id = "digit_2")

WebElement number2;

@AndroidFindBy(id = "result")

SelenideElement result;

}

61 of 76

Тест с пэдж обжектом

@Test

public void pageObject() {

CalculatorPage page = screen(CalculatorPage.class);

page.number2.click();

page.plus.click();

page.number4.click();

page.equal.click();

page.result.shouldHave(text("6"));

}

62 of 76

Wikipedia-Selenide

Пример тестов на Selenide+Appium:

github.com/wikipedia-qa/wikipedia-selenide

63 of 76

Плюсы

64 of 76

Плюсы

  • Пиши тесты на любимом ЯП
    • Не надо учить Swift/Kotlin/…
  • Переиспользуй код
    • между Web, Android, iOS
    • (но есть нюансы)
  • Запускай эмулятор в Docker / облаке
  • Тестируй гибридные приложения
    • Переключайся в ChromeDriver и нативку

65 of 76

Проблемы

66 of 76

Appium - это JavaScript

(лично меня) БЕСИТ

  • NPM
  • node_modules
  • В любой момент билд может сломаться
    • потому что какой-нибудь чёртов пакет не найден
    • или не собирается под Mac

67 of 76

Appium сырой

  • Медленный
  • Глотает ошибки
  • Не реализует функции
  • AppiumDriver implements JavascriptExecutor” нифига не значит

68 of 76

Appium для Selenium

-

как геометрия Лобачевского для геометрии евклидовой:

всё круглое и через жопу.

69 of 76

Стандартные методы не работают

  • $.getTagName()
  • executeJavascript()
  • driver.manage().window().setSize(...)

70 of 76

Вызываю window().setSize(...)

org.openqa.selenium.WebDriverException: An unknown server-side error occurred while processing the command. Original error: Could not proxy. Proxy error: Could not proxy command to remote server. Original error: 404 - undefined

71 of 76

Смотрим в логи аппиума:

[W3C (04cceb89)] Driver proxy active, passing request on via HTTP proxy

[debug] [WD Proxy] Matched '/wd/hub/session/04cceb89-3c9b-46d9-b5c4-eea0a61693c4/window/rect' to command name 'setWindowRect'

[debug] [WD Proxy] Proxying [POST /wd/hub/session/04cceb89-3c9b-46d9-b5c4-eea0a61693c4/window/rect] to [POST http://localhost:8201/wd/hub/session/b4cc73f9-bf84-45fa-b2dd-7301e8255d14/window/rect] with body: {"width":1366,"height":768}

[WD Proxy] 404 - undefined

[debug] [WD Proxy] StatusCodeError: 404 - undefined

[debug] [WD Proxy] at new StatusCodeError (/usr/local/lib/node_modules/appium/node_modules/request-promise-core/lib/errors.js:32:15)

[debug] [W3C (04cceb89)] Encountered internal error running command: Error: Could not proxy. Proxy error: Could not proxy command to remote server. Original error: 404 - undefined

72 of 76

Что дальше?

73 of 76

Тесты для мобильников

Удалённый

  • Appium server
  • Appium Driver
  • Test code отдельно от мобилки

Нативный

  • Espresso
  • XCTest / XCUITest

Selenide для Espresso?

74 of 76

Andrei Solntsev

selenide.org

Дуже дякую!

More videos

asolntsev.github.io/ru/video/

twitter.com/asolntsev

75 of 76

Статистика скачиваний

SeleniumCamp 2015

SeleniumCamp 2020

76 of 76

Thanks for the photos:

  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/