แนะนำ Best Practices ในการ Find Elements ใน Cypress ด้วย Cypress Testing Library

Traitanit Huangsri
Cypress.io Thailand
4 min readJun 21, 2020

สวัสดีครับ หลายคนที่เขียน Test Web App ของตัวเองด้วย Cypress น่าจะเคยเจอปัญหาในการ Find Element มาเพื่อทำ Action หรือ Assertion กันมากมายใช่มั้ยครับ จากที่ผมได้อ่านคำถามข้อสงสัยของหลายๆ คนใน Cypress.io Thailand Community ของเรา มักจะเจอปัญหาซ้ำๆ คล้ายๆ กัน เช่นจะค้นหา Element ยังไงดีให้ไม่เกิด Flaky Tests ได้ง่าย หรือบางครั้งลองใส่ค่า Locator ต่างๆ แล้วก็ยังหา Element ไม่เจออยู่ดี วันนี้ผมเลยอยากจะขอแนะนำ Best Practices ที่ทุกคนควรพิจารณานำไปปรับใช้กับตัวเองด้วยการใช้ Library เสริมอย่าง Cypress Testing Library กันครับ

ปัญหาของการใช้ CSS Selector แบบเดิมๆ

ปกติแล้วเราสามารถใช้คำสั่ง cy.get(<selector>) ในการค้นหา Dom Elements มาเพื่อทำเทสบางอย่าง เช่น cy.get(‘.list > .li’) ซึ่งปกติหลายๆ คนก็มักจะนิยมใช้ CSS Selector ในการค้นหา Elements กันใช่มั้ยล่ะครับ แต่จากประสบการณ์ของผมพบว่า CSS Selector บางแบบนั้นไม่เหมาะกับการนำมาใช้ทำเทส เช่น

  1. Class Selector: เช่น <div class=”myclass textBold”> เนื่องจากว่าในปัจจุบัน Web Technology มีการใช้ CSS Class ในการสร้าง UI ในหลากหลายรูปแบบ Class Selector นั้นมีโอกาสที่จะเปลี่ยนแปลงค่าได้ตลอดเวลาตามการใช้งานของ User เช่นเมื่อ User ทำ mousover ที่ web element ตัว Web App ก็อาจจะมี event listener ทำการเปลี่ยนแปลงค่า CSS class (เช่นลบ css “myclass” ออก) ณ ช่วงเวลาตอน Runtime ได้ จึงเป็นเหตุผลที่ว่า Class Selector ไม่เหมาะกับการนำมาใช้เขียนเทสครับ เพราะมัน Dynamic มาก
  2. การใช้ Selector ในลักษณะที่เป็น Hierarchy เช่น XPath ซึ่งถ้าใครที่เคยเขียนเว็บโดยเฉพาะถ้าใช้ Web Technology ใหม่ๆ คงจะเข้าใจเป็นอย่างดีว่าโครงสร้างของ DOM นั้นมีโอกาสเปลี่ยนแปลงและมีการ Re-render อยู่ตลอดเวลาตามการใช้งานของ User ยกตัวอย่างเช่น
<div class="form">
<div class="profile">
<div class="username">
<label>Username</label>
<input type="text" name="username" />
</div>
<div class="password">
<label>Password</label>
<input type="password" name="password" />
</div>
</div>

สมมติว่าเราต้องการพิม Password เข้าไปใน password input field ผมเคยเห็นหลายคนก็จะใช้ Developer Tools ในการหา password input field แล้วก็ทำการ copy XPath ออกมาได้เป็นค่า XPath อันนึงยาวๆ เช่น div > div > div[1] > label > input ลองนึกภาพดูนะครับว่าถ้าวันนึง label เกิดมีการเปลี่ยน tag เป็น span แทน CSS Selector นี้ก็จะใช้งานไม่ได้ (ทำให้หา Element ไม่เจอ) และทำให้เกิด Flaky Tests ทันที แล้วเราก็ต้องเสียเวลามาคอย update selector ที่ก็บอกได้เลยว่ามันไม่มีทาง stable ได้อีกเลย ดังนั้นผมขอแนะนำให้หลีกเลี่ยงการทำ Selector ในลักษณะที่เป็นโครงสร้าง Hierachy แบบนี้นะครับ

แล้วทีนี้จะแก้ปัญหานี้ยังไง?

วิธีการแก้ปัญหาที่ดีที่สุดสำหรับปัญหานี้ก็คือการ Define Custom Data Attribute เพิ่มเข้าไปใน DOM Element ของคุณตอนที่คุณวาด UI ออกมานั่นเอง ซึ่งข้อดีของการใช้ Custom Data Attribute นั้นคือค่าของมันจะไม่มีวันเปลี่ยนแปลงไม่ว่า User จะทำอะไรกับ Web App ของคุณก็ตาม ยกตัวอย่างเช่น <input type=”text” name=”username” data-testid=”usernameInput” /> “data-testid” คือ custom data attribute ที่เรา define เพิ่มเข้าไปเพื่อทำให้ Web App ของเรารองรับการทำเทสได้ดีขึ้น

ดังนั้นเมื่อเราจะเข้าถึง Element ด้วย Cypress ก็สามารถใช้คำสั่ง cy.get('input[data-testid="usernameInput"]') มันก็จะเข้าถึง Input Element ได้โดยตรงเสมอครับ

Find Element แบบ Pro ด้วย Cypress Testing Library

Cypress Testing Library เป็นหนึ่งใน Open Source Project Testing Library ที่รวบรวมเอา Testing Library เจ๋งๆ ที่ใช้ Concept ในการเข้าถึง DOM Element ด้วย Best Practices ที่ผมได้บอกไปข้างต้น และยังได้สร้าง API เพื่อ Shortcut การเข้าถึง Element ของเรานั้นสามารถเขียนได้ง่ายขึ้นแถมยังสามารถ Customize มันได้อีกด้วย

Project Testing Library นั้นมี Library ที่รองรับการเขียนเทสกับ Web Technology ที่หลากหลาย รองรับการทำ Unit Test ของ ​Web Library ชื่อดังเช่น React Testing Library, Vue Testing Library, Angular Testing Library และแน่นอนว่ารองรับการทำ End-to-End Test ด้วย Cypress ได้ด้วย ซึ่ง Library ทุกตัวถูกออกแบบอยู่บน Concept และพื้นฐานการเขียนเทสที่เป็น Practices เดียวกัน ทำให้คนที่ต้องใช้ Web Library ที่หลากหลายสามารถ Adopt Testing Library เหล่านี้ไปใช้กับ Project ได้ไม่ยากเลยครับ

Testing Library Project

เริ่มต้นใช้งาน Cypress Testing Library แบบ Zero to Hero

Cypress Testing Library นั้นได้เตรียม API มาให้เราใช้ในการ Find Elements มากมายและยังรองรับการ Find Element โดยใช้ Custom Data Attribute อย่างง่ายอีกด้วย ในบทความนี้ผมจะขอแนะนำวิธีการสร้าง Cypress Project ทำเทส http://todomvc.com/examples/react ด้วย Cypress Testing Library กันครับ

สร้าง Cypress Project กันก่อน

ผมจะเขียนเทสในตัวอย่างนี้ด้วย TypeScript ทั้งหมดนะครับ ซึ่งมีข้อดีตรงที่ว่ามันสามารถทำ Code Completion ในการเรียก API แต่ละตัวได้เป็นอย่างดี

sh$ mkdir cypress-testing-library-example
sh$ yarn init
sh$ yarn add --dev cypress typescript @testing-library/cypress

หลังจากที่เราได้ทำการ Init Cypress Project แล้วเราก็จะได้หน้าตาของไฟล์ package.json ออกมาแบบนี้ ผมได้เพิ่ม scripts เข้าไปเพื่อทำให้รันเทสด้วย Cypress UI Test Runner ได้ง่ายๆ ด้วยคำสั่ง yarn cypress:open ด้วยครับ

ทำการ Configure Code Completion

ผมสร้างไฟล์ tsconfig.json ไว้ใน cypress directory เพื่อให้ Enable Code Completion บน VSCode แบบนี้ครับ

@types/testing-library__cypress คือ TypeScript Types เพื่อทำให้เราสามารถใช้ Code Completion บน Cypress Testing Library ได้

เช็ค Custom Data Attribute

บน Web http://todomvc.com/examples/react นั้นมีการ define custom data attribute ไว้ให้ด้วย โดยใช้ชื่อ attribute ว่า data-reactid นั่นเอง

เริ่มเขียนเทสโดยใช้ API ของ Cypress Testing Library

Cypress Testing Library ได้เตรียม API ให้เราใช้ในการเข้าถึง DOM Elements ไว้หลายอันเลย เช่น

  • findByText ค้นหา element ด้วย Text Content ของ DOM Element เช่น <span>MyText</span> ก็สามารถใช้ cy.findByText(‘MyText’) ได้เลย
  • findByLabelText ค้นหา element ด้วย attribute aria-label เช่น <input aria-label=”username” /> ใช้ cy.findByLabelText(‘username’) ได้เลย
  • findByTestId ค้นหา element ด้วย custom data attribute data-testid (default) เช่น <span data-testid=”mytext”>Hello World</span> ก็ใช้ cy.findByTestId(‘mytext’) ได้เลย

สามารถเข้าไปดู API ทั้งหมดได้ที่ https://testing-library.com/docs/dom-testing-library/api-queries เลยครับ

เนื่องจากว่าเว็บ http://todomvc.com/examples/react นั้นใช้ data-reactid แทนที่จะใช้ data-testid เป็น custom data attribute ซึ่งก็ไม่ใช่ปัญหาเพราะ Cypress Testing Library ได้เปิด API ให้เราสามารถ Configure ชื่อ Custom Data Attribute เองได้ (เราจะตั้งชื่ออะไรก็ได้เป็นของเราเอง) โดยวิธีเรียกใช้ก็คือให้ทำการเรียก API configure(); ใน cypress/support/index.js ได้เลย

เพิ่ม Custom Commands ของ Cypress Testing Library เข้าไป

เริ่มเขียนเทสโดยการใช้ Cypress Testing Library API

ผมได้ทำการแยกค่า locators ที่ต้องใช้สำหรับ data-reactid ไว้อีกไฟล์เพื่อให้ maintain code ได้ง่าย เป็นอีก 1 Practice ที่อยากแนะนำครับ

และนี่ก็คือตัวอย่างการเขียนเทสด้วย Cypress Testing Library API ทำงานร่วมกับ Cypress Commands ได้อย่างลงตัวแถมยังสามารถทำ Code Completion ได้อีกด้วยครับ

react-todo.spec.ts

เมื่อรันเทสก็จะเห็นว่าใน UI Test Runner ก็จะมีบอก API ที่เราใช้ในการ Find Element ด้วย และรวมถึง Highlight Elements ที่กำลังเทสอยู่ให้ด้วย ดีมากๆ เลยนะครับ

หวังว่าบทความนี้คงจะเป็นประโยชน์กับหลายๆ คนที่อาจจะเคยมีปัญหากับการ Find Element บน Cypress ก็อยากให้แนะนำให้ลองเอา Practice การเพิ่ม Custom Data Attribute เพิ่มเข้าไปในโค้ดของเราเพื่อที่จะทำให้ App ของเราเทสได้ง่ายมากขึ้นและ Flaky Tests จะน้อยลงมากๆ ครับ สามารถอ่านข้อมูลรายละเอียดเพิ่มเติมของ Cypress Testing Library ได้ที่ https://github.com/testing-library/cypress-testing-library Happy Testing!

--

--