Подскажите пожалуйста, сталкивался кто-то с подобной связкой? В Appium немного сложно работать с элементами и ожиданиями, решил попробовать, может с Selenide будет проще. Реально ли это вообще? К примеру, хочу открыть приложение на главном скрине по имени (пример: SomeApps, допустим приложение надо открыть на скрине не через активити, а просто тапнуть по иконке с таким именем), а оно мне фаерфокс открывает
Скорее всего надо какуе-то обертку над драйвером сделать… Немного моего кода:
DriverSetup
package autoTests.apps.components;
import autoTests.apps.properties.PropertyLoader;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.remote.MobileCapabilityType;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
public class DriverSetup {
private static final Logger logger = LogManager.getLogger(DriverSetup.class);
private static DriverSetup _instance = null;
private AndroidDriver driver;
private WebDriverWait wait;
private URL serverUrl;
public int EXPLICIT_WAIT_TIME;
public int DEFAULT_WAIT_TIME;
public int IMPLICIT_WAIT_TIME;
public String WAIT_ACTIVITY;
public String APPIUM_PORT;
public String NEW_COMMAND_TIMEOUT;
public String DEVICE_READY_TIMEOUT;
public String DEVICE_NAME;
public String BROWSER_NAME;
public String PLATFORM_VERSION;
public String PLATFORM_NAME;
public String APP_PKG;
public String APP_ACTIVITY;
public String AUTOMATION_INSTRUMENTATION;
public String APPLICATION_NAME;
public String PERFORMANCE_LOGGING;
private DesiredCapabilities capabilities = new DesiredCapabilities();
private Properties prop = new Properties();
public DriverSetup() {
this.loadConfigProp();
this.setCapabilities();
try {
serverUrl = new URL("http://127.0.0.1:" + APPIUM_PORT + "/wd/hub");
} catch (MalformedURLException e) {
e.printStackTrace();
logger.error(e);
}
driver = new AndroidDriver(serverUrl, capabilities);
wait = new WebDriverWait(driver, EXPLICIT_WAIT_TIME);
driver.manage().timeouts().implicitlyWait(DEFAULT_WAIT_TIME, TimeUnit.SECONDS);
}
public static DriverSetup getInstance() {
if (_instance == null) {
_instance = new DriverSetup();
}
return _instance;
}
public AndroidDriver getDriver() {
return driver;
}
public WebDriverWait getWait() {
return wait;
}
public void loadConfigProp() {
EXPLICIT_WAIT_TIME = Integer.parseInt(PropertyLoader.loadProperty("explicitWait"));
DEFAULT_WAIT_TIME = Integer.parseInt(PropertyLoader.loadProperty("defaultWait"));
IMPLICIT_WAIT_TIME = Integer.parseInt(PropertyLoader.loadProperty("implicitWait"));
WAIT_ACTIVITY = PropertyLoader.loadProperty("waitActivity");
APPIUM_PORT = PropertyLoader.loadProperty("appiumServerPort");
NEW_COMMAND_TIMEOUT = PropertyLoader.loadProperty("newCommandTimeout");
DEVICE_READY_TIMEOUT = PropertyLoader.loadProperty("deviceReadyTimeout");
DEVICE_NAME = PropertyLoader.loadProperty("deviceName");
BROWSER_NAME = PropertyLoader.loadProperty("browserName");
PLATFORM_VERSION = PropertyLoader.loadProperty("platformVersion");
PLATFORM_NAME = PropertyLoader.loadProperty("platformName");
APP_PKG = PropertyLoader.loadProperty("applicationPackage");
APP_ACTIVITY = PropertyLoader.loadProperty("applicationActivity");
AUTOMATION_INSTRUMENTATION = PropertyLoader.loadProperty("automationInstumentation");
APPLICATION_NAME = PropertyLoader.loadProperty("applicationPath");
//PERFORMANCE_LOGGING = PropertyLoader.loadProperty("enablePerformanceLogging");
}
public void setCapabilities() {
capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, DEVICE_NAME);
capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, BROWSER_NAME);
capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, PLATFORM_VERSION);
capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, PLATFORM_NAME);
capabilities.setCapability(MobileCapabilityType.APP_PACKAGE, APP_PKG);
if (prop.containsKey("waitActivity")) {
capabilities.setCapability(MobileCapabilityType.APP_WAIT_ACTIVITY, WAIT_ACTIVITY);
} else {
capabilities.setCapability(MobileCapabilityType.APP_ACTIVITY, APP_ACTIVITY);
}
//if (!APPLICATION_NAME.isEmpty()) {
// capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AUTOMATION_INSTRUMENTATION);
// capabilities.setCapability(MobileCapabilityType.APP, new File(
// ClassLoader.getSystemResource(APPLICATION_NAME).getFile()).getAbsolutePath());
//}
capabilities.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, NEW_COMMAND_TIMEOUT);
capabilities.setCapability(MobileCapabilityType.DEVICE_READY_TIMEOUT, DEVICE_READY_TIMEOUT);
//capabilities.setCapability("unicodeKeyboard", true);
}
часть Application Manager
public class ApplicationManager {
public AndroidDriver driver;
public WebDriverWait wait;
public ApplicationManager() {
driver = DriverSetup.getInstance().getDriver();
wait = DriverSetup.getInstance().getWait();
}
Да, именно так и сделал выше, нашел на гите где-то примерчик и там было что-то подобное. Если честно после внедрения Selenide все скрины начали шустрее работать.
Есть у кого-то примеры небольшие, как юзать appium и selenide?
На одном из проектов, с самого начала решил попробовать использовать appium и selenide вместе. Долго и упорно совмещал, но по итогу selenide выпилил. Как бы ни был он хорош, их ветки разошлись в разные стороны. Мой совет - не мучайтесь! У последних версий appium java-client появилось много фишек, в том числе связанных с чисто мобильными действиями (swipe, tap, pinch, zoom). Ничего не мешает вам создать и хранить драйвер в отдельном контейнере, как это делает selenide. Посмотрите как ребята это реализовали. Я подглядел )) удобно. Но совместить пытаться не стоит. Это конечно мое мнение. Я так и не нашел тех, кто совместил. Да и попыток было не много.
Спасибо за информацию.
Я всё-таки хочу совместить когда-нибудь.
Хотя бы потому, что появляется всё больше лаптопов с тачскрином, а стало быть, классические браузеры (Chrome, IE, FF) тоже начнут поддерживать все эти события (swipe, tap, pinch, zoom). А может, уже поддерживают.
Алексей Баранцев рассказывал в девклубе, что разработчики Selenium тоже поглядывают в эту сторону и думают о том, как расширить стандарт Selenium Webdriver так, чтобы он поддерживал все новые девайсы, какими бы сумашедшими они ни были.
@asolntsev, было бы здорово, если бы совместили что-то подобное В будущем это многим понадобится…
@sergvkom, до последнего бился чтобы оставить, но на многие локаторы скорее всего что-то было с js, как-то оно падало на локаторах, которые реально присутствовали на скринах. Поэтому тоже выпилил… Но сама идея была конечно хорошая, чтобы быстро на мобилке тесты писать. Так приходится где-то шото с вейтами шаманить или ожидать
Хм… Наверное не подскажу в силу моего скудного опыта с Appium и Selenide. Но он даже с SelenideElement работает как-то странно, то есть элемент реально присутствует на скрине но в логах вылетают ошибки, что якобы его нет, при этом если менять на AndroidFintBy, то все без проблем находить, тапает, заполняет и так далее. Также очень странно, но я взял SelenideElement, к примеру инпут поле, в которое хотел сделать setValue.
selenideElementInputField.setValue("johny");
выбрасывало эксепшены, а вот такой вариант, вводил в поле текст:
selenideElementInputField.sendKeys("johny");
Сейчас уже эксепшены не скину, так как выпилил, но если когда будет еще возможность попробовать, сделаю файлик и туда все случаи с эксепшенами накидаю, скину в личку, может пригодится…
Я советую не объвлять веб-элементы как поля пэдж-обжекта, а объявлять методы в пэдж-обжекты, которые умеют делать логические действия с этой страницей. Внутри методов можно находить элементы через $(selector).
Блин. Вы думаете, именно events.fireChangeEvent(element); на андроиде грохается? А почему? Разве там нет событий?
Но вообще этот events.fireChangeEvent(element); был когда-то сделан ради IE, потому что в нём часто не срабатывал change event при изменении значения через sendKeys(). Может, в наше время он больше и не нужен…
А можно вопрос, какой от этого плюс, если не считать, что в файле меньше текста? Ведь в пейдже так наглядней получается, и более понятней… Да и каждый элемент получается отдельной переменной со смыслом…
Наглядней? Да нет, не наглядней. Все просто к этому привыкли, поэтому кажется, что наглядней.
Плюс фундаментальный.
Вся суть пэдж обжекта в том, что он должен инкапсулировать (т.е. скрывать) детали взаимодействия со страницей, с её элементами.
Золотое правило Мартина Фаулера: Если селектор поменяется - должен поменяться только пэдж обжект.
Этот принцип все и привыкли решать с помощью полей и аннотаций.
Но есть ещё второе правило:
Моё золотое правило: если поменяется алгоритм работы с элементом - должен поменяться только пэдж обжект. Например, чтобы ввести день рождения, раньше нужно было просто ввести значение в input, а теперь нужно сначала кликнуть “edit”, потом в открывшемся календаре выбрать нужный год, месяц и день и нажать “submit”. Я считаю, что и в этом случае должен поменяться только пэдж обжект. Точнее, даже только один метод пэдж обжекта - “enterBirthday()”.
Если взглянуть на вещи с этой позиции, становится понятно, что поля лишние - это детали реализации, они могут меняться.
Тут надо смотреть по конкретной реализации, иногда один селектор, используется больше чем в одном мтоде, да и в рамках метода может дергаться пару раз, тогда проще его обьявить как поле и менять один раз в одном месте. Если же селектор юзается исключительно один раз, эпизодически, то нет смысла его заводить в @FindBy.
При таком значительном изменении функциональности, как в вашем примере, что в том, что в другом случае будет меняться пейдж обджект, единственное, что с @FindBy прейдеться немного попрыгать между полями и методами, но это мелочи имхо.
В любом случае, я считаю, что легко можно миксовать оба подхода в зависимости от конкретной реализации.
Да, я примерно это и имел в виду.
Главное, чтобы у пэдж обжекта были публичные методы, позволяющие оперировать со страницей как с объектом. А как эти методы внутри реализованы - это ваше дело. Если вам для этого удобно использовать поля - ради бога. Главное, чтобы эти поля не были публичными, иначе нарушается инкапсуляция.
Как тогда в этом случае, я смогу пользоваться проверками именно в тесте, если поля будут private ? Например, в тесте я делаю проверку: placesPage.placesList.shouldHaveSize(1); где placesList это поле которое я дергаю из пейджи. Мне тогда придется писать проверки в самой пейдже (делать методы для проверок).
Да, придётся, и в этом и состоит суть пэдж обжектов: скрывать знание о том, КАК именно проводить проверки (т.е. взаимедействовать с веб-элементами). А знание о ом, КОГДА дёргать тут или иную проверку (т.е. вызвать соответствующий метод ПО), должно быть в тесте.