{"record":{"id":"da396feca6a9744b","repo":"jackwener/OpenCLI","slug":"button-not-found-follow-button-not-on-profile-pag","errorCode":null,"errorMessage":"BUTTON_NOT_FOUND: Follow button not on profile page (logged out, private account, or selectors changed)","messagePattern":"BUTTON_NOT_FOUND: Follow button not on profile page \\(logged out, private account, or selectors changed\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"clis/tiktok/follow.js","lineNumber":54,"sourceCode":"  ensureLoggedInOrThrow();\n  ensureNoRateLimitOrThrow();\n\n  const FOLLOW_LABELS = ['Follow', '关注', 'フォロー'];\n  const FOLLOWING_LABELS = ['Following', '已关注', 'フォロー中'];\n  const FRIENDS_LABELS = ['Friends', '互关', 'フレンド'];\n  const ALREADY_LABELS = FOLLOWING_LABELS.concat(FRIENDS_LABELS);\n\n  // Idempotent fast path: already in target state.\n  if (buttonExists(FRIENDS_LABELS)) {\n    return [{ username, url: ${JSON.stringify(TIKTOK_HOST)} + '/@' + encodeURIComponent(username), result: 'already-friends' }];\n  }\n  if (buttonExists(FOLLOWING_LABELS)) {\n    return [{ username, url: ${JSON.stringify(TIKTOK_HOST)} + '/@' + encodeURIComponent(username), result: 'already-following' }];\n  }\n\n  const followBtn = findButtonByText(FOLLOW_LABELS);\n  if (!followBtn) {\n    throw new Error('BUTTON_NOT_FOUND: Follow button not on profile page (logged out, private account, or selectors changed)');\n  }\n\n  const target = followBtn.closest('button') || followBtn.closest('[role=\"button\"]') || followBtn;\n  target.click();\n\n  // State verification: button text should flip to Following / 已关注 etc.\n  const flipped = await waitFor(() => buttonExists(ALREADY_LABELS), { timeoutMs: 5000 });\n  if (!flipped) {\n    ensureNoRateLimitOrThrow();\n    throw new Error('STATE_VERIFY_FAIL: follow button did not flip to Following within 5s; relation may not have been recorded');\n  }\n\n  // Re-check rate limit AFTER click — TikTok sometimes flashes captcha\n  // mid-flight even when the button text appears to flip.\n  ensureNoRateLimitOrThrow();\n\n  return [{\n    username,","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/tiktok/follow.js#L36-L72","documentation":"The injected follow script finds the Follow button by text label, but neither a Follow label nor a Following label was present on the profile page. The library throws a plain Error prefixed BUTTON_NOT_FOUND, listing the plausible causes: logged out, private account, or changed selectors.","triggerScenarios":"findButtonByText(FOLLOW_LABELS) returned null on the profile page: not logged in (buttons render differently), the target account is private/blocked/unavailable, the page showed a login wall, or TikTok renamed button labels so FOLLOW_LABELS/FOLLOWING_LABELS no longer match.","commonSituations":"Session expired so TikTok shows logged-out profile markup, following a private or region-blocked account, non-English locale where button text isn't in FOLLOW_LABELS, or TikTok UI redesign changing button markup.","solutions":["Verify the Chrome profile is logged in to TikTok before running the follow command","Open the target profile manually to confirm it's public and shows a Follow button","Check locale — if the UI is in a language whose label isn't in FOLLOW_LABELS, force en or add the label","Update the library if TikTok changed the button text/DOM structure","Catch the error and inspect a page screenshot to see what the profile actually rendered"],"exampleFix":"// before\nawait followUser(page, { username: 'target' });\n// after\ntry {\n  await followUser(page, { username: 'target' });\n} catch (e) {\n  if (/BUTTON_NOT_FOUND/.test(e.message)) {\n    await ensureTikTokLogin(page); // re-auth then retry\n    await followUser(page, { username: 'target' });\n  } else throw e;\n}","handlingStrategy":"try-catch","validationCode":"const btn = await page.evaluate(() => !!document.querySelector('button')) && await page.content().then(html => /Follow|Following|关注/i.test(html));\nif (!btn) throw new Error('Follow/Following button text not present — check login state and locale');","typeGuard":"function isButtonNotFound(e) { return /BUTTON_NOT_FOUND/.test(e.message); }","tryCatchPattern":"try {\n  await followUser(page, { username });\n} catch (e) {\n  if (isButtonNotFound(e)) {\n    await ensureTikTokLogin(page);      // most common cause: logged out\n    await followUser(page, { username });\n  } else throw e;\n}","preventionTips":["Ensure the profile is logged in before following","Only target public, non-blocked accounts","Force an English (or supported) locale so button labels match FOLLOW_LABELS","Take a screenshot on failure to see what actually rendered","Update the library when TikTok redesigns button markup"],"tags":["tiktok","dom","selectors","auth","scraping"],"backgroundTag":"element-not-found","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}