1

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?

CuriousGuy
  • 1,545
  • 3
  • 20
  • 42
  • Because the class of `driver` already implements `TakesScreenshot`. Look at the actual type of `driver` in a debugger or sysout `driver.getClass().getName()` and lookup what that class actually implements. – k5_ Sep 30 '16 at 08:22
  • @k5_ then why do we cast it to `TakeScreenShot`? Can't we just do `driver.getScreenshotAs()`? – CuriousGuy Sep 30 '16 at 08:24
  • It might just work, i'm not too familiar with the hierarchy of that class and you did not paste enough. But if it does not work without the cast (and works with) the type of `driver` field/variable does not implement that interface. Only the dynamic type does. – k5_ Sep 30 '16 at 08:27
  • What is the *declared* type of your variable `driver`? Is it `WebDriver` ? Does that interface extend `TakesScreenshot` ? – Erwin Bolwidt Sep 30 '16 at 08:27
  • @ErwinBolwidt question updated. – CuriousGuy Sep 30 '16 at 08:29
  • 1
    @CuriousGuy so change the type of `driver` to `FirefoxDriver` and it will work without the cast – k5_ Sep 30 '16 at 08:38

2 Answers2

1

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.

Erwin Bolwidt
  • 30,799
  • 15
  • 56
  • 79
  • Yes, this is what I was just thinking. But I have another question - why don't I just create a `FirefoxDriver` and save all the effort of casting in such cases? Why all tutorials and examples are using `WebDriver` objects instead of the 'real' drivers? – CuriousGuy Sep 30 '16 at 08:54
  • @CuriousGuy that's because in most cases, you care about using the `WebDriver` methods down the line, in more complicated scenarios. Please don't let this example of theirs confuse you and give you a bad impression of programming to an interface. It's actually quite a useful technique in most cases, it's just that it's not useful in this simple case. – bytehala Sep 30 '16 at 08:57
  • @CuriousGuy I guess the thinking behind it is that not all drivers may be capable of taking a screenshot. But it's cumbersome, and from the Javadoc it looks like all drivers do implement `TakesScreenshot`, so it may be something historical that doesn't make a lot of sense anymore. – Erwin Bolwidt Sep 30 '16 at 08:59
0

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.

Community
  • 1
  • 1
bytehala
  • 665
  • 1
  • 10
  • 25