บทที่ 6: Gestures — Tap, Swipe, Scroll, Long Press¶
Pre-chapter Retrieval¶
ก่อนอ่านบทนี้ ลองตอบก่อน:
ทำไม
accessibility_idถึงดีกว่าxpathสำหรับ mobile locator? และเมื่อไหรถึงควรใช้ xpath?
ดูเฉลย
accessibility_id เร็วกว่า (ไม่ต้อง scan ทั้ง UI tree), stable กว่า (ไม่ขึ้นกับ UI structure), และ portable ระหว่าง Android/iOS — ใช้ xpath เป็น fallback เมื่อไม่มี accessibility_id หรือ resource-idวัตถุประสงค์¶
อ่านจบบทนี้แล้วคุณจะ:
- ใช้ Swipe, Scroll เพื่อ navigate ใน app ได้
- ใช้ Long Press และ Tap ได้
- Hide keyboard หลัง input ได้
- เข้าใจว่าทำไม gesture ใน mobile ถึงต่างจาก mouse event ใน web
ทำไมต้องรู้? (Why)¶
Mobile app ต้องการ interaction แบบ touch ที่ web ไม่มี
ใน web testing คุณแค่ click, type, scroll (ด้วย JavaScript execute) — แต่บน mobile มีอีกเยอะ: - Swipe — ปัดหน้าจอ (navigation, delete item, unlock) - Scroll — เลื่อนดู list หรือ page - Long Press — กดค้าง (menu, copy, drag) - Pinch/Zoom — ย่อ/ขยาย (maps, image)
ถ้าทำ gesture ไม่ได้ → ทดสอบ flow ที่ต้อง swipe หรือ scroll ไม่ได้เลย
Analogy: Gesture เหมือน Remote Control ที่ต่างกัน¶
ลองนึกถึง remote TV กับ remote PS5: - ปุ่มพื้นฐาน (ปิด/เปิด, volume) = เหมือนกัน → click, type ใน web และ mobile - PS5 มี touchpad, L2/R2 pressure-sensitive = เฉพาะ PS5 → swipe, long press ใน mobile
Web testing = remote TV (ปุ่มมาตรฐาน) Mobile testing = remote PS5 (มี features พิเศษที่ต้องเรียนใหม่)
⚠️ ถ้าเชื่อ analogy นี้ 100% จะเข้าใจผิดว่า: - Gesture เป็น "optional extra" — จริงๆ app หลายตัว require gesture สำหรับ flow หลัก (เช่น swipe to delete, pull to refresh) - Gesture ทำงานเหมือนกันทุก app — จริงๆ แต่ละ app ใช้ gesture library ต่างกัน บาง swipe ต้องปรับ coordinates หรือ speed
เนื้อหาหลัก¶
Swipe¶
Swipe ใช้สำหรับ: เลื่อนหน้าจอ, เปลี่ยนหน้า, ลบ item
# Swipe(start_x, start_y, end_x, end_y, duration_ms)
Swipe 500 1500 500 500 800
# ปัดจากกลางล่างขึ้นบน — scroll down (เนื้อหาขยับขึ้น)
ทิศทาง Swipe:
Swipe Up (scroll down): start_y สูง → end_y ต่ำ
Swipe Down (scroll up): start_y ต่ำ → end_y สูง
Swipe Left (next page): start_x ขวา → end_x ซ้าย
Swipe Right (prev page): start_x ซ้าย → end_x ขวา
ข้อควรระวัง: coordinates ขึ้นอยู่กับ resolution ของ device — ต้องปรับให้ตรงกับ emulator ที่ใช้
Scroll¶
Scroll element ที่ต้องการให้เข้ามาในหน้าจอ:
# Scroll ลงไปหา element
Scroll Element Into View accessibility_id=submit_button
# หรือ scroll ทั้งหน้า
Swipe 540 1600 540 400 1000 # scroll down
Long Press¶
Long press สำหรับ context menu หรือ drag:
Hide Keyboard¶
หลัง Input Text บน mobile มักต้องซ่อน keyboard เพื่อให้เห็น element อื่น:
Input Text accessibility_id=search_field test query
Hide Keyboard
# ตอนนี้ keyboard ซ่อนแล้ว สามารถ click element อื่นได้
Click Element accessibility_id=search_button
Tap ด้วย Coordinates¶
เมื่อ element หาด้วย locator ไม่ได้ ใช้ tap ตำแหน่ง:
ตัวอย่าง 3 ระดับ¶
Beginner: Scroll หา element ที่อยู่นอกหน้าจอ¶
# tested: Robot Framework 7.x, AppiumLibrary 3.0.0
# requires: emulator รันอยู่, Appium server รันอยู่
*** Settings ***
Library AppiumLibrary
*** Keywords ***
Scroll Down Once
Swipe 540 1400 540 600 800
*** Test Cases ***
หา Terms & Conditions ที่อยู่ด้านล่างสุด
[Setup] Open Application http://localhost:4723
... platformName=Android
... appium:automationName=UIAutomator2
... appium:deviceName=emulator-5554
... appium:appPackage=com.example.app
... appium:appActivity=.RegisterActivity
Wait Until Element Is Visible accessibility_id=register_form 10s
# Scroll ลงหา checkbox
Scroll Down Once
Scroll Down Once
Wait Until Element Is Visible accessibility_id=terms_checkbox 5s
Click Element accessibility_id=terms_checkbox
[Teardown] Close Application
Intermediate: Swipe เพื่อ delete item ใน list¶
Banking app มักมี pattern "swipe left to delete" สำหรับ saved recipients:
# tested: Robot Framework 7.x, AppiumLibrary 3.0.0
# requires: app ที่มี swipeable list item
*** Settings ***
Library AppiumLibrary
*** Keywords ***
Swipe Left On Element
[Arguments] ${locator}
${element}= Get WebElement ${locator}
${location}= Get Element Location ${element}
${size}= Get Element Size ${element}
${start_x}= Evaluate ${location}[x] + ${size}[width] - 10
${start_y}= Evaluate ${location}[y] + ${size}[height] / 2
${end_x}= Evaluate ${location}[x] + 10
Swipe ${start_x} ${start_y} ${end_x} ${start_y} 500
*** Test Cases ***
ลบ Saved Recipient ด้วย Swipe
[Setup] Open Application http://localhost:4723
... platformName=Android
... appium:automationName=UIAutomator2
... appium:deviceName=emulator-5554
... appium:appPackage=com.example.app
... appium:appActivity=.SavedRecipientsActivity
Wait Until Element Is Visible accessibility_id=recipient_john 10s
Swipe Left On Element accessibility_id=recipient_john
Wait Until Element Is Visible accessibility_id=delete_button 5s
Click Element accessibility_id=delete_button
Page Should Not Contain Element accessibility_id=recipient_john
[Teardown] Close Application
Advanced: Pull to Refresh pattern¶
Banking app มักใช้ pull-to-refresh เพื่ออัปเดตยอดเงิน:
# tested: Robot Framework 7.x, AppiumLibrary 3.0.0
# requires: app ที่มี pull-to-refresh feature
*** Settings ***
Library AppiumLibrary
*** Keywords ***
Pull To Refresh
# ดึงจากบนลงล่าง เริ่มที่ y ต่ำ (ใกล้บน) ไปยัง y สูง (ลงล่าง)
Swipe 540 300 540 900 1000
# รอ loading เสร็จ
Run Keyword And Ignore Error
... Wait Until Element Is Not Visible accessibility_id=loading_indicator 15s
ตรวจสอบยอดเงินอัปเดตหลัง Refresh
[Setup] Open Application http://localhost:4723
... platformName=Android
... appium:automationName=UIAutomator2
... appium:deviceName=emulator-5554
... appium:appPackage=com.example.app
... appium:appActivity=.DashboardActivity
Wait Until Element Is Visible accessibility_id=balance_display 15s
${balance_before}= Get Text accessibility_id=balance_display
Pull To Refresh
Wait Until Element Is Visible accessibility_id=balance_display 15s
${balance_after}= Get Text accessibility_id=balance_display
# ยอดเงินอาจเหมือนกัน (ไม่มี transaction ใหม่) แต่ refresh ต้องทำงาน
Log Balance before: ${balance_before}, after: ${balance_after}
[Teardown] Close Application
Common Mistakes¶
❌ ใช้ Swipe coordinates คงที่โดยไม่คำนึง device resolution → Test pass บน emulator หนึ่งแต่ fail บน emulator อื่น ✅ คำนวณ coordinates แบบ dynamic จาก screen size หรือ element location
${size}= Get Window Size
${width}= Set Variable ${size}[width]
${height}= Set Variable ${size}[height]
# ใช้ percentage ของ screen size แทน hardcode
Swipe ${width/2} ${height*0.8} ${width/2} ${height*0.2} 800
❌ ลืม Hide Keyboard หลัง Input Text
→ Keyboard บัง element ที่ต้องการ click → Element not visible error
✅ เพิ่ม Hide Keyboard หลัง input เสมอ ก่อน interact กับ element อื่น
(source: AppiumLibrary docs)
❌ Swipe เร็วเกินไป (duration สั้นเกินไป) → App รับ gesture เป็น tap แทน swipe ✅ ปรับ duration ให้เหมาะสม — ลองเริ่มที่ 800-1000ms (source: ประสบการณ์ Appium community)
สรุปบท¶
ลองตอบก่อนดูเฉลย:
คำถาม 1:
Swipe 540 1400 540 600 800— ปัดทิศทางไหน? และ parameter สุดท้าย800หมายความว่าอะไร?คำถาม 2: ทำไมต้อง
Hide KeyboardหลังInput Text? จะเกิดอะไรถ้าไม่ทำ?คำถาม 3: ทำไม gesture coordinates ที่ hardcode ถึงเป็นปัญหาเมื่อรัน test บน device หลายรุ่น?
ดูเฉลย
**เฉลย 1:** ปัดจาก y=1400 (ด้านล่าง) ขึ้นไป y=600 (ด้านบน) = swipe up / scroll down เพื่อดูเนื้อหาที่อยู่ด้านล่าง — parameter สุดท้าย `800` = duration 800ms (ความเร็วในการปัด) **เฉลย 2:** keyboard บัง element อื่นบนหน้าจอ — ถ้าไม่ซ่อน keyboard การ click element ที่ถูก keyboard บังจะ fail เพราะ element not visible หรือ not interactable **เฉลย 3:** coordinates เป็น pixel absolute — device ที่ resolution ต่างกัน (เช่น 1080x2400 vs 1440x3040) จะมีพื้นที่หน้าจอต่างกัน coordinates เดียวกันอาจชนกับ element คนละตัวหรือ out-of-boundsบทต่อไป: บทที่ 7 — Page Object Model