Playwright 腳本為何會在網站改版後失效
Why Playwright Scripts Break When the Site Changes
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 symptom | Fix |
|---|---|
| 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:
| Option | What it does, and the catch |
|---|---|
| Fix by hand | Open the trace, pick a new locator with codegen. Catch: developer time on every break |
| Playwright healer agent | Replays 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
- All Playwright locator types: Playwright docs
- Playwright, Selenium, Puppeteer and agents compared: browser automation tools
- Run the same Playwright script on a remote browser: /playwright
來源:Browser Use · browser-use.com