Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

10.8.19

Selenium WebDriver Type Hierarchy

Ever wondered how is the WebDriver actually implemented or why do we use ChromeDriver but call it WebDriver or what is RemoteWebDriver? Lets find out.

  • WebDriver is an Interface.
  • JavascriptExecutor is an Interface.
  • RemoteWebDriver is the parent Class that implements the WebDriver and JavascriptExecutor interfaces. 
  • ChromeDriver and FirefoxDriver and browser drivers are the child Classes that extend the parent RemoteWebDriver class.

An Interface by definition does not have any implementation details of its methods, just the empty method declarations. Its the responsibility of the implementing class to 'implement' those methods by adding details of what the methods would do.
Since WebDriver and JavascriptExecutor are Interfaces they only have abstract (empty) methods; and the 'fully-implemented' class RemoteWebDriver actually provides the definition of the methods in these 2 interfaces - All the abstarct methods in the WebDriver and JavascriptExecutor interfaces are implemented in the RemoteWebDriver class.
Browser specific drivers like ChromeDriver and FirefoxDriver then go and extend this RemoteWebDriver class to add more methods of their own; or have their own implementations of the same methods.

But why this hierarchy?  The actual developers of Selenium don't know how all the different browsers work internally. So they just declared the methods that they thought were important to work with Selenium and left the actual implementation part of these methods to the developers of these browsers.
The real problem is that browsers are complicated software and not everything is open-source/visible to external developers, so they cannot customize.
For instance, the actual implementation of the 'Click' method for WebDriver could be different for each of Chrome and Firefox, hence, they have their own driver versions for the same (which is why we don't use Firefox driver on Chrome).
Also, in a way this puts the onus on the browser-companies to provide the implementation of their drivers to stay relevant and be widely adopted.

So can we do this?  WebDriver driver = new WebDriver();
We get a compile time error: Cannot instantiate the type WebDriver - why? because we cannot instantiate an interface ie., cannot create an object of an interface (WebDriver) and invoke its methods.
Since WebDriver is an Interface and not a 'Class', and all its methods are just empty shells (abstract), we really cannot do anything anyways by creating an object of the interface and trying to call its methods - hence, its not advised to create an object of an empty interface.
Thus, if we want to perform any action we have to invoke the implementing class of that interface.


So should we do this?  WebDriver driver = new RemoteWebDriver();
We get a compile time error: The Constructor RemoteWebDriver() is not visible - what this means is that there is no method like this to be called directly (constructor is also a method).
Though technically we can have this code -
WebDriver driver = new RemoteWebDriver(capabilities);
Or
WebDriver driver = new RemoteWebDriver(URL, capabilities.chrome());
Or
WebDriver driver = new RemoteWebDriver(commandExecutor, capabilities);
Why we don't use the above is because RemoteWebDriver is usually intended to be used while working with Selenium Grid and needs the Selenium server, wehere-as if we use ChromeDriver() we would be invoking the local installation of the chrome browser on our machines.

What about this?  ChromeDriver driver = new ChromeDriver();
Since ChromeDriver is a class, it implements all the methods of the WebDriver interface. But the 'driver' instance that gets created will only be able to use the methods implemented by ChromeDriver and supported only by the chrome browser; and as such we would be restricted to run our scripts only using the chrome browser.
To work with other browsers we will have to create individual objects via - FirefoxDriver driver = new FirefoxDriver();
And we will have to keep switching at runtime.

This is the reason we use this:  WebDriver driver = new ChromeDriver();
So that we can work with different browsers without having to update our code for every browser specific driver. And this would make our code more extensible by providing us the flexibility to work with any number of browsers (drivers).
Also, this is better design as a change in driver initialization for one browser will not be affect others, and we can have different configurations for different browsers.
Here, WebDriver is the interface, ChromeDriver() is the Constructor, new is the keyword and [new ChromeDriver()] is the object referenced by the 'driver' variable.
'Java' specific reason - WebDriver is the super interface for all browser classes like FirefoxDriver, ChromeDriver etc. So WebDriver instance can hold object of any driver class. This is also called Upcasting - When we pass the reference of a super-class [parent] to the object of its sub-class [child].

But can we do vice versa? - ChromeDriver driver = new WebDriver();
We get a compile time error: Cannot convert from WebDriver to ChromeDriver.

But then why do we have to do this? - JavascriptExecutor js = (JavascriptExecutor) driver;
WebDriver and JavascriptExecutor are two different interfaces, and they do not have any methods common. The 2 methods of the JSE (executeScript and executeAsyncScript) are not present in WebDriver interface.
But all the methods of the WebDriver and JSE interfaces have been implemented by the browser drivers.
Because we had up-cast the 'driver' object to WebDriver and WebDriver does not have the methods of JSE interface, we have to down-cast.
We wouldn't have had to down-cast had we just used [ChromeDriver driver = new ChromeDriver();] In this case, you do not need to downcast it to JavascriptExecutor as the 'driver' has visibility of all methods of JSE because the browser driver class 'ChromeDriver' extends 'RemoteWebDriver' class, hence, ChromeDriver has indirect access of  all methods of JSE via RemoteWebDriver.

Infact we can even cast it to ChromeDriver and not have to use JavascriptExecutor, like below -
JavascriptExecutor js = (ChromeDriver) driver; // This works too!!


Addtional notes -

  • SearchContext is the top most interface which has only two methods names findElement() and findElements(). These methods are abstract as SearchContext is an interface. This is the reason we do not up-cast to SearchContext because there is no point in just having 2 methods to work with; and having to downcast every time we want to use the third method.
  • WebDriver is also an interface which extends SearchContext but since WebDriver has the maximum number of methods, it is the key interface against which tests should be written. There are many implementing classes for the WebDriver interface, as listed as below:
    • AndroidDriver
    • AndroidWebDriver
    • ChromeDriver
    • FirefoxDriver
    • HtmlUnitDriver
    • InternetExplorerDriver
    • IPhoneDriver
    • IPhoneSimulatorDriver
    • SafariDriver
  • WebDriver has many abstarct methods like get(String url), close(), quit() , getWindowHandle etc. WebDriver also has nested interfaces names Window, Navigation, Timeouts etc that are used to perform specific actions like getPosition(), back(), forward() etc.
  • RemoteWebDriver is the fully implemented class for WebDriver, JavascriptExecutor and TakesScreenshot interfaces. (Fully implemented class means it defines the body for all inherited abstract methods.)
  • Then we have browser specific driver classes like ChromeDriver(), EdgeDriver(), FirefoxDriver() etc which extend RemoteWebDriver.
  • RemoteWebDriver implements JavascriptExecutor and provides definition for both methods of the JSE. Since all browser-specific driver classes like ChromeDriver etc extends RemoteWebDriver, we can execute JavaScript commands via JSE methods on these different browsers.


13.4.17

Troubleshooting hacks, Jugaad


1.              Guice Provision Error -
Cause - Happens when Surefire plugin is initiated on Maven 3.0.5 (which is too old now). This usually happens on Jenkins/TC when the default settings for Maven are used.
Resolution - Use latest version of Maven and specify the same in Jenkins' Maven Settings too

2.              SurefireBooterException -          
To resolve, add this config in the POM, to set useSystemClassloader to false:
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <useSystemClassLoader>false</useSystemClassLoader>
    </configuration>
</plugin>

3.              Run Maven commands without changing dir –
We don’t need to ‘cd’ to the directory containing the pom every time we want to run a mvn command, we can fire the mvn command from anywhere as long as we give the path to the pom like below:
Syntax: mvn –f <fullpath-to-pom> <goals> -D<params>
Sample: mvn –f C:/Automation/keyword/pom.xml test –Dthread1=Test1
It would be good to not have any spaces in the path so as to avoid escape char.
Use / instead of \  in the path.

4.              Invoke CMD via VbScript –
If you want to invoke the CMD utility automatically with certain parameters then use the below snippet:
Set oShell = CreateObject(“WScript.Shell”)
cmndToRun = “mvn –f C:/Automation/keyword/pom.xml test –Dthread1=Test1”
oShell.Run “cmd.exe /k “ & cmndToRun
To keep the CMD window open use /k after cmd.exe or use /c to close it.

5.              StackOverflowError –
Happens due to infinite recursion. For example when a method invokes itself during its execution or where one class object is instantiated under another class recursively. We will not get any compile-time Errors, but at runtime we will get this Error.

6.               Maven Compilation Error - package <name> does not exist
Resolution - Sometimes old or un-used packages are not found after updating versions of some libraries, which causes compilation Errors. Fastest solution is to delete these unwanted packages from Java files.

7.              Log4J package is not getting imported in the classes, hence, not able to initiate logging.
Steps to troubleshoot - A combination of these resolved this, after multiple iterations
·       Tried mvn dependency:resolve - It got successfully downloaded when resolving dependencies via mvn.
·       Delete local repo and re-download all dependencies from scratch - Even after deleting the old local repo, and rebuilding the same from scratch, it does not work
·       'Cleaned' the eclipse project - it resolved all Errors, but still Log4J is not getting imported.
·       Delete '.lastUpdated'  files from local repo
·       For Windows cd (change directory) to <user-directory>\.m2\repository and execute this command:
for /r %i in (*.lastUpdated) do del %i
·       Now update dependencies again.
·       You could also get Errors like: [Could not find artifact org.apache.logging.log4j:log4j:jar:2.6.1 in central (https://repo.maven.apache.org/maven2) -> [Help 1]]
·       Run mvn eclipse:eclipse - This could cause the following Error, visible only on eclipse, not in maven: [The project was not built due to "Resource already exists on disk: '/bddproject/target/classes/log4j.properties'.". Fix the problem, then try refreshing this project and building it since it may be inconsistent]. To resolve it, run mvn clean, as it will delete the target folder, where this Error was. Then go to eclipse and do Project > Clean. Now, all Errors should be resolved.

8.              Get 'failed to load jvm' Error when running eclipse
            Try restarting the machine, it gets resolved sometimes

9.              Even though the default story steps[in myStory] have been implemented, while running the MyStories class, they still come up as @Pending in the results.
Cause - The Pending annotation was already imported by default in the default MySteps class, which was marking all the steps as pending.
Resolution - Delete that import statement for Pending, and re run the test, it worked and all steps were Green/Run/Passed
Also, if now I add a Pending annotation but do not use it, it still runs the remaining steps, as it should run.
 
10.          Getting junk lines being reported in the console with the freemarker log -
Like - "Jul 05, 2016 1:17:56 AM freemarker.log._JDK14LoggerFactory$JDK14Logger info"
If Log4J works, then this is not needed

11.          Run via mvn is Erroring out -
mvn clean install - this command Errors out
Error - [Error] Failed to execute goal org.jbehave:jbehave-maven-plugin:4.0.5:run-stories-as-embeddables (embeddable-stories) on project bddproject: Execution embeddable-stories of goal org.jbehave:jbehave-maven-plugin:4.0.5:run-stories-as-embeddables failed: A required class was missing while executing org.jbehave:jbehave-maven-plugin:4.0.5:run-stories-as-embeddables: org/apache/log4j/Priority

12.          The simple-archetype comes with the default jbehave report template, which needs to be fixed

13.          Even though the M2E plugin is downloaded and installed, it does not show up in eclipse - there is nothing for maven

14.          Getting the following Error while running dependency:resolve command
Error - Failed to collect dependencies at org.jbehave:jbehave-core:jar:4.0.5 -> com.thoughtworks.xstream:xstream:jar:1.4.7:
Cause - Looks like the command to download the dependencies was getting timed out, as it worked well when the internet connection was strong
Resolution - Ran the dependency:resolve command again, and it was successful, without any Errors

15.          Getting the following Error when deleting and re-importing the bdd project
Error - unbound classpath variable 'm2_repo
Cause - Eclipse is not able to locate the path of the local mvn repo
Resolution - The below steps work to solve this issues
·       Open the Eclipse Preferences [Window - Preferences]
·       Go to [Java - Build Path - Classpath Variables]
·       Click New and set its name as M2_REPO
·       Click Folder and select your Maven repository folder. For example, my repository folder is C:/Users/user/.m2/repository
·       Rebuild the Project.

16.            No need to run these commands
mvn compile
mvn clean install
mvn clean

17.          Error - archive for required library cannot be read in eclipse
·       This generally happens when you are importing projects, which has external jars [added via maven POM or via direct import]
·       The first thing to try is delete those external jars and their folders, and then re-import them
·       Then in Eclipse, go to Project > Clean Project [Ensure that Build Automatically is checked]
·       If this does not resolve, then see if the jars got corrupted during copy/import, then replace them with original/valid jars

18.          Avoid having multiple versions of the same jars in the projects - only have the required version and delete the rest.

19.          If you get Errors like 'Source Not found' or 'Attach source', or 'NoClassDefFoundException' it generally means some jar is missing and not there in your build path, so find that jar, and just add it to your build path

20.          Split method in Java has a bug!
·       When we use a split function, ideally, if we don’t specify any limit, it should return all the tokens in the string, but it does not, if you have multiple delimiters in the end with empty tokens. 
·       For example in a | delimited message ("ASDAS|ASDASD|AA||||ASS|||||"), the last empty tokens would be ignored.
·       To fix this use the limit as -1
·       String[] token = sampleMsg.split("\\|" , -1);

21.          Always use string.isEmpty() method to check if the string is empty or not. 
1.    Never use null or any other method. Even if the variable is not a String, convert it to String via toString and then use isEmpty().
2.    Though a lot of people would frown upon this idea, but its simple, effective, and very easy to remember and can be implemented by a rookie in your team
22.          Ensure that you use JDK [and not JRE] in your Project Build Path

23.          Apache POI –
When adding apache poi in the dependency tree ensure to add dependencies for "poi-ooxml" and "poi-ooxml-schemas" as well, as some of the base classes for apache poi use these jars, and otherwise we would not be able to use certain classes like XSSF.

24.          If QCUtils is not working in UFT
·       Try checking the Registry values for this key.
·       'HKEY_CURRENT_USER\Software\Mercury Interactive\QuickTestProfessional\MicTest\QEEE'. 
·       This key had the parameter 'ExternalExecutionSupported', so either set it to Yes or delete it.
 
 
Other Hacks -
  • Error: Could not find PKIX Certificate Path when connecting to Artifactory.
    • Problem: When trying to run any maven commands on windows machines, sometimes we get this error where we are not able to connect to Artifactory or any Central Repo in Enterprise setups. This error will not come on your home computer but its one of the perks of working in a big Co.
    • What its not: This problem is not related to your Artifactory credentials or API Keys, or git or bitbucketor even the maven Settings.XML; although thats what you might be lead to think.
    • Cause: The problem is related to outdated Java Security Certificates or the use of incorrect ones. This happens when the java pkg gets upgraded or the one that you currently have installed does not have the required certificates. So the solution really lies in updating the security certificate file (cacertificates file in jdk dir).
    • Sol 1: Manually find and download the latest certificate and then update the cacertificates file, and then import them via the usual 'keystore' import command that you can easily google. The prob with this approach is that it needs Admin rights to edit the cacertificates file, and you will not get that ever in a big Co - another perk. So this method is DOA.
    • Sol 2: If your jdk package has recently been upgraded, then you might be lucky enough to get the latest java cacertificates files which will hopefully have the correct certificates added, and you will have to re-point your JAVA_HOME and M2_HOME and PATH variables to this new jdk pkg.
      • But that also needs Admin rights, so you will not be able to do that also. Sometimes some Cos have support teams that give you temp Admin rights, which might save the day for you, if not, read on...
      • What you can do is reset the Env Variables like JAVA_HOME and M2_HOME and PATH to new values for the current session via CMD prompt. This will work only till the time this CMD prompt is open, and all changes will be lost when you close it, and you will have to re-do these. The steps are:
        • SET JAVA_HOME=<new path>
        • SET JDK_HOME=<new path>
        • SET PATH=<new path>;%PATH%
        • Remember to append to the PATH variable otherwise it will overwrite and remove all the other values in it.
        • No need to change the variables for Maven
        • This should point your current session to the new jdk pkg folder which has the correct certs file.
    • Sol 3: Create a new folder for JDK pkg where you would have admin rights and then re-point all the variables, including Maven based, to this new folder. This approach would be helpful if you are trying to update the existing certs file with the new certs



6.7.16

Java Code Snippets

Some random code snippets...

Running JavaScript via WebDriver -


JavascriptExecutor jsRunner = (JavascriptExecutor) driver;

// This will get us the name of the browser on which the test is running
String jsString = "return navigator.appCodeName";
System.out.println ("BrowserName = " + jsRunner.executeScript(jsString));

Code to get the text of all the links on a web page -

List<WebElement> allLinks = driver.findElements(By.tagName("a"));
for (WebElement link : allLinks){
     System.out.println("Link text = " + link.getText());
}

File copy - just ensure that the folder path has a \\ in the end, otherwise, the files wont be copied/deleted and even strange-ly, there wont be any errors!

    //Function to copy all the files in a dir, but will not replace if the file already exists
    public void copyAllFilesToDir(String sourceDir, String destinationDir){
        String fileName = null;

        File folder = new File(sourceDir);
        File[] listOfFiles = folder.listFiles();
        //loop through all the files in the dir
        for (int i =0; i < listOfFiles.length; i++){
            //check if the item is a file or a folder
            if(listOfFiles[i].isFile()){
                fileName = listOfFiles[i].getName();
                copyFile(sourceDir + fileName, destinationDir + fileName);
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.getMessage();
                }
            }
        }
    }

    //Function to delete all files in a dir
    public void deleteAllFiles(String dirPath){

        File folder = new File(dirPath);
        File[] listOfFiles = folder.listFiles();
        for (int i = 0; i < listOfFiles.length; i ++){
            if (listOfFiles[i].isFile()){
                if(listOfFiles[i].delete()){
                    System.out.println("file deleted -" + listOfFiles[i].getName());
                }else {
                    System.out.println("could NOT delete file -" + listOfFiles[i].getName());
                }
            }
        }
    }

    //Function to copy one file at a time, does not overwrite/replace existing files
    public void copyFile(String sourceFilePath,String destinationFilePath){
        File sourceFile = new File(sourceFilePath);
        File destinationFile = new File(destinationFilePath);
        try {
            FileUtils.copyFile(sourceFile, destinationFile);
        } catch (IOException e) {
            e.getMessage();
        }
    }

17.6.15

Notes on Array ArrayList HashMap in Java



ArrayArrayList
  • Stores primitive data types and objects, both
  • Stores only objects. This is not to say that we cannot work with primitive data types in ArrayList. Autoboxing feature of java helps us circumvent this.
  • Eg., this works:
  • ArrayList<Integer> integerList = new ArrayList<Integer>();
  • Here we are not storing primitive in ArrayList, instead autoboxing will convert int primitive to Integer object
  • Defined in Java as a fundamental data structure itself
  • Belongs to Collections framework
  • Array is a fixed length data structure. [Static nature]
  • Fixed size - the size of the array has to be defined while declaring the array
  • In an array of size 10 you can set to null your element at index 5. Your array will still have 10 elements numbered 0 to 9 but with an empty element at index 5.
  • ArrayList is a variable length Collection class.
  • Automatically shrinks/grows, as and when elements are removed/added. [This is its Dynamic nature]. We can create instance of ArrayList without specifying size
  • Stores similar data of one type
  • Can store heterogeneous data types
  • Cannot use Generics with Array, because Array instance must know about what kind of data type it can hold, and throws ArrayStoreException, if you try to store a type which is not convertible into type of Array
  • Can use Generics with Array
  • No of elements = Array.length
  • No of elements = ArrayList.size()
  • You can simply use assignment operator to store element into Array
  • objArray[1] = new Object();
  • You have to use add() method to insert element into ArrayList.
  • It is not a class
  • It is a class with many methods
  • Cannot be synchronized, so cannot be used in multithreaded env
  • Can be obtained a synchronized version
  • Elements retrieved with For loop
  • Can be retrieved with For loop and iterators
  • Elements accessible with index number
  • Accessing methods like get() etc. are available
  • Can be multidimensional
  •  
  
  
  
ArrayListHashMap
  • ArrayList implements List Interface
  • HashMap is an implementation of Map interface
  • ArrayList maintains the insertion order
  • HashMap doesn’t maintain any order, the returned key-values pairs are not sorted in any kind of order
  • ArrayList allows duplicate elements
  • HashMap doesn’t allow duplicate keys (It does allow duplicate values)
  •  
  • If you want an array of all the keys in the hash map, you can use the keySet() method










19.11.14

Connect to database via JDBC

Though we have numerous methods [and sophisticated too] to connect to a database with java, if you are looking for a simple snippet to just connect, run a query, and get the result, this below code will suffice.
Also, if your purpose is just to query and get results, just having the right jar would be enough, no need for the database client/driver installation.

--------------------------------------
//function to connect to database using JDBC/JConnect
public Connection getJDBCConnection(){

System.out.println("Getting DB Connection properties");
Properties properties = getPropertyValues("test1.properties");

String dbDriver = properties.getProperty("database.driver");
String dbServer = properties.getProperty("database.server");
String dbServerPort = properties.getProperty("database.port");
String dbName = properties.getProperty("database.name");
String dbUsername = properties.getProperty("database.username");
String dbPassword = properties.getProperty("database.password");

String dbURL = "jdbc:sybase:Tds:" + dbServer + ":" + dbServerPort + "/" + dbName;

//Initializing the connection object
Connection connection= null;

try{
//Register the database driver
Class.forName(dbDriver);

//Open a connection
System.out.println("Connecting to database - " + dbURL);
connection = DriverManager.getConnection(dbURL, dbUsername, dbPassword);
System.out.println("DB connection successful");

}catch(SQLException se){
se.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}

return connection;
}
--------------------------------------

//function to run query and get the resultset back
public ResultSet runDBQuery(Connection connection, String sqlToRun){

Statement statement = null;
ResultSet resultSet = null;

try{

statement = connection.createStatement();

System.out.println("Executing SQL Query - " + sqlToRun);
resultSet = statement.executeQuery(sqlToRun);

}catch(SQLException se){
se.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}finally{
try{
if(connection.isClosed() == false)
connection.close();
}catch(SQLException se){
se.printStackTrace();
}//end finally try
}//end try

return resultSet;
}
--------------------------------------

Now to use all of these...
--------------------------------------
String sqlToRun = "SELECT EmployeeID FROM Employee Where EmployeeID = 245145";
try{
ResultSet rs = commonLib.runDBQuery(commonLib.getJDBCConnection(),sqlToRun);

while(rs.next()){
//Retrieve by column name
int id = rs.getInt("EmployeeID");
//String last = rs.getString("last");

System.out.println("EmployeeID: " + id);
}
rs.close();
}
catch(SQLException se){
//Handle errors for JDBC
se.printStackTrace();
}catch(Exception e){
//Handle errors for Class.forName
e.printStackTrace();
}
--------------------------------------
Output will be...
EmployeeID: 245145


The jdbc properties are as follows:

#This is the standard driver class for connecting to Sybase database using JConnect jar
database.driver=com.sybase.jdbc4.jdbc.SybDriver
database.server=abc.xyz.net
database.port=5500
database.name=emp_comp
database.username=autobot
database.password=autobot1