Java: вызов метода с именем, хранящимся в переменной

У меня есть требование, чтобы:

String command = "click";   // this can have value such as clear, getLocation, getSize, getTagName etc. 
WebDriver driver = new ChromeDriver(options); //creating a webdriver object
driver.findElement(By.id("id1")).click(); //Here I want "click" method should be called dynamically as per what I have stored in variable `command`.

Итак, есть ли что-то возможное:

driver.findElement(By.id("id1")).<something to call click()>

Я уже смотрел Reflection в Java, но это выглядело мне сложным в соответствии с моим требованием. Любые указатели будут полезны!

Ответы

Ответ 1

Самый простой способ сделать это - использовать отражение:

String command = "click";
WebElement element = driver.findElement(By.id("id1"));

Method method = WebElement.class.getMethod(command);
method.invoke(element);

Если вы также хотите вызвать By.id с отражением, вы можете сделать это:

String command = "click";
String id = "id";

Method byMethod = By.class.getMethod(id, String.class);
WebElement element = driver.findElement((By) byMethod.invoke(null, "id1"));

Method method = WebElement.class.getMethod(command);
method.invoke(element);

Ответ 2

Ваша переменная представляет что-то, что вы хотите сделать с веб-элементом (в этом случае нажмите на него).

Соответствующий тип для этого не String. Вместо этого используйте Consumer<WebElement> (или независимо от типа возвращаемого driver.findElement()):

Consumer<WebElement> command = e -> e.click();

// ...

command.accept(driver.findElement(By.id("id1")));

Это безопасное по типу, эффективное, рефакторизуемое и гораздо более гибкое, чем отражение (поскольку ваш потребитель может делать все, что захочет, с помощью этого элемента, не ограничиваясь одним вызовом метода без каких-либо аргументов. Например, введите текст в текстовом поле)

Ответ 3

С точки зрения дизайна (и, вероятно, это может быть дополнительно оптимизировано и абстрагировано наверняка), вы, вероятно, можете определить Enum, назовите его Action как:

public enum Action {
 CLICK,
 SENDKEY,
 etc
}

В вашем коде выполните следующее:

Action action = <input>;
// find the element
WebElement element = driver.findElement(By.id("id1"));
switch(action) {
    case CLICK:
        element.click();
        break;
    case SENDKEY:
        element.sendKey();
        break;
    ...
    default:
        System.out.println("Undefined action");
        break;
}

Ответ 4

Похоже, вы пытаетесь создать фреймворк, управляемый ключевыми словами. Честно говоря, я был бы очень удивлен, услышав об этом успешную историю. Всегда задавалось вопросом, какова реальная цель таких рамок? Кто это будет использовать? Менеджеры, руководство QA или заинтересованные стороны? Для меня нет смысла привлекать нетехнических людей к мероприятиям по автоматизации тестирования. Любая автоматизация требует хороших технических навыков (включая программирование и детоп). В противном случае вероятность отказа будет довольно высокой.

Во всяком случае, ваш вопрос скорее связан с отображением strings vs functional interfaces, или с использованием очень умного отражения. Но главная проблема заключается в том, что вы выбрали неправильный API [input] для этого. Следующая строка:

driver.findElement(locator).doSmth();

- это правильный способ сбой, поскольку findElement использует неявные ожидания. И я на 100% уверен, что вы начнете глобальный рефакторинг/пересмотренный реализованный подход, когда вы столкнетесь с NoSuchElementException/StaleElementReferenceException.

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

С технической точки зрения я сначала создавал общие оболочки, чтобы инкапсулировать вызовы API-интерфейсов низкого уровня WebDriver. Было бы намного проще отображать или отражать такие функции по сравнению с необработанными вызовами. Например. ожидаемые условия могут быть скрыты на уровне перечисления:

@Getter
@RequiredArgsConstructor
public enum WaitCondition {

    visible(ExpectedConditions::visibilityOfElementLocated),
    enabled(ExpectedConditions::elementToBeClickable);

    private final Function<By, ExpectedCondition<WebElement>> type;
}

Будет легко получить требуемую константу, вызвав, например. WaitCondition.valueOf("visible"), где входная строка может быть передана извне.

Общая оболочка API может выглядеть следующим образом:

protected void click(By locator) {
    click(locator, enabled);
}

protected void click(By locator, WaitCondition condition) {
    waitFor(locator, condition).click();
}

private WebElement waitFor(By locator, WaitCondition condition) {
    return wait.until(condition.getType().apply(locator));
}

Примеры отображения/отражения уже были предоставлены другими в этом потоке. Просто обратите внимание: если вы предпочитаете отражение, я бы рекомендовал вам посмотреть jOOR, что упростит этот процесс.