Unable to cast AppiumDriver to AndroidDriver, что б сделать driver.pushFile()

Ребята, привет :slight_smile:

Помогите пожалуйста написать рабочий код.

И так, ситуация следующая: имеем на проекте фреймворк для мобильной автоматизации = Java + TestNG + Appium, наша программа работает на Android и iOS.

Архитектура задизайнена таким образом, что мы не использует напрямую AndroidDriver или IOSDriver, а только работаем с AppiumDriver.

И все было хорошо до момента, пока не стало нужно протестировать image upload. В Appium есть специальный метод - driver.PushFile() , вот только он работает с дочерними класами AndroidDriver или IOSDriver.

Думала, что удастся закастить AppiumDriver к AndroidDriver:

((AndroidDriver) DriverProvider.getDriver()).pushFile("/sdcard/Pictures", new File("src/test/resources/Photos/Alpine_Pass.jpg"));

или

(AndroidDriver) DriverProvider.getDriver().pushFile("/sdcard/Pictures", new File("src/test/resources/Photos/Alpine_Pass.jpg"));

но, увы, оба варианта выдают

ClassCastException: io.appium.java_client.AppiumDriver cannot be cast to io.appium.java_client.android.AndroidDriver 

(пока не получилось нагуглить рабочий вариант)

Подскажите пожалуйста, что я делаю НЕ так и как можно разрешить эту проблему.

Буду очень благодарна за любую помощь! :kissing_heart:

А вы знаете в какой момент, что работает? Т.е. у вас может работать ios и вы кастите к android или наоборот работает android а вы кастите к ios.

да, конечно.
У нас есть следующий метод:

public boolean isAndroidDriver(){
    if(getDriver().getCapabilities().getCapability("appium.platformName").equals("Android")){
        return true;
    }
    return false;
}

То есть, даже если у нас есть возможность узнать текущую ОС, то мы все равно не возвращаем из метода инстанс AndroidDriver-a :frowning:

Так а тут какой объект возвращается?
Upd: сорри, просмотрел: AppiumDriver

Ну так ёлки зелёные, вы же сами задизайнили такую архитектуру, которая не позволяет сделать то, что вы хотите.

Придётся передизайнить архитектуру, так чтобы использовался AndroidDriver или IOSDriver вместо AppiumDriver.

1 лайк

Нет я имею ввиду, что у вас ссылка AppiumDriver ссылается на объекты androiddriver и iosdriver или же на только на объект appiumdriver? Если только реализация через объект AppiumDriver, то тут и понятно почему происходит исключение.

именно, ссылка AppiumDriver ссылается только на объект AppiumDriver

увы и ах, но фреймворк достался нам в наследство, поэтому и возникла необходимость рефакторить существующую архитектуру. Но я все же надеялась, что ето можно сделать меньшей кровью :joy:

Ну тогда как и говорили выше надо все переделывать под 2 драйвера. Т.к. вы не может просто напросто сделать каст объекта родителя к дочернему классу.

1 лайк

да, похоже, что это единственно верный и рабочий путь :slight_smile:

Ребята, большое всем спасибо за советы!!! :v:

Как вы получаете драйвер изначально ?
DriverProvider.getDriver() - что происходит в методе getDriver() ?
Вам нжно написать такой метод который исходя из того что вам сейчас нужно возвращался Android или iOS driver.
Всего один метод

public AppiumDriver getDriver(boolean isAndroidDriverNeeded){
      if (isAndroidDriverNeeded){
         return new AndroidDriver<>(new URL(yourAppiumUrl), capabilities);
   } else {

         return new iOSDriver<>(new URL(yourAppiumUrl), capabilities);
}
}

Чтото типа такого и не надо будет переписывать все и сможете кастить АппиумДрайвер до Android/iOS драйвера

2 лайка

Спасибо вам за пример кода :hibiscus:

Но, боюсь, здесь не всё так однозначно. Дело в том, что тесты у нас бегут в параллели и сам AppiumDriver обернут в ThreadLocal:

 private static final ThreadLocal<AppiumDriver<MobileElement>> APPIUM_DRIVER_THREAD_LOCAL = new ThreadLocal<>();

public static void setDriver(String driver, Capabilities capabilities) {
        LOGGER.ingo("Initializing the driver...");
        if (driver.equalsIgnoreCase("appium")) {
            APPIUM_DRIVER_THREAD_LOCAL.set(AppiumDriverBuilder.builder().capabilities(capabilities.asMap()).build());
        }
        LOGGER.ingo("Started Appium driver");
    }

    public static AppiumDriver<MobileElement> getDriver() {
        return APPIUM_DRIVER_THREAD_LOCAL.get();
    }

И теперь мне не совсем понятно, как быть с каждым драйвером по отдельности:

public static void setDriver(String driver, Capabilities capabilities) {
        LOGGER.ingo("Initializing the driver...");
        if (driver.equalsIgnoreCase("Android")) {
            APPIUM_DRIVER_THREAD_LOCAL.set(new AndroidDriver<>(new URL(yourAppiumUrl), capabilities));
        }else{
            APPIUM_DRIVER_THREAD_LOCAL.set(new IOSDriver<>(new URL(yourAppiumUrl), capabilities));
        }
        LOGGER.ingo("Started Appium driver");
    }

Подойдет ли такая реализация для запуска тестов в паралелли?

Сначала не понял почему нельзя кастануть, подумал, что там что-то типа такого:

Object o = new Integer();
Integer i = (Integer) o;

А оказывается там вот так:

Object o = new Object();
Integer i = (Integer) o;

тогда так действительно сделать не получится, нужно в методе getDriver возвращать не родительский класс, а дочерний.

3 лайка

Вроде должно сработать так. В крайнем случае можно фабричный метод сделать, который на вход получает AppiumDriver, а на выход создаёт новый iOS или Andriod. Но я бы такой костыль не стал делать (создание нового драйера через фабричный метод).

Подойдет.
Ведь ThreadLocal означает что каждый тред имеет свое значение этого поля
Вы главное пробуйте. Основная ваша проблема в том что АппиумДрайвер не знает он Андроид или iOS и поетому не может скастоваться.

2 лайка

Кстати, я тоже думала, почему не работает кастинг

((AndroidDriver) DriverFactory.getDriver()).pushFile(...)

ведь AndroidDriver является потомком AppiumDriver

package io.appium.java_client.android;

public class AndroidDriver<T extends WebElement>
    extends AppiumDriver<T>

или я чего-то не понимаю… :roll_eyes:

У тебя создается объект класс AppiumDriver, т.е. с полями, методами и т.д. которые находятся только в классе AppiumDriver. Если ты пытаешь сделать каст такого объекта к типу дочернего класса, то будет выкинуто исключение т.к. класс AppiumDriver не знает ничего о содержимом классов AndroidDriver и IOSDriver.

1 лайк

Так я выше написал почему не работает. Если заменить в моём примере Object на AppiumDriver, а Integer на AndroidDriver или IOSDriver, то это будет как раз ваша ситуация. Каст возможен в первом случае и невозможен во втором.

Можно рассмотреть каст ещё вот на примере Object, Number, Integer и Double.
Object o = new Integer(1); // допустимо
Integer i = new Object(); // недопустимо! мы не можем объявлять ссылку (переменную) на родительский объект, потому что у объявленного дочернего типа могут быть дополнительные методы, которых нет в родительском.
Integer i = new Double(0.0); // аналогично так тоже сделать нельзя, т.к. тип Double не является дочерним для Integer.

При этом если мы в переменную типа Object помещаем объект типа Integer или Double, то объект остаётся того же типа, он не становится объектом, но по ссылке напрямую к его дополнительным методам обратиться будет нельзя, т.к. код считает что имеет дело с обычным типом Object. Вот для этого и делается кастинг. Он приводит ссылку к типу объекта.

Например, мы можем указать вот так:
Number n = new Integer(1);
а дальше сделать каст переменно n до типа Integer.
Integer i = (Integer) n; // это допустимый каст, т.к. ссылка n ведёт на объект типа Integer.

1 лайк

Большое вам спасибо за столь детальное разъяснения! :hugs:
Тепер вижу, что каст работает только в одну сторону)