Parallel Testing on Android/iOS devices with Selenium Grid

Mild Atta
Arcadia Software Development
5 min readMay 20, 2020

โดยปกติแล้ว robot framework รันการทดสอบแบบ parallel นั้นจะทำการรันบนอุปกรณ์เพียงชิ้นเดียว แล้วถ้าหากมีอุปกรณ์ที่จะใช้ทำการทดสอบมากกว่า 1 ชิ้น จะสามารถทำอย่างไรได้บ้าง? การใช้ Selenium Grid คือคำตอบครับ

Selenium Grid

ด้วยความสามารถของ Selenium Grid ทำให้สามารถที่จะรันหลาย ๆ การทดสอบบนหลายอุปกรณ์ หลายเบราว์เซอร์ หลายระบบปฏิบัติการได้พร้อม ๆ กัน ตัวอย่างเช่น มี test case จำนวน 100 test cases ซึ่งปกติแล้วถ้าสั่งรันการทดสอบแบบ parallel ก็จะทำอยู่บนอุปกรณ์เพียงเครื่องเดียว แต่ถ้าหากใช้ Selenium-Grid ในการ set up อุปกรณ์ที่จะใช้ในการทดสอบขึ้นมาจำนวน 4 เครื่อง นั่นหมายความว่า test case จะถูกแบ่งส่งไปทดสอบบนอุปกรณ์ทั้ง 4 เครื่องพร้อมๆกัน

Credit: https://www.browserstack.com/guide/selenium-grid-tutorial

Parallel testing on multiple devices

ก่อนอื่นโจทย์ในการทดสอบครั้งนี้ คือ ทำการลองรันการทดสอบบนอุปกรณ์ 2 เครื่อง ต่างระบบปฏิบัติการ โดย test case จะต้องถูกส่งไปทดสอบบนอุปกรณ์ที่ว่าง ซึ่งหมายความว่าสคริปต์ของแต่ละ test case จะต้องสามารถรันได้ทั้ง 2 ระบบปฏิบัติการ แล้วจะต้องเตรียมอะไรบ้าง?

  1. อุปกรณ์ที่จะใช้ในการทดสอบ ในบทความนี้จะใช้อุปกรณ์ 2 เครื่อง คือ Samsung Galaxy A5 จำนวน 1 เครื่อง และ iPhone 5c จำนวน 1 เครื่อง
  2. Selenium Grid
  3. Robot framework
  4. Appium
  5. Resource ไฟล์ที่เก็บข้อมูล capabilities บางอย่างเพื่อทำหน้าที่ในการจอง value set สำหรับการ execute

ในขั้นตอนของการเตรียมการทำการทดสอบมี ดังนี้

เริ่มจากการ set up Selenium Grid ทั้ง 2 roles คือ hub และ node ซึ่งขั้นตอนนี้จำเป็นต้องใช้ Selenium Standalone Server ซึ่งดาวน์โหลดได้ ที่นี่ เมื่อดาวน์โหลดเสร็จแล้ว ให้ทำการเริ่มต้นเปิดใช้งาน hub โดยใช้คำสั่ง

/Path/Where File Was Downloaded> java -jar selenium-server-standalone-<put your download version here>.jar -role hub

ซึ่งโดยปกติแล้ว default port ของ hub จะเป็น 4444 แต่หากต้องการใช้ port อื่น เพียงแค่ระบุ port ที่ต้องการใช้ลงไปในคำสั่ง start hub ดังนี้

/Path/Where File Was Downloaded> java -jar selenium-server-standalone-<put your download version here>.jar -role hub -port <port number>

เมื่อสั่งรันคำสั่งแล้ว ควรจะขึ้นข้อความแบบนี้

[Hub.start] - Nodes should register to http://<grid-ip-adress>:<grid-port>/grid/register/

เมื่อทำการ start hub เป็นที่เรียบร้อยแล้ว ก็มาต่อกันด้วยการ start node ซึ่งในบทความนี้จะใช้ 2 nodes เพราะมี 2 อุปกรณ์ แต่การ start node จำเป็นจะต้องสร้างไฟล์ config.json เพื่อใช้ในการเชื่อมต่อกับ hub โดย 1 node ใช้ไฟล์ config 1 ไฟล์ ดังนั้น เมื่อต้องการ set 2 nodes ก็ต้องสร้างขึ้นมา 2 ไฟล์ ซึ่งไฟล์ config จะมีหน้าตาราว ๆ นี้ครับ

{
"capabilities":
[
{
"browserName": "<e.g._iPhone5_or_iPad4>",
"version":"<version_of_iOS_e.g._7.1>",
"maxInstances": 1,
"platform":"<platform_e.g._MAC_or_ANDROID>"
}
],
"configuration":
{
"cleanUpCycle":2000,
"timeout":30000,
"proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
"url":"http://<host_name_appium_server_or_ip-address_appium_server>:<appium_port>/wd/hub",
"host": <host_name_appium_server_or_ip-address_appium_server>,
"port": <appium_port>,
"maxSession": 1,
"register": true,
"registerCycle": 5000,
"hubPort": <grid_port>,
"hubHost": "<Grid_host_name_or_grid_ip-address>"
"hubProtocol": "<Protocol_of_Grid_defaults_to_http>"
}
}
  • *Note* ในไฟล์ config นั้นจำเป็นจะต้องระบุ browserName, version และ platform เพื่อที่ grid จะทำการ redirect ไปยัง device ได้ถูกต้อง ซึ่งหากเป็นการทดสอบแอปพลิเคชัน ตัว browserName ไม่จำเป็นต้องระบุเบราว์เซอร์จริง ๆ ให้ระบุเป็นชื่อรุ่น (จากที่ลองตั้งไม่ตรงกับชื่อรุ่น จริง ๆ ก็สามารถตั้งได้)
  • **Note** สามารถนำแพทเทิร์นนี้ ไปปรับค่าของ parameters ต่าง ๆ ให้ตรงกับ device, hub, node ที่ต้องการจะสร้างได้เลย หรือจะเพิ่ม parameters อื่น ๆ ก็ได้

เมื่อสร้างไฟล์เสร็จแล้ว จะมา start node กัน โดยในบทความนี้จะใช้ Appium Desktop ในการ start

Appium server configurations

โดยระบุค่าดังนี้

  • Server Address: ระบุ IP ของ Appium Server
  • Server Port: ระบุ Port ของ Appium ที่ต้องการใช้ (default port จะเป็น 4724)
  • Node Config File Path: ระบุ path ของไฟล์ config.json ที่สร้างขึ้นมา

แน่นอนว่าถ้าต้องการ set node จำนวน 2 nodes ก็จำเป็นต้องเปิด Appium Server 2 ตัว ดังภาพตัวอย่างต่อไปนี้

Appium Server Example #1
Appium Server Example #2

เมื่อกด start server แล้วขึ้นข้อความนี้ก็แสดงว่าสามารถที่จะเชื่อมตัว Appium server (Node) เข้ากับ Hub ได้แล้ว

[Appium] Appium successfully registered with the grid on http://<grid-ip-adress>:<grid-port>

เพื่อความมั่นใจว่า node ได้ต่อเข้ากับ hub จริง ๆ แล้วก็สามารถเข้าไปเช็คได้ผ่าน

http://<grid-ip-adress>:<grid-port>/grid/console
Grid Console UI Example

มาต่อกันที่ไฟล์ resource ซึ่งไฟล์นี้จะเอาไว้เก็บค่า capabilities บางส่วนของอุปกรณ์ ซึ่งในที่นี้จะเก็บไว้เพื่อตรวจเช็คสถานะในการใช้อุปกรณ์ว่าอุปกรณ์ถูกเรียกใช้โดย executor process อื่นอยู่หรือไม่ โดยใช้ร่วมกับ pabot.PabotLib library ในการล็อคค่าของ value set

มาสร้างไฟล์ resource กัน ซึ่งจะเซฟเก็บเป็นไฟล์ .txt, .dat เป็นต้น

** ดูรายละเอียดเพิ่มเติมได้ ที่นี่**

จากนั้นมาต่อกันที่การเตรียม test case ที่จะใช้ทดสอบกัน โดยจะยกตัวอย่างการเปิดแอปพลิเคชันข้อความ โดยของ iPhone 5c จะให้เปิดแอปพลิเคชันข้อความของเครื่องขึ้นมานะครับ ส่วนของ Samsung Galaxy A5 จะให้เรียกใช้แอปพลิเคชัน Mood

ในส่วนของ Test Case จะตั้งชื่อว่า ‘Open Message App’

*** Test Cases ***
Open Message App

Acquire Value Set
${platformVersion}= Get Value From Set platformVersion
${platformName}= Get Value From Set platformName
${automationName}= Get Value From Set automationName
${udid}= Get Value From Set udid
Log Next is send these values to the test case
Run Keyword If '${platformName}' == 'Android' msg_android ${platformVersion} ${platformName} ${automationName} ${udid}
... ELSE IF '${platformName}' == 'iOS' msg_ios ${platformVersion} ${platformName} ${automationName}
... ${udid}
Release Value Set
[Teardown] Close Application

ซึ่งเมื่อ test case ถูกรันขึ้นมา จะต้องเรียกใช้ keyword ของ pabot.PabotLib เพื่อที่จะทำการจอง value set สำหรับใช้ในการ execute และทำให้ process อื่นที่จะเข้ามาใช้ value set นั้นต่อ จะไม่สามารถเข้าถึงได้ โดยใช้ keyword ดังนี้

Acquire Value Set

หลังจากนั้นทำการ get value set จากไฟล์ resource มาเก็บใส่ตัวแปรเพื่อนำไปใช้ต่อในการเปิดแอปพลิเคชัน

${platformVersion}=    Get Value From Set    platformVersion
${platformName}= Get Value From Set platformName
${automationName}= Get Value From Set automationName
${udid}= Get Value From Set udid

เมื่อได้ค่ามาใส่ตัวแปรครบแล้ว ก็ให้ทำตรวจเช็คว่า value set ที่รับเข้ามานั้นเป็นของ Android หรือ iOS โดยจะเช็คจาก platformName ถ้าตรงกับ platform ใดก็ให้เรียกใช้ keyword ในการเปิดแอปพลิเคชันของ platform นั้น ๆ ที่สร้างไว้

Run Keyword If    '${platformName}' == 'Android'    msg_android    ${platformVersion}    ${platformName}    ${automationName}    ${udid}
... ELSE IF '${platformName}' == 'iOS' msg_ios ${platformVersion} ${platformName} ${automationName}
... ${udid}

เมื่อสามารถเรียกการทำงานของ keyword ที่สร้างขึ้นมาเพื่อทดสอบได้แล้ว หลังจากเสร็จสิ้นก็ให้ใช้คำสั่งเพื่อทำการปล่อยการจอง value set เพื่อให้ process อื่นเข้ามาใช้งานได้ โดยใช้ keyword ดังนี้

Release Value Set

เมื่อจบจากส่วนของ Test Case มาแล้วก็มาดูในส่วนของการสร้าง keyword เพื่อเรียกใช้แอปพลิเคชันของแต่ละระบบปฏิบัติการกัน ซึ่งในแต่ละ keyword ให้ทำการรับ arguments ที่ get มาจาก value set มาใช้ (ซึ่งจริง ๆ แล้วจะเขียนกำหนดเองไม่ผ่านการรับจากทาง argument ก็ได้) เพื่อกำหนด Desired Capabilities สำหรับการเปิดแอปพลิเคชันตามปกติ

จุดสำคัญคือ remote_url จากปกติที่ให้เข้าไปที่ Appium Server โดยตรง ก็เปลี่ยนเป็นให้เข้าไปที่ Grid Hub แทน เพื่อที่ Hub จะได้จัดการในการส่ง request ที่รับเข้ามาไปยัง Node (Appium Server) เอง

*** Keywords ***
msg_ios
[Arguments] ${platformVersion} ${platformName} ${automationName} ${udid}
Open Application remote_url=http://127.0.0.1:4444/wd/hub platformVersion=${platformVersion} platformName=${platformName} automationName=${automationName} udid=${udid} newCommandTimeout=3600
... useNewWDA=false noReset=true bundleId=com.apple.MobileSMS deviceName=iPhone5c
Wait Until Element Is Visible id=AIS
msg_android
[Arguments] ${platformVersion} ${platformName} ${automationName} ${udid}
Open Application remote_url=http://127.0.0.1:4444/wd/hub platformVersion=${platformVersion} platformName=${platformName} automationName=${automationName} udid=${udid} noReset=true
... appPackage=com.calea.echo appActivity=com.calea.echo.MainActivity deviceName=Samsung Galaxy A5

เมื่อเตรียมทุกอย่างเรียบร้อยแล้ว ก็ลองมาลองรันการทดสอบจริง ๆ กัน โดยในการทดสอบครั้งนี้จะใช้ 4 test cases โดยเป็นการเปิดแอปพลิเคชันข้อความ และเปิดแอปพลิเคชัน myAIS อย่างละ 2 test cases โดยใช้คำสั่ง

pabot --testlevelsplit --pabotlib --resourcefile deviceResources.txt parallel_test_on_2_platforms.robot

ผลลัพธ์ที่ได้ คือ Samsung Galaxy A5 และ iPhone 5c ได้รันการทดสอบไปเครื่องละ 2 test case แบ่งเป็น เปิดแอปพลิเคชัน myAIS 1 test case และเปิดแอปพลิเคชันข้อความ 1 test case ลองมาดูรายละเอียดใน log file กันหน่อย

Log File

จากภาพจะสังเกตเห็นได้ว่า Open Message App และ open myAIS เรียกจอง value set ของ Android ด้วยทั้งคู่ โดย open myAIS นั้นเริ่มทำการจองก่อนในเวลาเล็กน้อย (0.001 ms) (กรอบสีเหลือง) ซึ่งก็หมายความว่ามีสิทธิ์ในการเข้าถึง value set ‘Android’ ก่อน ใช้เวลาในการทำเพียง 0.009 ms เท่านั้น

ซึ่งเมื่อย้อนกลับไปดู Open Message App จะเห็นว่าใช้เวลาถึง 29.931 s ทำไมถึงใช้เวลานานกว่าอีก test case ก็เพราะว่าอีก test case ได้สิทธิ์ในการเข้าถึง value set ทำให้ต้องรอจนกว่าจะปล่อยการจองออกมา

เมื่อดูเวลาที่ open myAIS ใช้ตั้งแต่ Acquire จนกระทั่ง Release value set นั้นใช้เวลาไปประมาณ 29.912 s ก็เรียกได้ว่าเมื่อปล่อยการจองปุ๊บ Open Massage App ก็แทบจะเข้ามาทำจองต่อทันทีและเสร็จสิ้นภายในเวลา 0.0019 ms เท่านั้น

ซึ่งฝั่งของ iPhone 5c ได้ทำการรัน Open Message App (2) และ open myAIS (2) ก็เป็นไปในลักษณะเดียวกัน

อย่างไรก็ตามไม่ได้หมายความว่า test case จะถูกแบ่งไปทดสอบในแต่ละอุปกรณ์อย่างเท่า ๆ กัน ทั้งนี้ขึ้นอยู่กับความเร็วในการทดสอบของอุปกรณ์แต่ละตัว อย่างตัวอย่างที่ยกมาในบทความทั้ง 2 อุปกรณ์ได้รับ test case ไปทดสอบจำนวนเท่ากันที่ 2 test case ก็จริง แต่ก็มีบางครั้งที่ลองรันการทดสอบแล้ว iPhone 5c ทำการเรียก wda ขึ้นมาช้า ทำให้รันการทดสอบได้เพียง test case เดียว และ Galaxy A5 ก็เลยได้รันการทดสอบไป 3 test cases เป็นต้น

โดยสรุปก็คือ Selenium Grid จะเข้ามาช่วยจัดการเพื่อให้สามารถทำ parallel executing บนหลายอุปกรณ์ได้ ไม่จำกัดว่าจะต้องเป็นอุปกรณ์เดียวกัน หรือระบบปฏิบัติการเดียวกัน ทำให้สามารถลดระยะเวลาในการที่จะต้องมานั่งรันการทดสอบไปทีละเครื่องลงไปได้

หมายเหตุ

Test script ที่ยกตัวอย่างไปนั้น เพื่อใช้ในการทดลองรัน parallel Android/iOS โดยใช้ test case ข้อเดียวกัน

--

--