Selenium JavaScript Executor

JavaScript Executor ใน Selenium นั้นมันมีประโยชน์มาก. ถ้าใช้ให้ถูกวิธีจะสามารถเพิ่มความสามารถให้ Selenium ได้อย่างอลังการ. ยกตัวอย่างเช่น:

  • อยากจะ AJAX, XHR, หรือ Fetch แต่ติดปัญหาเรื่อง credential หรือ cross domain.
  • อยากจะรู้ว่า AJAX, XHR, หรือ Fetch นั้นได้ค่าอะไรกลับมา.
  • อยากจะเรียก JavaScript function บน app นั้นเพื่อทำงานบางอย่างเช่น expand/collapse dropdown, hide element.
  • อยากจะเก็บค่า performance พวก navigation timing, user timeline, resource timing.

ตัวอย่างด้านบนนั้นเป็นคำถามที่พบบ่อยและคนส่วนมากมักจะคิดว่า Selenium นั้นทำไม่ได้. แต่จริงๆมันทำได้ แต่ต้องใช้ท่ายาก ซึ่งก็คือ JavaScript Executor.

 

Contents

สร้าง JavaScript Executor

เราสร้าง JavaScript Executor ได้โดย cast WebDriver ให้เป็น JavascriptExecutor. เช่น

JavascriptExecutor jsExecutor = (JavascriptExecutor) driver;

สั่ง Execute JavaScript

JavaScript Executor นั้นมี 2 methodดังนี้:

executeScript(String script, … args)

method นี้จะ execute javascript text ที่ส่งเข้ามา. Parameter ตัวแรกคือ javascript code, parameter ตัวถัดๆมาจะเป็น arguments ที่เราสามารถนำไปใช้ใน code ของเราได้. ดูการส่ง parameter ในตัวอย่างที่ 1.
ห้ามใช้ method นี้ execute คำสั่งที่เป็น asynchronous เช่น function ที่ return Promise หรือพวกที่ใช้ callback เพราะว่า Selenium มันจะไม่หยุดรอ Promise resolve หรือ callback ใดๆทั้งสิ้น.  ดังนั้น JavaScriptExecutor มันจะจบลงอย่างรวดเร็ว โดยที่เราไม่ได้ผลลัพธ์.

ตัวอย่างที่ 1: เปลี่ยน style ของ element และสั่ง console.log.

public void testJsExecutor(){
    driver.get("https://www.blognone.com/");
    JavascriptExecutor jsExe = (JavascriptExecutor)driver; 
    WebElement img = driver.findElement(By.cssSelector("img")); 
    String script = "arguments[0].style.display=arguments[1]; if(arguments[2]){ console.log('its true'); }";
    jsExe.executeScript(script, img, "none", true);
}

ในตัวอย่างที่ 1 นั้นมีการส่ง parameter 4 ตัวเข้าไปใน executeScript. จะเห็นได้ว่าเราเอา arguments เหล่านั้นมาใช้ได้ง่ายๆด้วยการระบุ index เช่น arguments[1].

ตัวอย่างที่ 2: เก็บค่า performance entries มาเป็น List

public void testJsExecutorReturnList(){
    driver.get("https://www.blognone.com/"); 
    JavascriptExecutor jsExe = (JavascriptExecutor)driver; 
    String script = "return window.performance.getEntries();"; 
    List<Map<String, Object>> entries = (List<Map<String, Object>>)jsExe.executeScript(script); 
    for(int i = 0; i < entries.size(); i++) { 
        System.out.println("------- Entry: " + i + "--------"); 
        for(Map.Entry<String, Object> entry : entries.get(i).entrySet()) { 
            System.out.println(entry.getKey() + ":" + entry.getValue()); 
        } 
        System.out.println("------------------------"); 
    }
}

ตัวอย่างที่สองแสดงให้เห็นการส่ง Array จาก JavaScript กลับมาให้ Java. มีความยุ่งยากพอควร.

ตัวอย่างที่ 3: เก็บค่า performance entries มาเป็น JSON String

public void testJsExecutorReturnJsonString(){
    driver.get("https://www.blognone.com/"); 
    JavascriptExecutor jsExe = (JavascriptExecutor)driver; 
    String script = "return JSON.stringify(performance.getEntries());"; 
    String entriesString = (String)jsExe.executeScript(script); 
    System.out.println(entriesString);
}

ตัวอย่างที่สามแสดงการส่ง string จาก JavaScript กลับมาให้ Java.

 

executeAsyncScript(String script, … args)

method นี้แทบจะเหมือนกับ executeScript เลยฮะ. ต่างกันตรงที่ method นี้จะส่ง parameter ลับที่ชื่อว่า ‘callback’ เข้าไปให้ script ของเรา. เจ้า callback นี้ มีหน้าที่เป็นตัวบอก JavaScriptExecutor ว่า code ของเราทำงานเสร็จแล้วโดยสมบูรณ์.
เจ้า parameter callback นี้จะถูกส่งมาเป็นตัวสุดท้ายเสมอ.
ข้อควรระวังที่สุดในการใช้ method นี้คือ ต้องตั้งค่า scriptTimeout ก่อนเสมอ! เพราะว่า default scriptTimeout คือ 0 ms.

ตัวอย่างที่ 4: Get content size of an image (from cache)

public void getImageSize(){
    driver.get("https://www.blognone.com/"); 
    WebElement img = driver.findElement(By.cssSelector("img")); 
    String imgUrl = img.getAttribute("src").trim();
    String script = "var callback = arguments[arguments.length - 1];" +  
 "fetch(arguments[0],{cache:'force-cache'}).then((response)=> {" +
     "return response.blob(); }).then((blob)=>{" +
     " callback(blob.size); });";
    driver.manage().timeouts().setScriptTimeout(10, TimeUnit.SECONDS); 
    Object response = ((JavascriptExecutor) driver).executeAsyncScript(script, imgUrl);
    System.out.println(response); 
}

ตัวอย่างที่สี่แสดงการใช้ callback ในการบอกว่า JavaScript ทำงานเสร็จสมบูรณ์แล้ว. และอย่าลืมว่าต้องตั้งค่า scriptTimeout เสมอ.

 

ตัวอย่างที่ 5: Digest image content with SHA-256 algorithm.

 

public void getImageSHA256(){
    driver.get("https://www.blognone.com/");
    WebElement img = driver.findElement(By.cssSelector("img"));
    String imgUrl = img.getAttribute("src").trim();   
    String script = "function hex(buffer) { var hexCodes = [];  var view = new DataView(buffer);  for (var i = 0; i < view.byteLength; i += 4) { var value = view.getUint32(i); var stringValue = value.toString(16); var padding = '00000000'; var paddedValue = (padding + stringValue).slice(-padding.length); hexCodes.push(paddedValue);  }  return hexCodes.join(\"\");}" + "var callback = arguments[arguments.length - 1];" +  "fetch(arguments[0],{cache:'force-cache'}).then((response)=> {" +
        "return response.arrayBuffer(); }).then((buffer)=>{" + 
            " return crypto.subtle.digest('SHA-256', buffer); }).then((hashArray)=>{" +  
                " callback(hex(hashArray));"+ "});";   
    driver.manage().timeouts().setScriptTimeout(15, TimeUnit.SECONDS);
    Object response = ((JavascriptExecutor) driver).executeAsyncScript(script, imgUrl);   
    System.out.println(response); 
}

 

Leave a Reply

Your email address will not be published. Required fields are marked *