การหา Elements ด้วย Locators
จบบทนี้แล้วคุณจะ...
- อธิบายได้ว่า locator คืออะไร และทำไมมันถึงสำคัญต่อ automation
- แยกแยะ locator ประเภทต่าง ๆ ได้ (ID, CSS, XPath, text, role)
- ตัดสินได้ว่า locator ไหน "แข็งแรง" และ locator ไหน "เปราะบาง"
- เข้าใจแนวคิด semantic locator และ data-testid
ลองนึกภาพคุณเป็นหุ่นยนต์ที่ถูกสั่งให้ "กดปุ่มสมัครสมาชิก" บนหน้าเว็บ คุณต้องมีวิธีบอกหุ่นยนต์ว่าปุ่มนั้นอยู่ตรงไหน — นั่นคือสิ่งที่ locator ทำ
Locator คือ "ที่อยู่" หรือ "คำอธิบาย" ที่ใช้ระบุตัวตนของ element บนหน้าเว็บ เพื่อให้เครื่องมือ automation หาเจอและทำงานกับมันได้
ถ้าเทียบกับชีวิตจริง:
- ที่อยู่บ้าน = locator แบบ ID (เฉพาะเจาะจง ไม่ซ้ำใคร)
- "บ้านสีแดงหลังที่สาม" = locator แบบ CSS (อธิบายลักษณะ)
- "บ้านที่อยู่ถัดจากร้านกาแฟ ข้างซ้ายมือ ชั้นสอง" = locator แบบ XPath (อธิบายตำแหน่งเชิงโครงสร้าง)
ID Locator
หา element จาก attribute id ซึ่งควรจะไม่ซ้ำกันในหน้าเดียว
<button id="submit-btn">สมัคร</button>
- ข้อดี: เร็ว, เฉพาะเจาะจง, ไม่เปลี่ยนบ่อย
- ข้อเสีย: ไม่ใช่ทุก element จะมี id, บางระบบ generate id แบบ random
CSS Selector
ใช้ syntax เดียวกับ CSS ในการระบุ element
button.primary → ปุ่มที่มี class "primary"
#login-form input[type="email"] → input email ใน form login
.cart-item:first-child → item แรกในตะกร้า
- ข้อดี: ยืดหยุ่น, อ่านง่ายกว่า XPath, ทำงานเร็ว
- ข้อเสีย: ไม่สามารถ traverse ขึ้น parent ได้
XPath
ใช้ path expression เพื่อนำทางใน DOM tree
//button[@id="submit-btn"] → หาปุ่มจาก id
//div[@class="product"]//span[@class="price"] → หาราคาในกล่องสินค้า
//input[preceding-sibling::label[text()="Email"]] → input ที่อยู่หลัง label "Email"
- ข้อดี: ทรงพลังมาก, traverse ได้ทุกทิศทาง
- ข้อเสีย: อ่านยาก, ช้ากว่า CSS, เปราะบางถ้าเขียนไม่ดี
Text-based Locator
หา element จากข้อความที่แสดงบนหน้าจอ
"สมัครสมาชิก" → หาปุ่มหรือ link ที่มีข้อความนี้
"เข้าสู่ระบบ" → หาปุ่ม login
- ข้อดี: ตรงกับสิ่งที่ผู้ใช้เห็น, อ่านง่ายมาก
- ข้อเสีย: เปลี่ยนตามภาษา, พังถ้ามีการแก้ UI text
Role-based Locator (Semantic)
หา element จาก ARIA role หรือ semantic HTML
role="button" name="Submit"
role="textbox" name="Email"
role="navigation"
- ข้อดี: สอดคล้องกับ accessibility, ไม่ผูกกับ implementation detail
- ข้อเสีย: ต้องการ HTML ที่เขียนอย่าง semantic
ตัวอย่าง HTML
<div class="sc-kAzzGY bQRBFl">
<div class="sc-bdnxRM jWxYRR">
<form>
<div class="form-group">
<label>อีเมล</label>
<input type="email" class="sc-gsnTZi kZNMAq" />
</div>
<button class="sc-dkzDqf gNPFYR btn-primary" data-testid="login-submit">
เข้าสู่ระบบ
</button>
</form>
</div>
</div>
Locator ที่เปราะบาง (AVOID)
| Locator | ปัญหา |
|---|---|
.sc-kAzzGY.bQRBFl > .sc-bdnxRM > form > button |
class name จาก CSS-in-JS เปลี่ยนทุก build |
/html/body/div[3]/div[1]/form/button |
absolute XPath พังทันทีเมื่อ layout เปลี่ยน |
button.sc-dkzDqf.gNPFYR |
class name ที่ generate มาไม่มีความหมาย |
Locator ที่แข็งแรง (PREFER)
| Locator | เหตุผล |
|---|---|
[data-testid="login-submit"] |
สร้างมาเพื่อ testing โดยเฉพาะ, ไม่เปลี่ยนตาม UI |
button:has-text("เข้าสู่ระบบ") |
ตรงกับสิ่งที่ผู้ใช้เห็น |
role=button[name="เข้าสู่ระบบ"] |
semantic, accessibility-friendly |
input[type="email"] |
ใช้ attribute ที่มี semantic meaning |
data-testid คือ custom HTML attribute ที่ทีม dev เพิ่มเข้าไปเพื่อให้ automation หา element ได้ง่าย
<button data-testid="checkout-btn" class="sc-xyz abc123">
ชำระเงิน
</button>
ทำไมต้องมี?
- Class name อาจเปลี่ยนเมื่อ refactor CSS
- Text อาจเปลี่ยนเมื่อแก้ UI copy
- ID อาจไม่มีหรือถูกใช้เพื่อจุดประสงค์อื่น
data-testidถูกสร้างมาเพื่อ testing โดยเฉพาะ ไม่มีผลต่อ styling หรือ functionality
ข้อตกลงในทีม: dev และ QA ควรตกลงกันว่าจะใส่ data-testid ที่ element สำคัญ ๆ ตั้งแต่ตอนเขียน code ไม่ใช่มาเพิ่มทีหลัง
ลำดับความสำคัญในการเลือก locator (จากดีที่สุดไปน้อยที่สุด):
- Role/Semantic —
role=button[name="Submit"] - data-testid —
[data-testid="submit-btn"] - Text content —
text="Submit" - CSS Selector (stable attributes) —
input[type="email"] - ID —
#submit-btn(ถ้ามั่นใจว่า stable) - XPath (relative) —
//form//button[@type="submit"] - CSS (class-based) —
.btn-primary(ถ้า class มีความหมาย) - Absolute XPath — หลีกเลี่ยงให้ได้มากที่สุด
โจทย์
ให้ HTML snippet ต่อไปนี้ แล้วเลือก locator ที่ดีที่สุดสำหรับแต่ละ element ที่ระบุ พร้อมอธิบายเหตุผล
<nav class="sc-abc123 main-nav">
<ul>
<li><a href="/home" class="nav-link active">หน้าหลัก</a></li>
<li><a href="/products" class="nav-link">สินค้า</a></li>
<li><a href="/cart" class="nav-link" data-testid="cart-link">
ตะกร้า <span class="badge">3</span>
</a></li>
</ul>
</nav>
<main>
<section class="sc-def456">
<h2>สินค้าแนะนำ</h2>
<div class="product-grid">
<div class="sc-ghi789 product-card" data-testid="product-1">
<img src="phone.jpg" alt="สมาร์ทโฟน XYZ" />
<h3>สมาร์ทโฟน XYZ</h3>
<span class="price" data-testid="price-1">฿12,900</span>
<button class="sc-jkl012 add-to-cart" aria-label="เพิ่มสมาร์ทโฟน XYZ ลงตะกร้า">
เพิ่มลงตะกร้า
</button>
</div>
</div>
</section>
</main>
คำถาม:
- หา link ไปหน้าตะกร้า
- หาราคาสินค้าชิ้นแรก
- หาปุ่ม "เพิ่มลงตะกร้า"
- หารูปสินค้า
- หา navigation ทั้ง block
ดูคำตอบแนะนำ
Link ตะกร้า:
[data-testid="cart-link"]— มี data-testid ให้แล้ว เป็น locator ที่ stable ที่สุด ไม่ต้องพึ่ง class name ที่อาจเปลี่ยนราคาสินค้า:
[data-testid="price-1"]— มี data-testid ให้แล้ว ดีกว่าใช้.priceเพราะถ้ามีสินค้าหลายชิ้น class จะซ้ำกันปุ่มเพิ่มลงตะกร้า:
role=button[name="เพิ่มสมาร์ทโฟน XYZ ลงตะกร้า"]— ใช้ aria-label ที่เป็น semantic locator ดีกว่า class name ที่ generate มา หรือจะใช้[data-testid="product-1"] buttonก็ได้รูปสินค้า:
img[alt="สมาร์ทโฟน XYZ"]— ใช้ alt text ซึ่งเป็น semantic attribute ดีกว่า src ที่อาจเปลี่ยนNavigation block:
role=navigationหรือnav— ใช้ semantic HTML tag ดีกว่า class name ที่ generate มา
สิ่งที่ต้องเอาติดตัวไปจากบทนี้
- Locator คือวิธีที่ automation ใช้หา element บนหน้าเว็บ — เลือกดีหรือเลือกแย่ต่างกันลิบลับ
- Locator ที่เปราะบาง (generated class, absolute XPath) จะทำให้ test พังบ่อยโดยไม่มีบั๊กจริง
- Locator ที่แข็งแรง ใช้ semantic attributes, data-testid, หรือ role-based — ทนต่อการเปลี่ยนแปลง UI
- data-testid เป็นข้อตกลงระหว่าง dev กับ QA ที่ช่วยให้ automation มั่นคง
- การเลือก locator ที่ดีคือ ทักษะพื้นฐานที่สำคัญที่สุด ของ automation engineer