Mở đầu — vì sao bài này quan trọng
Nếu bạn hỏi bất kỳ kỹ sư QA nào ở Việt Nam rằng "công cụ automation nào phổ biến nhất trong tin tuyển dụng?", câu trả lời gần như luôn có tên Selenium. Dù thị trường đã có những công cụ mới hơn như Cypress hay Playwright (chúng ta sẽ học ở các bài sau), Selenium vẫn là "ngôn ngữ chung" của ngành. Rất nhiều framework nội bộ tại các công ty như FPT Software, KMS Technology, hay các product team ở Tiki, VNG đều được xây trên nền Selenium. Hiểu Selenium không chỉ giúp bạn qua vòng phỏng vấn, mà còn giúp bạn đọc hiểu code của bất kỳ framework nào — vì phần lớn chúng đều mượn tư duy từ Selenium.
Bài này là nền móng. Nếu bạn nắm chắc cách WebDriver "nói chuyện" với trình duyệt, cách khởi tạo một session, cách tìm và tương tác với phần tử trên trang, thì mọi kỹ thuật nâng cao sau này — Locators, Wait, Page Object Model — đều chỉ là những lớp xây thêm lên nền này. Ngược lại, nếu bạn bỏ qua phần cơ bản và nhảy thẳng vào framework phức tạp, bạn sẽ liên tục gặp những lỗi khó hiểu mà không biết gốc rễ nằm ở đâu.
Ở bài này, tôi sẽ tập trung vào WebDriver Basics: Selenium là gì, kiến trúc hoạt động, cách cài đặt và viết script đầu tiên bằng Python, các thao tác cơ bản với trình duyệt và phần tử. Tôi sẽ cố ý không đi sâu vào chiến lược locator hay wait — đó là chủ đề riêng của các bài kế tiếp. Mục tiêu của bạn sau bài này: tự viết được một script Selenium chạy được từ đầu đến cuối.
Khái niệm cốt lõi
Selenium là gì?
Selenium là một bộ công cụ mã nguồn mở (open-source) để tự động hóa trình duyệt web. Nói đơn giản, thay vì bạn ngồi click chuột, gõ phím, chờ trang load rồi kiểm tra kết quả bằng mắt, bạn viết code để máy làm việc đó thay bạn — nhanh hơn, lặp lại chính xác hàng nghìn lần, không mệt mỏi.
Cần phân biệt rõ: "Selenium" không phải một thứ duy nhất, mà là một gia đình công cụ. Trong đó ba thành phần chính là:
- Selenium WebDriver: trái tim của Selenium — thư viện cho phép bạn điều khiển trình duyệt bằng code (Python, Java, C#, JavaScript...). Đây là thứ chúng ta học.
- Selenium IDE: một extension ghi lại thao tác (record-and-playback) trên trình duyệt. Dễ dùng nhưng chỉ phù hợp làm quen hoặc prototype nhanh, không dùng cho dự án thật.
- Selenium Grid: hạ tầng để chạy test song song trên nhiều máy, nhiều trình duyệt cùng lúc (sẽ nói kỹ ở bài về Parallel Execution).
WebDriver hoạt động như thế nào?
Đây là phần nhiều bạn bỏ qua nhưng lại cực kỳ quan trọng để debug sau này. Hãy hình dung có ba nhân vật:
- Script của bạn (code Python) — người ra lệnh.
- Trình duyệt (Chrome, Firefox...) — người thực thi.
- Browser Driver (ChromeDriver, GeckoDriver...) — người phiên dịch đứng giữa.
Vì sao cần biết điều này? Vì lỗi phổ biến nhất của người mới là phiên bản ChromeDriver không khớp với phiên bản Chrome. Khi Chrome tự cập nhật lên bản mới (điều thường xuyên xảy ra), nếu ChromeDriver cũ, "người phiên dịch" và "người thực thi" không hiểu nhau, script chết ngay. Hiểu kiến trúc này, bạn sẽ biết ngay phải sửa ở đâu.
Một tin vui: từ Selenium 4.6 trở đi, có công cụ Selenium Manager tích hợp sẵn, tự động tải đúng driver cho bạn. Bạn không còn phải tự tay tải và chỉ đường dẫn driver như thời Selenium 3 nữa. Đây là lý do tôi khuyên bạn luôn dùng Selenium 4 trở lên.
Object WebDriver và Session
Khi bạn viết driver = webdriver.Chrome(), bạn đang tạo ra một session — một phiên làm việc gắn với một cửa sổ trình duyệt cụ thể. Biến driver là "tay cầm điều khiển" của bạn. Mọi lệnh đều đi qua nó: driver.get(url) để mở trang, driver.find_element(...) để tìm phần tử, driver.quit() để đóng phiên.
Một session tồn tại độc lập và giữ nguyên trạng thái (cookies, localStorage, tab đang mở) cho đến khi bạn gọi quit(). Điều này quan trọng: mỗi test tự động nên bắt đầu bằng một session sạch để tránh ảnh hưởng lẫn nhau — nguyên tắc "test isolation" mà chúng ta sẽ nhắc lại nhiều lần trong khóa.
Tìm phần tử (Finding Elements)
Muốn tương tác với một nút, một ô nhập liệu, hay một dòng text, trước hết bạn phải "chỉ" cho WebDriver biết phần tử đó nằm đâu. Đây gọi là locating elements. Trong Selenium 4 (Python), cú pháp chuẩn là:
from selenium.webdriver.common.by import Byelement = driver.find_element(By.ID, "username")
find_element trả về một phần tử đầu tiên khớp; find_elements (có "s") trả về danh sách tất cả phần tử khớp. Các chiến lược By gồm ID, NAME, CSS_SELECTOR, XPATH, CLASS_NAME, TAG_NAME, LINK_TEXT. Bài 7 sẽ dạy bạn chọn chiến lược nào cho tình huống nào — ở bài này bạn chỉ cần biết cơ chế.
Tương tác cơ bản
Sau khi có phần tử, bạn thao tác với nó qua vài phương thức cốt lõi:
.click()— nhấp chuột..send_keys("văn bản")— gõ chữ vào ô input..clear()— xóa nội dung ô input..text— lấy chữ hiển thị..get_attribute("href")— lấy giá trị thuộc tính..is_displayed(),.is_enabled(),.is_selected()— kiểm tra trạng thái.
Tình huống thực tế
Ví dụ 1: Team QA của một sàn TMĐT kiểm tra chức năng đăng nhập
Hãy tưởng tượng một team QA gồm 4 người tại một công ty thương mại điện tử tầm trung ở TP.HCM, mỗi sprint 2 tuần. Trước đây họ test đăng nhập bằng tay: mở trình duyệt, gõ email, gõ mật khẩu, bấm đăng nhập, kiểm tra chuyển trang. Mỗi lần khoảng 40 giây, nhân với 5 trình duyệt và khoảng 12 kịch bản (đúng/sai mật khẩu, tài khoản khóa, để trống ô...). Tính ra mỗi lần regression mất gần 40 phút chỉ riêng phần đăng nhập, và họ phải làm lại mỗi lần dev deploy — có sprint tới 6-7 lần.
Một bạn junior viết script Selenium đầu tiên chỉ khoảng 15 dòng: mở trang, tìm ô email theo By.ID, send_keys email, tìm ô mật khẩu, gõ mật khẩu, click nút đăng nhập, rồi assert URL đã đổi sang trang chủ. Script chạy trong 6 giây. Sau khi mở rộng cho đủ 12 kịch bản, toàn bộ chạy dưới 2 phút.
Bài học rút ra: Sức mạnh của WebDriver không nằm ở chỗ nó làm được điều con người không làm được, mà ở chỗ nó làm điều lặp đi lặp lại một cách nhanh và ổn định. Bắt đầu từ một luồng nhỏ, quen thuộc, chạy hàng ngày như đăng nhập chính là cách "ăn điểm" đầu tiên khi giới thiệu automation vào team.
Ví dụ 2: Sự cố "hôm qua chạy được, hôm nay chết hết"
Một anh QA ở Đà Nẵng kể lại một buổi sáng thứ Hai kinh điển: toàn bộ 80 test Selenium của team đồng loạt fail với thông báo SessionNotCreatedException: This version of ChromeDriver only supports Chrome version 122. Hôm trước mọi thứ xanh mướt, không ai đụng vào code.
Nguyên nhân: cuối tuần máy CI tự cập nhật Chrome lên bản 123, trong khi ChromeDriver được "ghim cứng" ở bản 122 trong repo. "Người phiên dịch" và "người thực thi" lệch phiên bản, và như tôi đã giải thích ở phần kiến trúc, chúng lập tức từ chối hiểu nhau.
Cách xử lý của anh có hai bước. Trước mắt, anh nâng Selenium lên bản 4.11 để dùng Selenium Manager — công cụ này tự dò phiên bản Chrome đang cài và tải đúng driver, xóa bỏ hoàn toàn dòng code hardcode đường dẫn driver cũ. Về lâu dài, anh chuyển hẳn sang chạy test trong Docker với image cố định phiên bản Chrome (chủ đề bài Docker cho Test Environments), để môi trường không còn "tự ý thay đổi sau lưng".
Bài học rút ra: Phần lớn lỗi "tự nhiên hỏng" trong Selenium đến từ sự lệch phiên bản giữa trình duyệt và driver, chứ không phải do code test sai. Hiểu kiến trúc ba lớp giúp bạn chẩn đoán trong 5 phút thay vì hoảng loạn cả buổi sáng.
Ví dụ 3: Từ script "chạy được" đến script "chạy sạch"
Một bạn fresher tại một startup fintech viết script đầu tiên và rất hào hứng vì nó chạy được. Nhưng khi review, tech lead phát hiện script quên gọi driver.quit(). Hậu quả: sau một đêm CI chạy 200 lần, server đầy rẫy hàng trăm tiến trình Chrome "mồ côi" (zombie process) ngốn hết RAM, khiến các job sau đó chậm rì rồi crash.
Fix rất đơn giản: bọc phần thân test trong khối try...finally, đặt driver.quit() trong finally để đảm bảo dù test pass hay fail thì trình duyệt vẫn luôn được đóng và giải phóng tài nguyên.
Bài học rút ra: driver.quit() không phải chi tiết nhỏ nhặt. Với người mới, "chạy được" chỉ là một nửa; "chạy sạch, không rò rỉ tài nguyên" mới là chuẩn của code automation nghiêm túc. Đây cũng là lý do các framework thật luôn quản lý vòng đời driver qua setup/teardown (như fixture của pytest — bài 19).
Hướng dẫn từng bước
Ta sẽ viết một script Selenium hoàn chỉnh bằng Python. Giả sử bạn đã cài Python 3.
Bước 1 — Cài đặt thư viện
Mở terminal và chạy:
pip install selenium
Nếu bạn dùng Selenium 4.6 trở lên (hãy kiểm tra bằng pip show selenium), Selenium Manager đã tích hợp sẵn, bạn không cần tải ChromeDriver thủ công.
Bước 2 — Khởi tạo WebDriver
from selenium import webdriver
from selenium.webdriver.common.by import Bydriver = webdriver.Chrome() # Selenium Manager tự lo driver
Nếu muốn chạy Chrome ở chế độ ẩn (headless — không hiện cửa sổ, hữu ích trên CI), bạn thêm options:
from selenium.webdriver.chrome.options import Optionsopts = Options()
opts.add_argument("--headless=new")
driver = webdriver.Chrome(options=opts)
(Chi tiết về headless là chủ đề riêng của bài 25 — ở đây tôi chỉ giới thiệu để bạn biết nó tồn tại.)
Bước 3 — Mở trang và tối đa hóa cửa sổ
driver.get("https://www.saucedemo.com/") # trang demo công khai để luyện tập
driver.maximize_window()
saucedemo.com là trang luyện tập automation miễn phí, rất phổ biến — bạn dùng nó thoải mái mà không lo ảnh hưởng hệ thống thật.
Bước 4 — Tìm phần tử và tương tác
driver.find_element(By.ID, "user-name").send_keys("standard_user")
driver.find_element(By.ID, "password").send_keys("secret_sauce")
driver.find_element(By.ID, "login-button").click()
Bước 5 — Kiểm chứng kết quả (assertion)
assert "inventory" in driver.current_url, "Đăng nhập thất bại!"
print("Đăng nhập thành công:", driver.current_url)
Bước 6 — Đóng session an toàn
Ghép tất cả lại với try...finally để đảm bảo trình duyệt luôn được đóng:
from selenium import webdriver
from selenium.webdriver.common.by import Bydriver = webdriver.Chrome()
try:
driver.get("https://www.saucedemo.com/")
driver.maximize_window()
driver.find_element(By.ID, "user-name").send_keys("standard_user")
driver.find_element(By.ID, "password").send_keys("secret_sauce")
driver.find_element(By.ID, "login-button").click()
assert "inventory" in driver.current_url, "Đăng nhập thất bại!"
print("PASS")
finally:
driver.quit()
Chạy file này. Bạn sẽ thấy Chrome tự mở, gõ thông tin, đăng nhập, rồi đóng lại. Xin chúc mừng — bạn vừa viết xong test automation đầu tiên.
Lỗi thường gặp & mẹo
Lỗi SessionNotCreatedException / driver không khớp phiên bản: Như ví dụ 2, đây là lỗi kinh điển. Mẹo: nâng Selenium lên bản mới nhất để dùng Selenium Manager, và tuyệt đối tránh hardcode đường dẫn driver theo kiểu Selenium 3.
Quên driver.quit(): Gây rò rỉ tiến trình. Mẹo: luôn đặt quit() trong finally, hoặc dùng fixture của test framework. Phân biệt: close() chỉ đóng tab hiện tại, quit() đóng toàn bộ session và tắt driver — dùng test luôn cần quit().
NoSuchElementException ngay khi trang vừa load: Người mới thường nghĩ script sai locator, nhưng thực tế phần lớn là do trang chưa render kịp phần tử thì code đã tìm. Đây chính là lý do tồn tại của Wait Strategies (bài 8). Ở giai đoạn này, đừng vội "chữa cháy" bằng time.sleep() bừa bãi — hãy ghi nhớ đây là vấn đề timing và sẽ được giải quyết đúng cách sau.
Nhầm find_element với find_elements: find_element ném exception nếu không tìm thấy; find_elements trả về danh sách rỗng []. Nếu bạn cần kiểm tra "phần tử có tồn tại không", dùng find_elements rồi kiểm tra độ dài danh sách sẽ an toàn hơn là bọc try/except.
send_keys bị lỗi tiếng Việt có dấu: Với các ô nhập liệu tiếng Việt, đôi khi send_keys("Nguyễn") cho kết quả sai do cơ chế gõ ký tự Unicode. Đây là một chủ đề thú vị dành riêng cho bài 45 (Test dấu tiếng Việt) — bạn hãy để ý nhưng chưa cần lo ở bài này.
Mẹo debug nhanh: Khi test fail, chụp màn hình ngay bằng driver.save_screenshot("error.png") trong khối except. Một tấm ảnh tại thời điểm lỗi thường tiết lộ vấn đề nhanh hơn hàng chục dòng log.
Bài tập thực hành
- Chạy lại script mẫu: Cài
selenium, chạy đoạn code đăng nhậpsaucedemo.comở trên đến khi thấy chữ "PASS". Nếu gặp lỗi driver, hãy thử tự chẩn đoán bằng kiến thức về kiến trúc ba lớp.
- Kịch bản đăng nhập sai: Sửa mật khẩu thành một chuỗi sai, tìm phần tử thông báo lỗi trên trang (dùng
By.CSS_SELECTORvới class chứa "error"), lấy.textcủa nó và in ra. Assert rằng thông báo lỗi có xuất hiện.
- Khám phá phương thức: Sau khi đăng nhập thành công, dùng
find_elementsđể đếm số sản phẩm hiển thị trên trang inventory, rồi in ra con số đó. Gợi ý: mỗi sản phẩm nằm trong một phần tử có class chung.
- Thêm chụp màn hình: Bọc script trong
try/except/finally, thêmsave_screenshotkhi có lỗi vàquit()trongfinally. Cố tình dùng một locator sai để kiểm chứng ảnh được lưu.
- Suy ngẫm: Viết ra (bằng lời của bạn) điều gì xảy ra ở tầng WebDriver Protocol khi bạn gọi
driver.find_element(...). Giải thích được cho người khác nghĩa là bạn đã thực sự hiểu.
Tóm tắt
Selenium WebDriver là công cụ mã nguồn mở để điều khiển trình duyệt bằng code, và là nền tảng phổ biến nhất của automation testing tại Việt Nam. Điều cốt lõi cần khắc sâu sau bài này:
- Kiến trúc ba lớp: script → browser driver (người phiên dịch) → trình duyệt. Hiểu nó giúp bạn debug lỗi phiên bản trong vài phút.
- Vòng đời session:
webdriver.Chrome()tạo phiên,driver.get()mở trang,find_elementtìm phần tử, các phương thứcclick/send_keys/textđể tương tác, vàquit()để đóng sạch. - Selenium 4 với Selenium Manager loại bỏ nỗi đau quản lý driver thủ công — hãy luôn dùng bản mới.
- Ba nguyên tắc từ tình huống thực tế: bắt đầu từ luồng nhỏ quen thuộc, cảnh giác với lệch phiên bản driver, và luôn đóng session để không rò rỉ tài nguyên.