Trong quy trình phát triển phần mềm truyền thống, việc tạo các kịch bản kiểm thử tự động cho ứng dụng web thường đòi hỏi kỹ sư kiểm thử phải chuyển đổi thủ công từ các kịch bản kiểm thử chức năng. Các công cụ tạo kịch bản tự động hiện có trên thị trường phần lớn dựa vào phương pháp ghi lại hành động của người dùng để tạo ra các bước kiểm thử. Mặc dù tiện lợi, nhưng các kịch bản được ghi lại thường thiếu tính linh hoạt, khó bảo trì và vẫn cần sự can thiệp thủ công đáng kể.
Với sự ra đời của các Mô hình Ngôn ngữ Lớn (LLM), một hướng tiếp cận mới đã mở ra: liệu LLM có thể thực thi các kịch bản kiểm thử chức năng và sinh ra các kịch bản kiểm thử tự động không?
Giá Trị Ứng Dụng
Khi các kỹ sư kiểm thử mô tả rõ ràng các bước trong kịch bản kiểm thử chức năng, một công cụ sử dụng LLM có thể trực tiếp chuyển đổi chúng thành các kịch bản kiểm thử tự động cho web. Điều này hứa hẹn giảm thiểu đáng kể công sức và tài nguyên cần thiết cho việc tạo và duy trì bộ kiểm thử tự động.
Triển Khai Thực Tế
Nguyên Lý Hoạt Động
Nguyên lý cốt lõi của giải pháp này xoay quanh việc cho phép một LLM tương tác với trình duyệt web thông qua một bộ công cụ chuyên dụng. LLM sẽ phân tích các bước kiểm thử thủ công, sau đó sử dụng các công cụ này để mô phỏng hành vi người dùng, ghi lại các thao tác, và cuối cùng, chuyển đổi các thao tác đã ghi thành mã kiểm thử tự động.
Yêu Cầu Đối Với Kịch Bản Kiểm Thử Thủ Công
Để LLM có thể hiểu và chuyển đổi thành công kịch bản kiểm thử chức năng sang dạng tự động, các bước trong kịch bản phải được diễn đạt một cách rõ ràng và chi tiết. Nếu các bước không rõ ràng hoặc mơ hồ, LLM sẽ gặp khó khăn trong việc xác định các hành động cụ thể cần thực hiện.
Ví dụ về một kịch bản kiểm thử đăng nhập rõ ràng:
1. Mở trang web tại: https://litemall.hogwarts.ceshiren.com/#/login?redirect=%2Fdashboard
2. Nhập "hogwarts" vào trường tên người dùng.
3. Nhập "test12345" vào trường mật khẩu.
4. Nhấn nút "Đăng nhập".
5. Kiểm tra URL hiện tại sau khi đăng nhập thành công.
6. Hoàn thành và đóng trình duyệt.
Sử Dụng Agent để Thực Thi Kịch Bản Kiểm Thử
Bản thân các LLM chỉ có khả năng "suy nghĩ" chứ không thể trực tiếp thực hiện các hành động trên trình duyệt. Tuy nhiên, thông qua cơ chế Agent của LangChain, chúng ta có thể "kết nối" các công cụ bên ngoài vào LLM. Để thực thi các bước kiểm thử chức năng, LLM cần được trang bị các công cụ để tương tác với trình duyệt web.
Các khái niệm chính: Agent, Tools (Công cụ).
- Xây dựng bộ công cụ tương tác web:
Chúng ta sẽ tạo một lớp Python để gói gọn các thao tác cơ bản của Selenium WebDriver, giúp LLM có thể "điều khiển" trình duyệt.
from selenium import webdriver from selenium.webdriver.common.by import By from time import sleep class BrowserAutomationClient: def __init__(self): self._driver = None self._active_element = None def _initialize_webdriver(self): if not self._driver: self._driver = webdriver.Chrome() self._driver.implicitly_wait(5) # Chờ ẩn 5 giây def navigate_to_url(self, target_url: str) -> str: self._initialize_webdriver() self._driver.get(target_url) return self.get_page_interactive_summary() def get_page_interactive_summary(self) -> str: # Lấy một bản tóm tắt các phần tử tương tác trên trang script = """ var elements_info = ""; document.querySelectorAll('button, input, a, [role="button"], [type="submit"]').forEach(el => { let info = el.outerHTML; if (el.placeholder) info += ` placeholder='${el.placeholder}'`; if (el.ariaLabel) info += ` aria-label='${el.ariaLabel}'`; if (el.id) info += ` id='${el.id}'`; if (el.name) info += ` name='${el.name}'`; elements_info += info + "\\n"; }); return elements_info; """ return self._driver.execute_script(script) def perform_click_on_element(self) -> str: # Nhấp vào phần tử đang được chọn if self._active_element: self._active_element.click() sleep(1) # Chờ một chút sau khi nhấp return self.get_page_interactive_summary() def send_input_text(self, text_to_send: str) -> str: # Nhập văn bản vào phần tử đang được chọn if self._active_element: self._active_element.clear() self._active_element.send_keys(text_to_send) return self.get_page_interactive_summary() def locate_element_by_css(self, css_selector: str) -> str: # Tìm kiếm và lưu trữ phần tử bằng CSS selector print(f"Searching for CSS selector: {css_selector}") element = self._driver.find_element(by=By.CSS_SELECTOR, value=css_selector) self._active_element = element return self.get_page_interactive_summary() def close_browser_session(self): if self._driver: self._driver.quit() self._driver = None def retrieve_current_page_url(self) -> str: return self._driver.current_url - Tạo công cụ LangChain:
Sử dụng decorator
@toolcủa LangChain để biến các phương thức của lớpBrowserAutomationClientthành các công cụ mà Agent có thể sử dụng.from langchain.tools import tool import time # Import time for sleep browser_handler = BrowserAutomationClient() @tool def open_url(url: str) -> str: """Mở một URL trong trình duyệt và trả về nội dung tóm tắt của trang.""" return browser_handler.navigate_to_url(url) @tool def find_element(css_selector: str) -> str: """Định vị một phần tử web bằng CSS selector và trả về tóm tắt các phần tử trên trang.""" return browser_handler.locate_element_by_css(css_selector) @tool def click_element_by_css(css_selector: str) -> str: """Định vị phần tử bằng CSS selector rồi nhấp vào nó. Trả về tóm tắt trang.""" browser_handler.locate_element_by_css(css_selector) return browser_handler.perform_click_on_element() @tool def type_text(css_selector: str, text: str) -> str: """Định vị phần tử bằng CSS selector và nhập văn bản vào. Trả về tóm tắt trang.""" browser_handler.locate_element_by_css(css_selector) return browser_handler.send_input_text(text) @tool def wait_for_seconds(seconds: int): """Chờ một số giây nhất định.""" time.sleep(seconds) @tool def get_current_browser_url() -> str: """Lấy URL hiện tại của trình duyệt.""" return browser_handler.retrieve_current_page_url() @tool def quit_browser(): """Đóng trình duyệt.""" browser_handler.close_browser_session() automation_tools = [open_url, find_element, click_element_by_css, type_text, wait_for_seconds, get_current_browser_url, quit_browser] - Khởi tạo và chạy Agent:
Sau khi có các công cụ, chúng ta khởi tạo một Agent và Agent Executor với LLM và bộ công cụ đã tạo.
from langchain import hub from langchain.agents import create_structured_chat_agent, AgentExecutor from langchain_openai import ChatOpenAI # Ví dụ dùng OpenAI LLM from langchain_core.agents import AgentAction # ... (các import khác) llm_model = ChatOpenAI(temperature=0) # Chọn một LLM thích hợp # Lấy prompt agent từ hub của LangChain agent_prompt = hub.pull("hwchase17/structured-chat-agent") # Tạo Agent web_automation_agent = create_structured_chat_agent(llm_model, automation_tools, agent_prompt) # Tạo Agent Executor agent_runner = AgentExecutor( agent=web_automation_agent, tools=automation_tools, verbose=True, # Hiển thị chi tiết quá trình thực thi return_intermediate_steps=True, # Trả về các bước trung gian handle_parsing_errors=True ) - Thực thi Agent:
user_query = """ Bạn là một chuyên gia kiểm thử tự động. Hãy thực hiện các bước sau trên trình duyệt web, mỗi bước định vị phần tử cần dựa trên thông tin HTML trả về từ bước trước đó: 1. Mở https://litemall.hogwarts.ceshiren.com/#/login?redirect=%2Fdashboard 2. Nhập 'hogwarts' vào trường tên người dùng. 3. Nhập 'test12345' vào trường mật khẩu. 4. Nhấn nút đăng nhập. 5. Khi vào trang chính, lấy URL hiện tại. 6. Hoàn thành và đóng trình duyệt. """ execution_result = agent_runner.invoke({"input": user_query})
Ghi Lại Các Bước Thực Thi
Agent Executor có khả năng ghi lại tất cả các bước đã thực hiện. Thông tin này được lưu trữ trong thuộc tính intermediate_steps của kết quả trả về. Chúng ta có thể trích xuất và định dạng lại các bước này để sử dụng cho việc tạo mã kiểm thử.
# Lấy kết quả thực thi từ Agent
execution_result = agent_runner.invoke({"input": user_query})
# Lấy các bước thực thi trung gian
intermediate_steps_log = execution_result["intermediate_steps"]
formatted_steps = []
# Duyệt qua các bước để trích xuất thông tin công cụ và đầu vào
for step in intermediate_steps_log:
action = step[0] # Action là phần tử đầu tiên trong tuple
if isinstance(action, AgentAction):
formatted_steps.append({'tool_used': action.tool, 'tool_arguments': action.tool_input})
# formatted_steps giờ chứa một danh sách các dictionary mô tả hành động
Tạo Kịch Bản Kiểm Thử Tự Động
Với các bước thực thi đã được ghi lại (dưới dạng JSON), chúng ta có thể chuyển chúng cho một LLM khác (hoặc cùng một LLM với một prompt khác) để tạo ra mã kiểm thử tự động sử dụng pytest và Selenium WebDriver.
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
import json
test_case_generation_prompt = PromptTemplate.from_template("""
Bạn là một kỹ sư kiểm thử tự động web chuyên nghiệp, với kinh nghiệm sử dụng pytest và Selenium WebDriver.
Dưới đây là một chuỗi các bước kiểm thử tự động được mô tả dưới dạng JSON:
{execution_log}
Dựa trên nhật ký thực thi này, hãy viết một kịch bản kiểm thử pytest hoàn chỉnh,
đảm bảo mã nguồn phải chính xác và tuân thủ các phương pháp hay nhất của Selenium.
Sử dụng các tên hàm và biến phù hợp với mục đích kiểm thử.
""")
# ... (Khởi tạo LLM nếu chưa có)
# llm_model = ChatOpenAI(temperature=0)
# Ví dụ cách sử dụng prompt:
# prompt_input = test_case_generation_prompt.format(
# execution_log=json.dumps(formatted_steps, indent=2),
# input="Vui lòng tạo mã kiểm thử tự động tương ứng."
# )
# generated_test_code = llm_model.invoke(prompt_input).content
Kết Hợp Agent và Chain trong LangChain
Để tối ưu hóa quy trình, chúng ta có thể kết hợp Agent Executor với các Chain của LangChain, tạo thành một luồng làm việc liền mạch.
import json
from langchain import hub
from langchain.agents import create_structured_chat_agent, AgentExecutor
from langchain.globals import set_debug
from langchain_core.agents import AgentAction
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI
# Giả định automation_tools đã được định nghĩa ở trên
# from .automation_tools import automation_tools # Cần điều chỉnh import nếu code ở file riêng
set_debug(True) # Bật chế độ debug để xem chi tiết
# Khởi tạo LLM
main_llm = ChatOpenAI(temperature=0)
# Lấy prompt mặc định cho Agent từ hub
agent_system_prompt = hub.pull("hwchase17/structured-chat-agent")
# Tạo Agent thực thi các bước trình duyệt
browser_action_agent = create_structured_chat_agent(main_llm, automation_tools, agent_system_prompt)
# Tạo Agent Executor
browser_action_executor = AgentExecutor(
agent=browser_action_agent,
tools=automation_tools,
verbose=True,
return_intermediate_steps=True,
handle_parsing_errors=True
)
# Kịch bản kiểm thử thủ công đầu vào
manual_test_scenario = """
Bạn là một chuyên gia kiểm thử tự động. Hãy thực hiện các bước sau trên trình duyệt web,
mỗi bước định vị phần tử cần dựa trên thông tin HTML trả về từ bước trước đó:
1. Mở https://litemall.hogwarts.ceshiren.com/#/login?redirect=%2Fdashboard
2. Nhập 'hogwarts' vào trường tên người dùng.
3. Nhập 'test12345' vào trường mật khẩu.
4. Nhấn nút đăng nhập.
5. Khi vào trang chính, lấy URL hiện tại.
6. Hoàn thành và đóng trình duyệt.
"""
# Hàm này sẽ được gọi trong LangChain Chain để thực thi Agent và lấy log
def retrieve_agent_execution_log(_):
# Thực thi Agent với kịch bản thủ công
result = browser_action_executor.invoke({"input": manual_test_scenario})
# Trích xuất các bước trung gian
steps_data = result["intermediate_steps"]
log_entries = []
for entry in steps_data:
action_item = entry[0]
if isinstance(action_item, AgentAction):
log_entries.append({'tool_name': action_item.tool, 'parameters': action_item.tool_input})
return json.dumps(log_entries, indent=2)
# Prompt để tạo mã kiểm thử tự động từ nhật ký thực thi
code_gen_prompt = PromptTemplate.from_template("""
Bạn là một kỹ sư kiểm thử tự động web chuyên nghiệp, với kinh nghiệm sử dụng pytest và Selenium WebDriver.
Dưới đây là một chuỗi các bước kiểm thử tự động đã được ghi lại, mô tả dưới dạng JSON:
{execution_log}
Dựa trên nhật ký thực thi này, hãy viết một kịch bản kiểm thử pytest hoàn chỉnh và chính xác,
tuân thủ các phương pháp hay nhất của Selenium. Đặt tên hàm kiểm thử là `test_login_scenario`.
Hãy cung cấp mã Python đầy đủ, bao gồm cả các import cần thiết và setup/teardown.
""")
# Xây dựng Chain
full_automation_chain = (
RunnablePassthrough.assign(execution_log=retrieve_agent_execution_log) # Gọi hàm để lấy log
| code_gen_prompt # Truyền log vào prompt tạo mã
| main_llm # Truyền prompt cho LLM
| StrOutputParser() # Phân tích output thành chuỗi
)
# Chạy Chain để sinh ra mã kiểm thử
print(full_automation_chain.invoke({"input": "Vui lòng tạo mã kiểm thử tự động cho quy trình này."}))
Kết Quả Sinh Mã Kiểm Thử
Dưới đây là một ví dụ về mã kiểm thử tự động được sinh ra dựa trên các bước đã thực hiện:
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
@pytest.fixture(scope="module")
def browser_instance():
driver = webdriver.Chrome()
driver.implicitly_wait(10)
yield driver
driver.quit()
def test_login_scenario(browser_instance):
driver = browser_instance
base_url = "https://litemall.hogwarts.ceshiren.com/#/login?redirect=%2Fdashboard"
# Bước 1: Mở trang web
driver.get(base_url)
WebDriverWait(driver, 10).until(EC.url_to_be(base_url))
print(f"Navigated to: {driver.current_url}")
# Bước 2: Nhập tên người dùng
username_field = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "input[name='username']"))
)
username_field.clear()
username_field.send_keys("hogwarts")
print("Entered username.")
# Bước 3: Nhập mật khẩu
password_field = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "input[name='password']"))
)
password_field.clear()
password_field.send_keys("test12345")
print("Entered password.")
# Bước 4: Nhấn nút đăng nhập
login_button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, "button[type='button'].el-button--primary span"))
)
login_button.click()
print("Clicked login button.")
# Bước 5: Kiểm tra URL sau khi đăng nhập
expected_dashboard_url = "https://litemall.hogwarts.ceshiren.com/#/dashboard" # Giả định URL sau đăng nhập
WebDriverWait(driver, 20).until(EC.url_contains("/dashboard"))
current_page_url = driver.current_url
print(f"Current URL after login: {current_page_url}")
assert "/dashboard" in current_page_url, "Failed to navigate to dashboard after login."
print("Successfully verified dashboard navigation.")
# Bước 6: Đóng trình duyệt (được xử lý bởi fixture)