Tuesday 11 December 2012

Simulating touch gestures on Ipad/Iphone using Webdriver

I have been working with Webdriver to test a HTML5 , JavaScript rich web application across different devices(Android / IOS) and browsers. Webdriver is a powerful tool which serves my purpose.

Webdriver has flavors for both Android and IOS devices. Webdriver for Android devices Android driver is more matured than IOS webdriver and has support for all touch events / gestures. On other hand IOS driver do not have any support of touch actions and gestures simulation.

I have been working on this to implement this. By googling I found this but the solution here depends on underlying implementation and works only for events implemented in jquery.By further investigation I found my task can be done with the help of YUI (yahoo's library). This library provides way to simulate touch events / gestures. I found this to be independent of underlying implementation.

The javascripts written using this library are executing and resulting proper simuation of actions. All I need now is way to inject these JavaScripts in to the browser, and this magic is done by JavascriptExecutor. Webdriver provides a way to inject a Javascript and execute it in browser by method executeScript.

So by including YUI library and by injecting javascripts we can simulate gestures in IOS devices using Webdriver. As we are achieving this by JavaScript this solution works well for other browsers too.

 import java.util.ArrayList;  
 import java.util.List;  
 import org.openqa.selenium.By;  
 import org.openqa.selenium.JavascriptExecutor;  
 import org.openqa.selenium.Point;  
 import org.openqa.selenium.WebDriver;  
 import org.openqa.selenium.WebElement;  
 public class TouchAction {  
      public static String YUI_PATH = "http://yui.yahooapis.com/3.7.3/build/yui/yui.js";  
      private WebDriver driver;  
      private JavascriptExecutor js;  
      public TouchAction(WebDriver driver)  
      {  
           this.driver=driver;  
           js = (JavascriptExecutor) driver;  
      }  
      public void includeYUIlibrary(){  
           List<WebElement> importedScripts = driver.findElements(By.tagName("script"));  
           boolean yuiIncluded = false;  
           for(WebElement e : importedScripts)  
           {  
                if(e.getAttribute("src").contains(YUI_PATH))  
                {  
                     yuiIncluded = true;  
                     break;  
                }  
           }  
           if(!yuiIncluded)  
           {  
                /*  
                 *      script = document.createElement('script');    
                     script.type = 'text/javascript';    
                     script.async = true;    
                     script.onload = function(){   
                         // remote script has loaded    
                    };    
                     script.src = 'http://yui.yahooapis.com/3.7.3/build/yui/yui.js';   
                      document.getElementsByTagName('head')[0].appendChild(script);  
                 */  
                String IncludeYUI = "script = document.createElement('script');script.type = 'text/javascript';script.async = true;script.onload = function(){};script.src = '"+YUI_PATH+"';document.getElementsByTagName('head')[0].appendChild(script);";    
                js.executeScript(IncludeYUI);  
           }  
      }  
      public void tap(String jqueryToElement)  
      {  
           includeYUIlibrary();  
           /*  
            //simulate tap gesture  
         node.simulateGesture("tap");  
            */  
           String JavascriptToTap = "YUI().use('node-event-simulate', function(Y){var node = Y.one('"+jqueryToElement+"');node.simulateGesture('tap');});";  
           js.executeScript(JavascriptToTap);  
      }  
      public void tap(String jqueryToElement, Point p)  
      {  
           includeYUIlibrary();  
           /*  
            * // simulate tap with options and callback  
             node.simulateGesture("tap", {  
               point: [30, 30], // tap (30, 30) relative to the top/left of the node  
            });  
            */  
           String pointlocation = "point:["+p.getX()+","+p.getY()+"]";  
           String JavascriptToTapatPoint = "YUI().use('node-event-simulate', function(Y){var node = Y.one('"+jqueryToElement+"');node.simulateGesture('tap',{"+pointlocation+"});});";  
           js.executeScript(JavascriptToTapatPoint);  
           try {  
                Thread.sleep(1000);  
           } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
           }  
      }  
      public void tap(String jqueryToElement, Point p,long milliseconds)  
      {  
           includeYUIlibrary();  
           /*  
            * // simulate tap with options and callback  
             node.simulateGesture("tap", {  
               point: [30, 30], // tap (30, 30) relative to the top/left of the node  
               hold: 3000,   // hold for 3sec in a tap  
          });  
            */  
           String pointlocation = "point:["+p.getX()+","+p.getY()+"]";  
           String JavascriptToTaplongatPoint = "YUI().use('node-event-simulate', function(Y){var node = Y.one('"+jqueryToElement+"');node.simulateGesture('press',{"+pointlocation+",hold:"+milliseconds+"});});";  
           js.executeScript(JavascriptToTaplongatPoint);  
           try {  
                Thread.sleep(1000);  
           } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
           }  
      }  
      /*  
       * xdistance : +ve integer to move right and -ve integer to move left  
       * ydistance : +ve integer to move down and -ve integer to move up  
       * milliseconds : drag is performed for specified time  
       */  
      public void drag(String jqueryToElement,int xdistance,int ydistance,long milliseconds)  
      {  
           includeYUIlibrary();  
           /*  
            node.simulateGesture("move", {  
     path: {  
       xdist: 1500,  
       ydist: 0  
     } ,  
     duration: [milliseconds]  
   });  
       */  
           //String JavascriptTodrag = "YUI().use('node-event-simulate', function(Y){var node = Y.one('"+jqueryToElement+"');node.simulateGesture('move',{path:{xdist:"+xdistance+",ydist:"+ydistance+"},duration:"+milliseconds+"});});";  
           String JavascriptTodrag = "YUI().use('node-event-simulate', function(Y){var node = Y.one('"+jqueryToElement+"');node.simulateGesture('move',{path:{xdist:"+xdistance+",ydist:"+ydistance+"},duration:"+milliseconds+"}, function() {Y.log('I was called.');});});";  
           //js.executeAsyncScript("var callback = arguments[arguments.length - 1];"+"callback("+JavascriptTodrag+");");  
           js.executeScript(JavascriptTodrag);  
           try {  
                Thread.sleep(milliseconds+1000);  
           } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
           }  
      }  
      /*  
       * xdistance : +ve integer to move right and -ve integer to move left  
       * ydistance : +ve integer to move down and -ve integer to move up  
       */  
      public void drag(String jqueryToElement,int xdistance,int ydistance)  
      {  
           includeYUIlibrary();  
           /*  
            node.simulateGesture("move", {  
     path: {  
       xdist: 1500,  
       ydist: 0  
     } ,  
     duration: 1000  
   });  
       */  
           //String JavascriptTodrag = "YUI().use('node-event-simulate', function(Y){var node = Y.one('"+jqueryToElement+"');node.simulateGesture('move',{path:{xdist:"+xdistance+",ydist:"+ydistance+"},duration:2000});});";  
           String JavascriptTodrag = "YUI().use('node-event-simulate', function(Y){var node = Y.one('"+jqueryToElement+"');node.simulateGesture('move',{path:{xdist:"+xdistance+",ydist:"+ydistance+"},duration:2000}, function() {Y.log('I was called.');});});";  
           //js.executeAsyncScript("var callback = arguments[arguments.length - 1];"+"callback("+JavascriptTodrag+");");  
           js.executeScript(JavascriptTodrag);  
           try {  
                Thread.sleep(2000+1000);  
           } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
           }  
      }  
      /**  
       *   
       * @param jqueryToElement - String   
       * @param axis - Character X or Y in caps  
       * @param distance  
       * @param milliseconds  
       */  
      public void flick(String jqueryToElement,char axis,int distance,long milliseconds)  
      {  
           includeYUIlibrary();  
           /*  
            node.simulateGesture("flick", {  
     axis: y  
     distance: -100   
     duration: [seconds]  
        });  
           */  
           String direction = (""+axis).toUpperCase();  
           String JavascriptToflick = "YUI().use('node-event-simulate', function(Y){var node = Y.one('"+jqueryToElement+"');node.simulateGesture('flick',{axis:"+direction+",distance:"+distance+",duration:"+milliseconds+"});});";  
           js.executeScript(JavascriptToflick);  
           try {  
                Thread.sleep(milliseconds+1000);  
           } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
           }  
      }  
      /**  
       *   
       * @param jqueryToElement  
       * @param axis - axis of movement X or Y  
       * @param distance  
       */  
      public void flick(String jqueryToElement,char axis,int distance)  
      {  
           includeYUIlibrary();  
           /*  
            node.simulateGesture("flick", {  
     axis: y  
     distance: -100   
     duration: 50  
        });  
           */  
           String direction = (""+axis).toUpperCase();  
           String JavascriptToflick = "YUI().use('node-event-simulate', function(Y){var node = Y.one('"+jqueryToElement+"');node.simulateGesture('flick',{axis:"+direction+",distance:"+distance+",duration:2000});});";  
           js.executeScript(JavascriptToflick);  
           try {  
                Thread.sleep(2000+1000);  
           } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
           }  
      }  
      /**  
       *   
       * @param jqueryToElement  
       * @param startCircleRadius   
       * @param endCircleRadius  
       * (startCircleRadius < endCircleRadius) - spread(zoom in)  
       * (startCircleRadius > endCircleRadius) - pinch(zoom out)  
       * @param milliseconds  
       */  
      public void pinch(String jqueryToElement,int startCircleRadius,int endCircleRadius,long milliseconds)  
      {  
           includeYUIlibrary();  
           /*  
            //simulate a pinch: "r1" and "r2" are required  
        node.simulateGesture("pinch", {  
     r1: 100, // start circle radius at the center of the node  
     r2: 50,  // end circle radius at the center of the node  
     duration : [milliseconds]  
        });  
        //simulate a spread: same as "pinch" gesture but r2>r1  
        node.simulateGesture("pinch", {  
     r1: 50,  
     r2: 100,duration : [milliseconds]  
        });  
           */  
           int r1 = startCircleRadius;  
           int r2 = endCircleRadius;  
           String JavascriptTopinch = "YUI().use('node-event-simulate', function(Y){var node = Y.one('"+jqueryToElement+"');node.simulateGesture('pinch',{r1:"+r1+",r2:"+r2+",duration:"+milliseconds+"});});";  
           js.executeScript(JavascriptTopinch);  
           try {  
                Thread.sleep(milliseconds+1000);  
           } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
           }  
      }  
      /**  
       *   
       * @param jqueryToElement  
       * @param startCircleRadius   
       * @param endCircleRadius  
       * (startCircleRadius < endCircleRadius) - spread(zoom in)  
       * (startCircleRadius > endCircleRadius) - pinch(zoom out)  
       */  
      public void pinch(String jqueryToElement,int startCircleRadius,int endCircleRadius)  
      {  
           includeYUIlibrary();  
           /*  
            //simulate a pinch: "r1" and "r2" are required  
        node.simulateGesture("pinch", {  
     r1: 100, // start circle radius at the center of the node  
     r2: 50  // end circle radius at the center of the node  
        });  
        //simulate a spread: same as "pinch" gesture but r2>r1  
        node.simulateGesture("pinch", {  
     r1: 50,  
     r2: 100  
        });  
           */  
           int r1 = startCircleRadius;  
           int r2 = endCircleRadius;  
           String JavascriptTopinch = "YUI().use('node-event-simulate', function(Y){var node = Y.one('"+jqueryToElement+"');node.simulateGesture('pinch',{r1:"+r1+",r2:"+r2+"});});";  
           js.executeScript(JavascriptTopinch);  
           try {  
                Thread.sleep(1000+1000);  
           } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
           }  
      }  
      public static void main (String[] args)  
      {  
           WebDriver driver = new FirefoxDriver();  
           driver.get("http://www.applicationundertest.com");  
           TouchAction touchAction = new TouchAction(driver);  
           touchAction.tap("#element");  
           //tap and hold for 3 secs  
           touchAction.tap("#element",3000);  
           //drag 100pt right  
           touchAction.drag("#Canvas", 100, 0);  
           //drag 200pt towards top over period of 3 secs   
           touchAction.drag("#Canvas", 0, -200,3000);  
           //flick in horizontal direction  
           touchAction.flick("#canvas", 'X', 100);  
           //pinch(r1>r2) pinch action  
           touchAction.pinch("#canvas", 100, 50);  
           //pinch(r1<r2) spread action  
           touchAction.pinch("#canvas", 50, 100);  
      }  
      }