Reputation: 7643
I have a requirement such that:
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`.
So, is there something possible like:
driver.findElement(By.id("id1")).<something to call click()>
I have already looked at Reflection in Java, but that looked to me complex as per my requirement. Any pointers will be helpful!
Upvotes: 16
Views: 5166
Reputation: 861
Seems like you're trying to build a keyword-driven framework. To be honest, I'd be very surprised to hear any successfull story on that. Was always wondering what's the real purpose of such kind of frameworks? Who gonna use it? Managers, manual QAs or stakeholders? For me it makes no sense to involve non-tech persons in test automation activities. Any automation requires good tech skills (including programming and devops). Otherwise, failure probability is quite high.
Anyway, your question is more about strings
vs functional interfaces
mapping, or very smart reflection usage. But the main problem is that you've chosen wrong API [inputs] for that. The following line:
driver.findElement(locator).doSmth();
is a right way to fail, as findElement
uses implicit waits. And I'm 100% sure that you'll start global refactoring / revising implemented approach, when you face with NoSuchElementException
/ StaleElementReferenceException
.
Common sense suggests using fluent waits with ExpectedConditions
. But it'll make your task even more complicated, as besides locators and actions you have to think about conditions, which should be supplied by the users.
From technical point, I'd create common wrappers first to encapsulate low-level WebDriver API calls. It'd be much easier to map or reflect such functions in comparison with raw calls. E.g. expected conditions could be hidden on enum level:
@Getter
@RequiredArgsConstructor
public enum WaitCondition {
visible(ExpectedConditions::visibilityOfElementLocated),
enabled(ExpectedConditions::elementToBeClickable);
private final Function<By, ExpectedCondition<WebElement>> type;
}
It'll be easy to retrieve required constant by calling e.g. WaitCondition.valueOf("visible")
, where input string could be passed from outside.
Common API wrapper may look like the following then:
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));
}
Mapping / reflection examples were already provided by others in this thread. Just a note: if you prefer reflection, I'd recommend you to look at jOOR library, which simplifies this process.
Upvotes: 0
Reputation: 192
The simplest way to do this is to use reflection:
String command = "click";
WebElement element = driver.findElement(By.id("id1"));
Method method = WebElement.class.getMethod(command);
method.invoke(element);
If you also want to call By.id
with reflection, then you can do this:
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);
Upvotes: 11
Reputation: 31878
In terms of design(and probably this can be further optimized and abstracted for sure), you can probably define an Enum
, let us name it Action
as:
public enum Action {
CLICK,
SENDKEY,
etc
}
In your code then do:
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;
}
Upvotes: 5
Reputation: 691735
Your variable represents something you want to do with a web element (in this case, click on it).
The appropriate type for that is not String
. Use a Consumer<WebElement>
instead (or whatever the type of what driver.findElement()
returns is):
Consumer<WebElement> command = e -> e.click();
// ...
command.accept(driver.findElement(By.id("id1")));
This is type-safe, efficient, refactorable, and much more flexible than reflection (since your consumer can do whatever it wants with the element, not limited to a single method call without any argument. Like for example, enter some text in a text field)
Upvotes: 22