跳到正文
Browser Use·· 2 小時前AI 評分60

Playwright 腳本為何會在網站改版後失效

Why Playwright Scripts Break When the Site Changes

AI 導讀

Playwright 腳本若依賴 CSS 類別、DOM 位置、XPath 或生成式 ID 定位元素,網站改版後即使頁面外觀不變,也可能失效。文章建議改用角色、標籤、文字或自訂 test ID 定位,並以 locator 的自動等待及 web-first assertions 取代固定等待時間。

正文

Your script finds elements by how the page is built (CSS classes, DOM position, XPath, generated IDs), and those change on redesigns even when the page looks the same to a user. Find elements by what a user sees (role, label, text, or a test ID you own), let Playwright wait for them, and on sites you don't control, plan for drift with a repair step.

We make Browser Use (the last two options below). Playwright facts link to playwright.dev, checked October 27, 2026.

Brittle vs resilient

// Breaks when a designer renames a class or wraps the button in another div
await page.locator("#app > div:nth-child(2) > button.btn-x7f3").click();
await page.waitForTimeout(3000); // a guess at how long the page takes
 
// Finds the button the way a user does; waits until it can be clicked
await page.getByRole("button", { name: "Sign in" }).click();
await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible();

Causes, symptoms, fixes

Cause and symptomFix
Brittle CSS or XPath selectors. Timeout waiting for a locator after a redesign.Playwright does not recommend CSS or XPath. Use getByRole, getByLabel, getByText. Codegen picks role, text and test ID locators for you
Generated class names, dynamic IDs. Works today, fails after the next deploy.Never select on a generated value such as .css-1x2y3z or #input-4821. In your own app, add a test ID and use getByTestId, which Playwright calls the most resilient option
Timing and races. Passes locally, flaky in CI.Act through locators: a click waits until the element is visible, stable, receives events and is enabled (auto-waiting). Replace waitForTimeout with web-first assertions such as toBeVisible()
A new element matches too. The action throws: locators are strict.Narrow with filter() or chaining. Avoid first() and nth(): when the page changes, Playwright may click an element you did not intend
Copy changes. "Submit order" became "Place order".Your app: a test ID survives text and role changes. Someone else's site: match the stable part of the name with a regex
A/B tests, layout variants. Fails on some runs only.Locate by a role and name present in every variant. In your own app, force one variant in test setup
iframes, shadow DOM. Visible in the screenshot, not found by the locator.iframes: frameLocator(). Shadow DOM: locators pierce open shadow roots, except XPath; closed roots are not supported
Session expired. The run lands on the login page.Check the URL after goto and fail loudly. Playwright: "delete the stored state when it expires" (auth)
Cookie banners, popups. An overlay covers the target until timeout.For overlays that appear unpredictably, page.addLocatorHandler() (since v1.42) runs your handler before an action when the overlay is visible. Dismiss predictable ones in your normal flow

On sites you don't control you can't add test IDs, so expect drift: detect it, alert, repair.

When selectors drift

Your options, from manual to self-healing:

OptionWhat it does, and the catch
Fix by handOpen the trace, pick a new locator with codegen. Catch: developer time on every break
Playwright healer agentReplays the failing steps, inspects the current UI, suggests a patch such as a locator update, and reruns until it passes or its guardrails stop it (docs). Catch: built for Playwright Test suites, run from an AI coding tool; review its patch
Agent fallback you write (Browser Use, ours)Keep your Playwright steps. When one fails, an agent in the same Chrome re-finds the element from the task; both share one browser over CDP (example). Catch: model tokens on each fallback; check what it did
Rerunnable script (Browser Use Cloud, ours)An agent saves a working script. Later runs reuse it and, when the task asks, repair it (self-heal) if the site changed (docs). Catch: every run still starts a model and uses tokens (pricing)

The fallback in Python. Start Chrome with --remote-debugging-port=9222 --user-data-dir=/tmp/pw-profile; since Chrome 136 the port is ignored on your default profile. Browser(cdp_url=...) attaches to that Chrome (docs); ChatBrowserUse reads BROWSER_USE_API_KEY (docs).

import asyncio
from playwright.async_api import async_playwright, TimeoutError as PlaywrightTimeout
from browser_use import Agent, Browser, ChatBrowserUse
 
CDP = "http://localhost:9222"
URL = "https://example.com/reports"
 
async def main():
    async with async_playwright() as p:
        browser = await p.chromium.connect_over_cdp(CDP)
        page = await browser.contexts[0].new_page()
        await page.goto(URL)
        try:
            await page.get_by_role("button", name="Export CSV").click(timeout=10_000)
        except PlaywrightTimeout:
            print("selector drifted, falling back to the agent")  # each one is a locator to fix
            agent = Agent(task=f"On {URL}, export the table as CSV",
                          browser=Browser(cdp_url=CDP), llm=ChatBrowserUse())
            await agent.run()
 
asyncio.run(main())

More

來源:Browser Use · browser-use.com