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);  
      }  
      }  

3 comments:

  1. Hi,
    Thank you for the script.
    However I get
    org.openqa.selenium.WebDriverException: Script execution failed. Script: YUI().use('node-event-simulate', function(Y){var node = Y.one('lobby');node.simulateGesture('move',{path:{xdist:20,ydist:-20},duration:2000}, function() {Y.log('I was called.');});});;
    YUI is not defined (WARNING: The server did not provide any stacktrace information)
    Command duration or timeout: 9 milliseconds
    Build info: version: '2.31.0', revision: '1bd294d', time: '2013-02-27 20:53:56'
    System info: os.name: 'Windows 7', os.arch: 'amd64', os.version: '6.1', java.version: '1.6.0_32'
    Session ID: 4feb5de875eab5da3039fd26b2c78148
    Driver info: org.openqa.selenium.chrome.ChromeDriver
    Capabilities [{platform=XP, chrome.chromedriverVersion=26.0.1383.0, acceptSslCerts=false, javascriptEnabled=true, browserName=chrome, rotatable=false, locationContextEnabled=false, version=25.0.1364.152, cssSelectorsEnabled=true, databaseEnabled=false, handlesAlerts=true, browserConnectionEnabled=false, nativeEvents=true, webStorageEnabled=true, applicationCacheEnabled=false, takesScreenshot=true}]
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
    at java.lang.reflect.Constructor.newInstance(Unknown Source)
    at org.openqa.selenium.remote.ErrorHandler.createThrowable(ErrorHandler.java:187)
    at org.openqa.selenium.remote.ErrorHandler.throwIfResponseFailed(ErrorHandler.java:145)
    at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:554)
    at org.openqa.selenium.remote.RemoteWebDriver.executeScript(RemoteWebDriver.java:463)
    at Test2.drag(Test2.java:143)
    at MyTest.test(MyTest.java:47)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

    Error when trying to drag an element, what am I doging wrongly?

    ReplyDelete
  2. Hi alex, your browser says , it doesnt know what yui is. Yui library should be included in your webpage, includeyui() in above code does the job for you . for this u need to be connected to internet.

    ReplyDelete
  3. Hello Naga, I am getting error as "YUI is not defined" while trying to execute your script. Can you please help.

    Exception in thread "main" org.openqa.selenium.WebDriverException: unknown error: YUI is not defined
    (Session info: chrome=47.0.2526.106)
    (Driver info: chromedriver=2.20.353145 (343b531d31eeb933ec778dbcf7081628a1396067),platform=Windows NT 6.1 SP1 x86_64) (WARNING: The server did not provide any stacktrace information)
    Command duration or timeout: 5 milliseconds
    Build info: version: '2.48.2', revision: '41bccdd', time: '2015-10-09 19:55:52'
    System info: host: 'occoadmin-PC', ip: '10.15.81.67', os.name: 'Windows 7', os.arch: 'amd64', os.version: '6.1', java.version: '1.8.0_71'
    Driver info: org.openqa.selenium.chrome.ChromeDriver
    Capabilities [{applicationCacheEnabled=false, rotatable=false, mobileEmulationEnabled=true, chrome={userDataDir=C:\Users\rdash\AppData\Local\Temp\scoped_dir17212_18691}, takesHeapSnapshot=true, databaseEnabled=false, handlesAlerts=true, hasTouchScreen=true, version=47.0.2526.106, platform=XP, browserConnectionEnabled=false, nativeEvents=true, acceptSslCerts=true, locationContextEnabled=true, webStorageEnabled=true, browserName=chrome, takesScreenshot=true, javascriptEnabled=true, cssSelectorsEnabled=true}]
    Session ID: 270758bf27ffcdcf61c8185dc8f3071e

    ReplyDelete