Tự động hóa kiểm thử bằng AI: Xây dựng các kịch bản Playwright linh hoạt với LLM

AI tutorial - IT technology blog
AI tutorial - IT technology blog

Sự ức chế từ các bộ test dễ gãy

Chuyện thường xảy ra vào lúc 4:30 chiều thứ Sáu. Bạn đã sẵn sàng đẩy một bản cập nhật quan trọng, nhưng CI/CD pipeline đột ngột thất bại. Thủ phạm là gì? Một lập trình viên đã đổi tên một CSS class hoặc bọc một nút bấm trong một thẻ <div> mới. Trong khi ứng dụng vẫn hoạt động hoàn hảo với người dùng, các bài kiểm thử End-to-End (E2E) của bạn lại bị hỏng vì chúng không còn tìm thấy phần tử id="submit-btn-v2" nữa.

Các công cụ tự động hóa tiêu chuẩn như Playwright, Selenium, hay Cypress cực kỳ nhanh, nhưng bản chất chúng rất cứng nhắc. Chúng tuân theo các chỉ dẫn được mã hóa cứng (hardcoded). Nếu bạn yêu cầu một kịch bản nhấp vào một XPath cụ thể và đường dẫn đó thay đổi dù chỉ một node, bài test sẽ sụp đổ. Theo kinh nghiệm của tôi, các kỹ sư QA thường dành 30% đến 40% thời gian sprint hàng tuần chỉ để “bảo trì locator”—sửa các bài test cũ thay vì xây dựng những bài test mới.

Việc chuyển đổi từ một manual tester sang một QA Engineer cấp cao đòi hỏi phải giải quyết được nút thắt cổ chai này. Bằng cách tích hợp các Mô hình Ngôn ngữ Lớn (LLM) vào quy trình kiểm thử, chúng ta có thể thoát khỏi các selector mong manh để hướng tới kiểm thử “dựa trên ý định” (intent-based testing). Điều này có nghĩa là cho máy tính biết mục tiêu cần đạt được, chứ không chỉ là tọa độ nào cần nhấp vào.

Tại sao các Selector truyền thống thường thất bại

Tự động hóa thất bại vì sự mất kết nối cơ bản. Con người nhìn vào một trang web và thấy “Nút màu xanh có chữ Đăng nhập”. Tuy nhiên, một kịch bản kiểm thử lại thấy div > form > div:nth-child(3) > button. Khi giao diện thay đổi, con người vẫn thấy nút đó, nhưng kịch bản thì bị lạc lối.

Các frontend framework hiện đại như React hay Tailwind CSS càng làm trầm trọng thêm vấn đề này. Chúng thường tạo ra các class động, được băm (hashed) như class="css-1v2x3y4" và thay đổi mỗi khi code được build lại. Điều này tạo ra một núi nợ kỹ thuật (technical debt). Để giải quyết vấn đề này, chúng ta cần một hệ thống hiểu được ngữ cảnh của trang web và thích ứng với các thay đổi trong thời gian thực, giống như cách một manual tester vẫn làm.

Thu hẹp khoảng cách giữa Playwright và LLM

Chiến lược của chúng ta là sử dụng Playwright để điều phối cấp thấp—xử lý điều khiển trình duyệt, chặn mạng và chụp ảnh màn hình—trong khi giao phó việc ra quyết định cho một LLM như GPT-4o hoặc Claude 3.5 Sonnet. Thay vì mã hóa cứng một selector, chúng ta gửi một phiên bản DOM đã được lọc cho AI và hỏi: “Nút thanh toán nằm ở đâu?”

Cách tiếp cận kết hợp này mở ra hai khả năng lớn:

  • Tự phục hồi (Self-Healing): Khi một selector chính bị lỗi, kịch bản sẽ tự động yêu cầu AI tìm phần tử dựa trên nhãn hoặc mục đích trực quan của nó.
  • Kiểm thử bằng ngôn ngữ tự nhiên: Bạn có thể viết các bài test bằng tiếng Việt đơn giản, chẳng hạn như “Thêm chiếc laptop đắt nhất vào giỏ hàng”, và để AI dịch ý định đó thành các hành động trên trình duyệt.

Thực hành: Xây dựng Test Runner tự phục hồi

Hãy cùng xây dựng một bản thử nghiệm (proof-of-concept) bằng Python và Playwright. Chúng ta sẽ tạo ra một cơ chế dự phòng để kích hoạt LLM khi một selector tiêu chuẩn bị lỗi.

Điều kiện tiên quyết

Bạn sẽ cần một API key của OpenAI và thư viện Playwright. Cài đặt chúng bằng các lệnh sau:

pip install playwright openai
playwright install chromium

Bước 1: Trích xuất ngữ cảnh DOM

Gửi toàn bộ file HTML cho LLM là việc không hiệu quả và tốn kém. Một trang landing page điển hình có thể có 5.000 dòng code, tiêu tốn khoảng 0,10 USD cho mỗi yêu cầu token. Thay vào đó, chúng ta chỉ trích xuất các phần tử có khả năng tương tác.

async def get_interactive_elements(page):
    # Chỉ trích xuất metadata mà AI cần để đưa ra quyết định
    elements = await page.evaluate("""
        () => {
            const interactive = Array.from(document.querySelectorAll('button, input, a, [role="button"]'));
            return interactive.map(el => ({
                tag: el.tagName,
                text: el.innerText || el.placeholder || el.ariaLabel,
                id: el.id,
                classes: el.className
            }));
        }
    """)
    return elements

Bước 2: Yêu cầu AI tìm Selector

Khi một locator bị lỗi, chúng ta gửi danh sách JSON đã rút gọn cho LLM. Sử dụng một cấu trúc prompt chặt chẽ để đảm bảo mô hình trả về một selector có thể sử dụng được thay vì một lời giải thích dài dòng.

from openai import OpenAI
client = OpenAI()

async def ai_find_element(goal, elements):
    prompt = f"""
    Tôi đang tự động hóa trình duyệt. Hãy tìm phần tử tốt nhất cho: '{goal}'
    Các phần tử hiện có: {elements}
    CHỈ trả về 'id' hoặc một CSS selector duy nhất. Nếu không có ID, hãy dùng text selector như \"text='Đăng nhập'\".
    """
    
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": prompt}]
    )
    return response.choices[0].message.content.strip()

Bước 3: Triển khai cú click chuột linh hoạt

Bây giờ chúng ta sẽ bọc logic vào một hàm linh hoạt. Nếu quá trình chờ (timeout) 2 giây kết thúc, AI sẽ tiếp quản để cứu vãn bài test.

async def resilient_click(page, selector, goal):
    try:
        # Thử cách nhanh và rẻ trước
        await page.click(selector, timeout=2000)
    except Exception:
        print(f"Selector thất bại. AI đang tìm kiếm: '{goal}'...")
        elements = await get_interactive_elements(page)
        new_selector = await ai_find_element(goal, elements)
        await page.click(new_selector)

Vượt xa hơn Selector: Xác thực bằng hình ảnh (Visual Assertions)

Xác thực hình ảnh là nơi các LLM đa phương thức (multi-modal) thực sự tỏa sáng. Các công cụ truyền thống so sánh từng pixel, vốn nổi tiếng là thiếu ổn định vì chỉ cần lệch một pixel cũng có thể gây ra lỗi giả. Với các mô hình Vision, bạn có thể đặt những câu hỏi ở cấp độ cao hơn: “Nút thanh toán có hiển thị và có màu đỏ không?” hoặc “Bố cục có bị vỡ trên thiết bị di động không?”

Gần đây, tôi đã thay thế 50 câu lệnh xác thực (assertions) riêng lẻ trong một luồng thanh toán bằng một phân tích ảnh màn hình duy nhất. Điều này đã phát hiện ra một lỗi CSS khi tiêu đề đè lên nút ‘Thanh toán ngay’—một lỗi mà các bài kiểm thử chức năng truyền thống sẽ bỏ lỡ vì về mặt kỹ thuật phần tử đó vẫn tồn tại trong DOM.

Bài học từ thực tế

Mặc dù kiểm thử bằng AI mang tính đột phá, nhưng nó không phải là chiếc đũa thần. Đầu tiên, hãy cân nhắc về chi phí. Việc gọi LLM cho mỗi hành động là rất tốn kém. Bạn chỉ nên kích hoạt AI như một cơ chế dự phòng để ngăn chặn các lỗi sai (false negatives) trong môi trường CI của mình.

Thứ hai, hãy quản lý kỳ vọng về độ trễ. Một selector Playwright tiêu chuẩn xử lý trong 5 mili giây, trong khi một cuộc gọi AI có thể mất từ 3 đến 5 giây. Hãy sử dụng AI trong giai đoạn phát triển để “khám phá” các selector hoặc như một lưới an toàn, nhưng đừng dựa dẫm vào nó cho mọi tương tác nếu bạn cần thực thi bộ test trong vòng 10 phút.

Cuối cùng, hãy luôn duy trì nhật ký kiểm tra (audit trail). Nếu AI nhấp nhầm nút vì prompt của bạn mơ hồ, bạn cần biết lý do tại sao. Tôi khuyên bạn nên lưu ảnh màn hình và ghi lại suy luận của AI mỗi khi logic tự phục hồi được kích hoạt.

Kết luận

Sự kết hợp giữa tốc độ thực thi của Playwright và khả năng suy luận của LLM đại diện cho thế hệ tiếp theo của đảm bảo chất lượng phần mềm. Chúng ta đang tiến tới một thế giới nơi các bài test là những tác nhân thông minh hiểu được ý định của ứng dụng. Hãy bắt đầu từ những việc nhỏ bằng cách triển khai tính năng tự phục hồi cho các thành phần dễ gãy nhất của bạn; đó là cách hiệu quả nhất để giảm bớt gánh nặng bảo trì mà không làm phức tạp hóa cơ sở hạ tầng của bạn.

Share: