Play with selenium

8 minute read

Published:

Introduction

ronaldo

Theo như bài báo mới nhất sáng nay của một hãng truyền thông uy tín hàng đầu nước Mỹ, thì Cristiano Ronaldo cùng rất nhiều người nổi tiếng khác trên khắp thế giới đang rất cần một mạng xã hội chuyên biệt để tương tác với người hâm mộ của họ thay vì dùng Facebook như hiện nay. Do nắm bắt được xu hướng đó, công ty mình đã tranh thủ giờ nghỉ trưa tạo ra một bản demo mạng xã hội mới với đầy đủ mọi tính năng cần thiết. Và thật bất ngờ là chỉ sau 5 phút mời chào đã có 1000 celebrities đồng ý dùng thử, nhưng vì họ quá bận rộn nên đã yêu cầu mình tạo giùm tài khoản đăng nhập với avatarcover được sao chép từ tài khoản twitter.

Tất nhiên là có thể làm việc này thủ công nhưng điều đó tốn khá nhiều thời gian và có thể sẽ khiến những vị thương đế quyền năng của chúng ta đổi ý, thế nên chúng ta sẽ cần một cách tiếp tận nhanh hơn, hiệu quả hơn, ví dụ như tự động login vào Twitter rồi chuyển hướng đến trang cá nhân của Ronaldo, sau đó crawl avatarcover về rồi upload lại lên dịch vụ mới của chúng ta. Cách tiếp cận mới này sẽ được thực hiện bằng Selenium (cụ thể là Selenium Webdriver), một công cụ rất hay mà mình sẽ giới thiệu với các bạn trong phạm vi bài viết này.

Basic

  • Selenium (Selenium RC)

    A testing framework invented by Jason Huggins

  • WebDriver

    WebDriver is a remote control interface invented by Simon Stewart that enables introspection and control of user agents[1]. It provides a platform- and language-neutral wire protocol as a way for out-of-process programs to remotely instruct the behavior of web browsers.

    In computing, a user agent is any software, acting on behalf of a user, which “retrieves, renders and facilitates end-user interaction with Web content.

  • Selenium WebDriver

    Selenium WebDriver = Selenium + WebDriver[2][3]

    Đại khái là nó trông như thế này

    ronaldo

    Và đây là cách WebDriver hoạt động

    ronaldo

  • Architecture
    • Selenium Client library
      • Đây là thư viện cung cấp cho bạn những câu lệnh và function cần thiết để gửi câu lệnh đến WebDriver ví dụ như click chuột, nhấn phím, chuyển hướng…
      • Nếu bạn sử dụng Gradle thì bạn có thể tải thư viện về bằng cách thêm đoạn code sau đây vào tập tin build.gradle.
         // https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java
         implementation group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.141.59'
        
      • Còn nếu bạn chỉ sử dụng Java thuần túy thì nhớ tải thư viện về và đặt nó trong đường dẫn classpath nhé.
    • JSON Wire Protocol
      • Đây là một cơ chế trao đổi dữ liệu giữa client library (Java library) và webdriver(Chrome Driver). Client sẽ gửi đi HTTP request bao gồm serialized data với content type là application/json và kết quả trả về cũng được deserialization thành JSON tương ứng[4].
    • Browser Driver
      • Cái này dùng để giao tiếp với browser thật (Chrome), nó nhận request từ JSON Wire Protocol thông qua HTTP Server rồi xử lý, và tiếp tục gửi request đã được xử lý đến browser (thông qua browser’s native support) cũng như trả lại response từ browser về cho client.
      • Trước đây với Selenium RC(Selenium Remote Control), nếu bạn muốn điều khiển browser thì Selenium RC sẽ phải chèn thêm code Javascript vào browser nhưng cách này tỏ ra không hiệu quả và hiện đã bị thay thế bằng WebDriver[5]. Các loại WebDriver khác nhau như ChromeDriver, GeckoDriver được duy trì và phát triển bởi những nhà phát hành trình duyệt là GoogleMozilla cho phép bạn tương tác với browser hiệu quả hơn nhiều so với cách tiếp cận trước đó nên trong bài viết này mình sẽ sử dụng ChromeDriver làm mẫu.
    • Real Browser
      • Browser nhận request từ WebDriver rồi thực thi. Cửa sổ trình duyệt sẽ bật lên (hoặc chạy ở headless mode) và thực hiện mọi thao tác được bạn chỉ định như đi đến site X, click vào button Y…
    • Bonus thêm cái ảnh cho dễ hình dung[6]

    ronaldo

Prerequisites

  • Để sử dụng được Selenium cần cài đặt tương đối nhiều package như mình có liệt kê ở script dưới đây. Các bạn chỉ cần copy vào terminal là chạy được (Mình đã chạy thành công trên Ubuntu 20.04).

      # Install dependencies.
        
      sudo apt update
      sudo apt install -y unzip                    
        
      # https://www.baeldung.com/java-headless-mode
      sudo apt install -y openjdk-8-jre-headless 
          
      # X virtual framebuffer is an in-memory display server for UNIX-like operating system that enables you to run graphical applications without a display.
      # It allow chrome web driver run in headless mode.
      sudo apt install -y xvfb 
        
      # Allow chrome webdriver control input device like mouse and keyboard
      sudo apt install -y libxi6    
      # Allow chrome webdriver set it own configure
      sudo apt install -y libgconf-2-4
        
      # json and xml processor tools
      sudo apt install python3-pip jq
      pip3 install xq
        
      LATEST_CHROME_DRIVER_VERSION=`curl -sS https://chromedriver.storage.googleapis.com/LATEST_RELEASE`
      LATEST_SELENIUM_STANDALONE_JAR=`curl https://selenium-release.storage.googleapis.com | xq | jq `last(.ListBucketResult | .Contents | .[] | .Key | select(test(".*selenium-server-standalone.*.jar")))`
        
      # install Google Chrome
      wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
      sudo dpkg -i google-chrome-stable_current_amd64.deb
        
      # Install ChromeDriver.
      wget -N https://chromedriver.storage.googleapis.com/$LATEST_CHROME_DRIVER_VERSION/chromedriver_linux64.zip -P ~/
      unzip ~/chromedriver_linux64.zip -d ~/
      rm ~/chromedriver_linux64.zip
      sudo mv -f ~/chromedriver /usr/local/bin/chromedriver
      sudo chown root:root /usr/local/bin/chromedriver
      sudo chmod 0755 /usr/local/bin/chromedriver
        
      # Install Selenium server (not required on local machine)
      wget -N https://selenium-release.storage.googleapis.com/$LATEST_SELENIUM_STANDALONE_JAR -P ~/
      sudo mv -f ~/selenium-server-standalone-$SELENIUM_STANDALONE_VERSION.jar /usr/local/bin/selenium-server-standalone.jar
      sudo chown root:root /usr/local/bin/selenium-server-standalone.jar
      sudo chmod 0755 /usr/local/bin/selenium-server-standalone.jar
    
  • Có câu lệnh nào bạn không hiểu thì đặt câu hỏi cho mình ở dưới nhé.

Automation

Bây giờ là lúc dùng những kiến thức đã học để giải quyết bài toán được đưa ra ở đầu bài viết sử dụng JavaSelenium.

  • Việc đầu tiên cần làm là khởi tạo ChromeDriver, trái tim của Selenium.
      // Phải chỉ ra đường dẫn đến tập tin thực thi của chromedriver nha, không là chẳng biết ở đâu mà tìm luôn đấy.
      System.setProperty("webdriver.chrome.driver", "/usr/bin/chromedriver");
      ChromeOptions chromeOptions = new ChromeOptions();
        
      // mở trình duyệt
      chromeOptions.addArguments("--start-maximized");
      // chạy ngầm
      chromeOptions.addArguments("--headless");
        
      WebDriver driver = new ChromeDriver(chromeOptions);
    
  • Tiếp theo thì để crawl được avatar và background của các celebrities thì cần đăng nhập vào Twitter trước đúng không?

    selenium.gif

    Để nhập được username, password cũng như click vào button Log In thì bạn cần cho webdriver biết vị trí của những thành phần này trên trình duyệt. Và thật may mắn là thư viện selenium-java của chúng ta đã hỗ trợ điều này rất tốt với hàng loạt phương thức khác nhau như sử dụng CSS selector, Xpath, Attribute Name… Thế nên là cách nào dễ thì chúng ta làm cách đó.

    ronaldo

      String twitterUserNamePath = "session[username_or_email]";
      String twitterPasswordPath = "session[password]";
      String twitterLoginPath = "/html/body/div/div/div/div[2]/main/div/div/div[2]/form/div/div[3]/div";
        
      driver.get("http://www.twitter.com/login");
      
      // Nghỉ ngơi tí đợi website load không thì ăn ngay quả bug NO SUCH ELEMENT.
      TimeUnit.SECONDS.sleep(1);
      
      driver.findElement(By.name(twitterUserNamePath)).sendKeys("username");
      driver.findElement(By.name(twitterPasswordPath)).sendKeys("password");
      driver.findElement(By.xpath(twitterLoginPath)).click();
      
      TimeUnit.SECONDS.sleep(2);
    

    Ở đoạn code phía trên mình chọn sử dụng attribute's name cho đỡ tốn không gian nhưng đối với những tag không có name thì bạn có thể cân nhắc những lựa chọn sau đây.

    ronaldo

  • Và tiếp tục với cách thức như trên thì cuối cùng chúng ta thu được kết quả này

    selenium.gif

    Video xem ở đây

Final note

  • Trông có vẻ đơn giản tuy nhiên luôn có những bài toán rắc rối hơn mà có thể bạn sẽ gặp phải như sau:
    • Có image upload mất 10s, có image upload chỉ mất 1s, làm sao để vừa tối ưu thời gian vừa đảm bảo image đã được upload thành công?
      • Thật may mắn là thư viện có hỗ trợ các phương thức như là waitElementToBeActionable().
    • Đối với những element bị ẩn (display:none) thì webdriver sẽ ném ra ngoại lệ No such element, vậy làm thế nào để webdriver có thể focus vào element đó?
      • Đáp án là bạn cần phải chèn thêm script để làm cho element đó visible thì mới có thể thao tác tiếp được.
    • Cuối cùng là sau khi xong xuôi hết mọi thứ thì nhớ tắt webdriver đi nhé, không thì phải tải ram trên mạng xuống để dùng đó.
        driver.quit();
      
  • Source code

Reference

  1. https://www.w3.org/TR/webdriver/
  2. https://www.quora.com/What-is-the-difference-between-Selenium-RC-and-Selenium-web-driver
  3. https://www.scientecheasy.com/2020/07/selenium-webdriver-architecture.html
  4. https://stackoverflow.com/questions/54382080/what-is-the-difference-between-protocol-and-json-wire-protocol
  5. https://www.h2kinfosys.com/blog/difference-selenium-rc-webdriver
  6. https://www.quora.com/How-does-the-Selenium-WebDriver-work/answer/Alex-Siminiuc-2

Terminology

  1. https://medium.com/swlh/lets-take-a-look-at-selenium-d8640539ee23#619d
  2. https://www.scientecheasy.com/2020/07/selenium-webdriver-architecture.html/

Thank for reading :blush:.