Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. Show all posts

3.8.19

Difference between WebDriver and JavaScript Clicks

We can click on a webelement in 2 ways:
Using WebDriver click – element.click()
Using JavaScript click – ((JavascriptExecutor)driver).executeScript("arguments[0].click()", element); 

When we click on a webelement using WebDriver, it checks for 2 conditions before clicking - The element must be visible; and it must have a height and width greater than 0.
If preconditions are not satisfied, the click is not performed and we get an exception.

But the JavaScript click method does not check these preconditions before clicking. The HTMLElement.click() method simulates a mouse click on an element. When click() is used with supported elements (such as an <input>), it fires the element’s click event.
So, JavaScript can click on a webelement which may not be actually visible to the user or be clickable by WebDriver API. 

But we know - "Selenium-WebDriver makes direct calls to the browser using each browser's native support for automation." Meaning...WebDriver tries to mimic actual user behavior when working on browsers.

From Selenium 3 onwards, WebDriver APIs are designed to simulate actions similar to how a real user would perform actions on the GUI via the browser, and not use wrapped JS calls to execute different commands on the browser, like it happens via SeleniumRC.

All browsers now have their own drivers which implement the WebDriver API and Selenium communicates with these drivers via HTTP and these drivers communicate natively with the browser. So we can say that the ChromeDriver performs actions similar to a user using the chrome browser.

JavaScript bypasses this and goes to interact with the DOM of the page directly. This is not how a real user would use a browser.
This is also similar to the problem we had with v1 of Selenium where it used JavaScript to directly communicate with the browser [because SeleniumRC was just a form of wrapped JavaScript calls]

Also sometimes, use of JS methods may not trigger events which would have been otherwise triggered had we used WebDriver. For example, a subsequent onClick() event may not get triggered when a button is clicked via JS.

Hence, in order to simulate actual user behavior we should go for WebDriver and use JS sparingly and only when direct methods of WebDriver dont work.


This was precisely the problem with SeleniumRC.

SeleniumRC had 2 components - core and server. The core is basically a bunch of JavaScript code that is injected into the browsers to control/automate the behavior. Using JavaScript to control browsers caused issues, specially with IE as it has a different implementation/behavior with JavaScript. In a way, Selenium sends Selenese commands over to Se Core via JS Injections which in turn control the browser. 

Also, there was problem with the same origin policy of browsers and to overcome this, the server component was used so that all the JavaScript code injection was directed via the server, so as to appear that its originating from the same host. This caused problems when there were popups, file uploads, etc, and this was relatively slower to run.
[To avoid 'Same Origin Policy' proxy injection method is used, in proxy injection mode the Selenium Server acts as a client configured HTTP proxy, which sits between the browser and application under test and then masks the AUT under a fictional URL]

Also, there were many overlapping and confusing methods implemented which made it difficult to use.

WebDriver is a cleaner and object oriented implementation, and it controls the browsers using their native methods for browser automation, and does not rely on JavaScript injection. It works at the OS/Browser level, and does not have to use JS to control the browser.

Also SeleniumRC did not support headless testing; there was no HtmlUnitDriver.

2.8.19

Use JavaScript with Selenium WebDriver

WebDriver is very powerful and supports lots of methods and features. But there are some cases which are best handled via JavaScriptExecutor.

JS extends the capabilities of the WebDriver and can be helpful in the below cases.

  • Submit page instead of Click - Sometimes a button on a webpage does not have any Click methods, instead its treated as a form that has to be submitted. In such cases, even though there is button you can theoritically click, its not registered as a click but rather as a submit. Hence, in such cases, the click() method of the WebDriver may not always work and we may have to submit the page/form via JS.
  • Handling nested web elements - Usual WebDriver commands like "Click" may not work on toggle always, as it may find that object is not clickable.
  • Complete area of some web elements like button, checkbox etc are not  clickable. You need to click on specific part of element to perform action. Selenium might fail here some times. In this case also JS is very useful.
  • Handling different types of Calendars.
  • Scrolling can be a big problem in selenium. Using JS, we can scroll by pixels or to the web element.
  • Handling hidden elements - JS can get text or attribute value from hidden webelements which could be difficult by direct methods.
  • Drag and drop issues can be handled via js.
  • Object Location - JS can also be used to locate web elements using below methods
    • getElementById
    • getElementsByClassName
    • getElementsByName
    • getElementsByTagName

 /*
  * Function to execute Synchronous script via JS
  * The return is of the type superclass Object
  */
 public Object executeSyncJS(String jsCode) {

   /*
   * Downcasting driver to JavascriptExecutor object because we had upcasted our driver object to WebDriver
   * We don't need to downcast had we used [ChromeDriver driver= new ChromeDriver();] instead of [WebDriver driver= new ChromeDriver();]
   */

  JavascriptExecutor jsExe = (JavascriptExecutor) getDriver();
 

  /*
   * The return type of the JS Response depends on the result of the command executed
   * It could be anything - String, boolean, Map etc
   * Since Object class is the parent class of all objects, we are setting the type of response to 'Object'
   */

  Object jsResponse = jsExe.executeScript(jsCode) ;

  return jsResponse ;
 }

 

 /*
  * Function to click a WebElement via JavaScript
  */
 public Object jsClick(WebElement elementToClick) {

  JavascriptExecutor jsExe = (JavascriptExecutor) getDriver();

  Object jsResponse = jsExe.executeScript("arguments[0].click;" , elementToClick );

   return jsResponse;
 }


  public void jsDemo() {
  

  //Scroll vertically via JS 
  executeSyncJS("window.scroll(0,1000)");

  Utils.sleep(1000);

  executeSyncJS("window.scrollTo(0,2000)");

  Utils.sleep(1000);

  executeSyncJS("window.scrollBy(0,1000)");

   //Return the result of a script execution

  Object result = executeSyncJS("return 1===2");

  log.info("Result of JS: " + result);
 
 }

 

 /*
  * Function to find element by ID via JavaScript
  * To return a WebElement we need to downcast the Object
  */
 public WebElement jsGetElementById() {

  WebElement webElement = null ;

//  getDriver().get("https://www.cleartrip.com/");

  JavascriptExecutor jsExe = (JavascriptExecutor) getDriver();

  jsExe.executeScript("return document.getElementById('FromTag')") ;

  return webElement;
 }