{"record":{"id":"cf0a15d985716d67","repo":"xpzouying/xiaohongshu-mcp","slug":"s-w-cf0a15","errorCode":null,"errorMessage":"点击筛选选项「%s」失败: %w","messagePattern":"点击筛选选项「(.+?)」失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"xiaohongshu/search.go","lineNumber":136,"sourceCode":"\t\t}\n\t\thumanize.Delay(ctx, humanize.BeforeClick)\n\n\t\t// 等待筛选面板出现\n\t\tpage.MustWait(`() => document.querySelector('div.filter-panel') !== null`)\n\n\t\t// 记下筛选前的结果，用来判断筛选后的数据什么时候到位\n\t\tbefore := readFeedIDs(page)\n\n\t\t// 用 ClickNoWait：筛选面板是 hover 浮层，rod 的 WaitInteractable 会误判被遮挡而死等；\n\t\t// ClickNoWait 移进面板内选项（维持 hover、面板不关）再点。\n\t\tfor _, pf := range pending {\n\t\t\toption, err := findFilterOption(page, pf)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\thumanize.Delay(ctx, humanize.BeforeClick)\n\t\t\tif err := humanize.ClickNoWait(option); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"点击筛选选项「%s」失败: %w\", pf.option, err)\n\t\t\t}\n\t\t}\n\n\t\twaitFeedsChanged(page, before, 15*time.Second)\n\t}\n\n\tresult := page.MustEval(`() => {\n\t\tif (window.__INITIAL_STATE__ &&\n\t\t    window.__INITIAL_STATE__.search &&\n\t\t    window.__INITIAL_STATE__.search.feeds) {\n\t\t\tconst feeds = window.__INITIAL_STATE__.search.feeds;\n\t\t\tconst feedsData = feeds.value !== undefined ? feeds.value : feeds._value;\n\t\t\tif (feedsData) {\n\t\t\t\treturn JSON.stringify(feedsData);\n\t\t\t}\n\t\t}\n\t\treturn \"\";\n\t}`).String()","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/xpzouying/xiaohongshu-mcp/blob/332d196854a9eac0d2b8c2c0e3d0cc43139d724c/xiaohongshu/search.go#L118-L154","documentation":"After opening the filter panel, Search() locates each pending filter option via findFilterOption and clicks it with humanize.ClickNoWait (chosen because the panel is a hover overlay where rod's WaitInteractable misjudges occlusion). If the click fails (element detached when the hover overlay closed, overlay dismissed, timeout), the error is wrapped with this prefix naming the option text.","triggerScenarios":"Search with filters when: the hover panel closes between finding and clicking an option (mouse drifted, another overlay), the option element got re-rendered (stale node), ClickNoWait's pointer events were intercepted, or findFilterOption matched a hidden duplicate tag div that is not clickable.","commonSituations":"Applying multiple filters where clicking one option re-renders the panel, invalidating the next option element; viewport too small so the panel scrolls/closes; headless mouse behavior differences.","solutions":["Re-find each option element immediately before its click (no caching across iterations)","Move the mouse back into the panel before each subsequent click to keep it open","Retry the whole filter application once: re-open panel and apply remaining filters","If a specific option consistently fails, check whether XHS renders it as multiple div.tags and target the visible one"],"exampleFix":"// before\noption, err := findFilterOption(page, pf)\nhumanize.Delay(ctx, humanize.BeforeClick)\nif err := humanize.ClickNoWait(option); err != nil { ... }\n// after\n// re-find right before each click + one retry\nvar clickErr error\nfor retry := 0; retry < 2; retry++ {\n    option, err := findFilterOption(page, pf)\n    if err != nil { clickErr = err; continue }\n    if clickErr = humanize.ClickNoWait(option); clickErr == nil { break }\n}\nif clickErr != nil { return nil, fmt.Errorf(\"点击筛选选项「%s」失败: %w\", pf.option, clickErr) }","handlingStrategy":"retry","validationCode":"// verify each option is present and visible before the click sequence starts\nfor _, pf := range pendingFilters {\n    if _, err := findFilterOption(page, pf); err != nil {\n        return fmt.Errorf(\"option %q not found before clicking\", pf.option)\n    }\n}","typeGuard":null,"tryCatchPattern":"res, err := action.Search(ctx, keyword, filters...)\nif err != nil && strings.Contains(err.Error(), \"点击筛选选项\") {\n    // re-open the panel and re-apply from scratch; stale elements won't heal in place\n    time.Sleep(1 * time.Second)\n    return action.Search(ctx, keyword, filters...)\n}","preventionTips":["Always re-find option elements right before each click; never cache across clicks","Keep the pointer inside the panel between clicks so the hover overlay stays open","Apply filters one at a time and wait for the feed list to change between them","Test with a viewport large enough to render the whole panel"],"tags":["browser-automation","go","click","stale-element"],"backgroundTag":"stale-element-click-failed","analyzedSha":"332d196854a9eac0d2b8c2c0e3d0cc43139d724c","analyzedAt":"2026-09-05T22:22:55.988Z","contentChangedAt":"2026-09-05T22:22:55.988Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}