I was thinking on the following example for taking a screenshot in WebDriver2:
WebDriver driver = new FirefoxDriver();
driver.get("http://www.google.com/");
File scrFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
When a class implements an Interface, the class must implement the defined the methods in the interface, right?
So, how come during runtime we implement TakeScreenshot interface, without implementing the logic in getScreenshotAs method before that?
I tried to simulate it this way:
interface TakeScreenShot{ public void getScreenshotAs(); }
class WebDriver
{
public static void main (String[] args) throws java.lang.Exception
{
WebDriver driver = new WebDriver();
((TakeScreenShot)driver).getScreenshot();
}
}
I ran it in Ideone and I am getting a runtime error:
Runtime error time: 0.05 memory: 711168 signal:-1
So, how does it work in WebDriver?
In your first example, WebDriver is an interface - and interface that doesn't extend TakesScreenshot. So if you have a variable driver of the declared type WebDriver, you can't call methods on that variable that are in TakesScreenshot.
But the variable driver points to an actual object which has an implementation class - FirefoxDriver. And FirefoxDriver does implement the TakesScreenshot interface.
There are several other ways in which you can invoke the getScreenshotAs method:
Example 1:
change the declared type of driver to FirefoxDriver which does
implement TakesScreenshot:
FirefoxDriver driver = new FirefoxDriver();
driver.get("http://www.google.com/");
File scrFile = driver.getScreenshotAs(OutputType.FILE);
Example 2:
cast WebDriver driver to FirefoxDriver instead of
TakesScreenshot - because FirefoxDriver implements
TakesScreenshot you can call methods from the latter interface
directly through type FirefoxDriver.
WebDriver driver = new FirefoxDriver();
driver.get("http://www.google.com/");
File scrFile = ((FirefoxDriver)driver).getScreenshotAs(OutputType.FILE);
Now in your second example, you have a class WebDriver (not an interface) and that class doesn't implement TakesScreenshot so you can't cast it to that type.
This is because the interface WebDriver is not a subclass of TakesScreenshot. However, the object being assigned to driver in your first snippet is of type FirefoxDriver which is both a WebDriver and TakesScreenshot.
The technique being done in your first snippet is "programming to an interface". There are a lot of duplicate questions regarding that, but this answer is the simplest.
You basically do that when you only care that driver is a WebDriver instance, and you don't care whether it is an OperaDriver, ChromeDriver, etc. This is useful when you want to use WebDriver methods without worrying about how that functionality is implemented.
Now casting it to TakesScreenshot means that whatever driver variable is, you are hoping that it also implements TakesScreenshot. Because if it does not, you will encounter a ClassCastException at runtime, as you observed.
Related
I have seen somewhere we can use both
WebDriver driver = new FirefoxDriver()
or
SearchContext driver = new FirefoxDriver()
I am confused what is the difference between these two different interfaces?
SearchContext
SearchContext is an interface which is the runtime container for contextual information for applications search. It contains search related meta information and can hold the reference to an external context that might be useful for the purpose of search as well as security. When used for searching, it holds a reference to AppsWebContext and can be obtained by getAppsContext. This context is passed to most applications plug-in code where custom implemenation can obtain runtime context information.
Interface SearchContext
SearchContext Interface have 2(two) subinterfaces:
WebDriver
WebElement
The implementing classes are:
ChromeDriver
EdgeDriver
EventFiringWebDriver
FirefoxDriver
InternetExplorerDriver
OperaDriver
RemoteWebDriver
RemoteWebElement
SafariDriver
SearchContext has only two methods:
findElement(By by)
findElements(By by)
Example
An example of using SearchContext is as follows:
#Override
public List<WebElement> findElements(SearchContext searchContext) {
List<WebElement> elements = new ArrayList<>();
try {
elements.add(this.findElement(searchContext));
} catch (Exception ex) {
}
return elements;
}
This is the best blog which clearly answers this question:
http://makeseleniumeasy.com/2017/04/02/hierarchy-of-selenium-classes-and-interfaces/
To add more:
SearchContext driver = new ChromeDriver();
Now if you like to use the abstract methods available with WebDriver like get(String url), close(), quit() etc. you have to downcast the driver instance to WebDriver level:
((WebDriver) driver).close();
SearchContext is the superInterface of Webdriver and WebElement interfaces. As said in previous answers, searchContext have only two abstract methods.
findElement(By by)
findElements(By by)
If we create object using searchContext,only above specified method could be used.
WebDriver have many useful and required methods like get,getTitle,close,quit,switchTo,etc.
These webdriver methods cannot be used directly unless you downcast to Webdriver.
So, it is advisable to use
WebDriver driver = new FirefoxDriver()
SearchContext is the super interface of WebDriver and WebElement interface.
So, SearchContext and WebDriver has parent child relationship between them.
SearchContext has only two abstract methods:
WebElement findElement(By by)
java.util.List findElements(By by) : Return list of WebElements
On the other hand WebDriver interface also have many abstract methods.
All abstract methods of SerachContext and Webdriver interface are implemented in the RemoteWebDriver class.
These links will help you to understand:
https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/SearchContext.html
https://www.javadoc.io/doc/org.seleniumhq.selenium/selenium-api/2.50.1/org/openqa/selenium/WebDriver.html
I understand Selenium Webdriver is a Interface and all the browser class is implementing this Interface, I also understand Interface variables are by default static and final. When I was reading about parallel execution using TestNG I read that selenium commands are passed to correct browser using session ID and each of the instances have different session IDs. I am confused how is it possible to assign different values to static final session ID variables of Webdriver Interface??
WebDriver interface doesn't contain session information, neither does SearchContext, which WebDriver Extends.
RemoteWebDriver is handling most of the business, it implements WebDriver.
ChromeDriver for example extends RemoteWebDriver.
this allows the following:
WebDriver driver = new ChromeDriver();
RemoteWebDriver has-a sessionId
you can't get to the sessionId, because its private in RemoteWebDriver.
You want to look for HttpCommandExecutor, RemoteWebStorage and WebStorage,
RemoteSessionStorage and SessionStorage.
Best way is to use your IDE. In Eclipse you would right click on the Class and then "open declaration"
In summary, you can instantiate multiple instances of RemoteWebDriver, each with their own sessionId. When you chose to use the WebDriver interface you lose access to methods not described by the interface.
BTW: If you have attempted to use the JavascriptExecutor, you know you have to cast your WebDiver to it. This is because RemoteWebDriver also implements this interface, but you can't access its methods using the WebDriver Interface.
hope this helps...
How to initialize the driver so it can be used by all classes
Hi All,
I am writing a test automation framework in JAVA using Appium, Selenium and Cucumber.
I start off by declaring an Appium Driver in one of my test step files and then this gets cast to an Android Driver or iOS Driver depending on the app under test.
I need some help please - I need all of my class files to have access to this instance of the driver but I’m not sure how to do this. The test is driven from the feature file and some of the test steps are in different class files so how can they all access this instance of the driver?
Thanks
Matt
You can make an initialising method in the class where all the other config setup can be done and then you can make an instance of that class to call the getDriver method.
For example:
public class initialiseDriver{
private static AppiumDriver<MobileElement> driver;
public AppiumDriver<MobileElement> getDriver() throws IOException {
if (PLATFORM_NAME.equals("Android")) {
// setup the android driver
} else if (PLATFORM_NAME.equals("iOS")) {
// setup the ios driver
}
return driver;
}
}
You can just call this method where you want to use the driver. Ideally, you should initialise the driver by calling this method in the #BeforeSuite/#BeforeClass method, so that you don't need to call this method everytime you start your script as it would be called implicitly with the #BeforeSuite/#BeforeClass.
you can define your AppiumDriver as static
public class AppiumHelper(){
public static AppiumDriver<MobileElement> driver;
public void setupDriver(){
//define your DesiredCapabilities
//initialize your driver
}
Then you can use your driver in your test method like
public void test1(){
MobileElement element= AppiumHelper.driver.findElementById("elements id");
}
The serenity PageObject class provides an inbuilt getDriver() method which you can call wherever you want to initialize the driver(preferably in the test classes). Avoid trying to initialize the driver in any of your step definations/step libraries(Managing using #Managed annotation) else it will throw a :
null pointer exception.
I'm using WebDriver (Selenium) and I want to add custom methods to WebDriver such as driver.performCustomAction().
Being that I could instantiate an instance of FirefoxDriver or ChromeDriver I cannot simply extend FirefoxDriver bec I would not be able to use the functionality with Chrome Driver.
Tech I could create a new class and pass an instance of WebDriver to the constructor (so it could be either FF or Chrome) but then I would be unable to perform all of the non custom actions of each class such as findElements(), getText() on the new object.
In other words, if my new class is called WrappedWebDriver and I instaniate a new instance of it as follows:
WebDriver FFDriver = new FirefoxDriver();
WrappedWebDriver WDriver = new WrappedWebDriver(FFDriver);
I will be able to call WDriver.performCustomAction() but I will not be able to call WDriver.findElement() or any of the other methods defined in the FirefoxDriver class (or the actions I could perform using FFDriver ).
How can I add new methods that apply to both FirefoxDriver and ChromeDriver without writing it twice while retaining all functionality of each respective class?
P.S: I know Java doesn't allow multiple inheritance is there some other way around it?
I think you can create Wrapper class which will hold instance of the Webdriver and you will wrap methods of webdriver which u want to support. You can do it for instance like this:
public class WrappedWebDriver {
public WebDriver driver;
public WrappedWebDriver(WebDriver driver){
this.driver = driver
}
public WebElement find(By by){
//your customization code
return driver.findElement(by);
}
public void setText(By by, String text){
//your customization code
driver.findElement(by).sendKeys(text)
}
public void performCustomAction(){
//your customization code
}
}
You can customize Webdriver standard methods by adding some functionality in wrapped methods. By making driver public, you give user an option to choose beetween using standard driver methods or your customized methods.
There is nice and useful Wrapper API for selenium named Conductor. https://github.com/conductor-framework/conductor. There you can find more complex example how to wrap WebDriver.
You perhaps need to be Extending EventFiringWebDriver. This class by its very behavior is composite in nature (Its created by taking a reference to an existing webdriver instance) and it was originally designed to be used for tapping into the before/after events of all webdriver originating actions. But it can very well suite your purpose.
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.events.EventFiringWebDriver;
public class WrappedDriver extends EventFiringWebDriver {
public WrappedDriver(WebDriver driver) {
super(driver);
}
public void performCustomAction(){
//your customization code
}
}
So in essence you first build a decorator that implements all the interfaces that an actual RemoteWebDriver sub-class would implement and then have your customized class extend the decorator. The decorator class here in this case is EventFiringWebDriver
Here is how I declare firefox driver:
public static WebDriver driver = new FirefoxDriver();
I place the code above outside main and within my class (global)
Here is how I declare chrome driver:
System.setProperty("webdriver.chrome.driver", "/path/xxx/xxx/xx");
WebDriver driver = new ChromeDriver();
I place the code above in main
Here is the issue:
I want to make the ChromeDriver as a global but I NEED to set the property before doing so. But I place the System.setProperty("xx","xx"); within the main body. Cuz it gives error when placed outside.
Here is a user trying to do the same thing as me. Trying to run different browsers using the same driver : How to run Selenium tests in multiple browsers for cross-browser testing using Java?
The answer is involves declaring the driver in the main body and not as a constant before.
My issue: All functions need driver declaration from before. Calling functions which use driver. If I declare driver in main, I need to continuously pass it as a parameter to all the functions. I do not wish to do that. Here is an example function
public static void a(){
driver.findElement(By.id("hi"));
}
How about something like:
class SomeTest {
static WebDriver driver;
public static void main(String[] args) {
System.setProperty("key", "value");
driver = new ChromeDriver();
}
public static void a() {
driver.findElement(By.id("hi"));
}
}