{"record":{"id":"a35af2dc1afa3772","repo":"moeru-ai/airi","slug":"like-button-not-found","errorCode":null,"errorMessage":"Like button not found","messagePattern":"Like button not found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"integrations/twitter-services/src/core/services/tweet.ts","lineNumber":117,"sourceCode":"    }\n    catch (error: unknown) {\n      console.error('Error searching tweets:', error)\n      throw new Error(`Failed to search tweets: ${errorToMessage(error)}`)\n    }\n  }\n\n  /**\n   * Likes a tweet with the given ID\n   */\n  async function likeTweet(tweetId: string): Promise<boolean> {\n    try {\n      const page = ctx.page\n      await page.goto(`${TWITTER_BASE_URL}/i/status/${tweetId}`)\n      await page.waitForSelector(SELECTORS.TIMELINE.TWEET)\n\n      const likeButton = await page.$(SELECTORS.TIMELINE.LIKE_BUTTON)\n      if (!likeButton) {\n        throw new Error('Like button not found')\n      }\n\n      // Check if already liked\n      const isAlreadyLiked = await page.$eval(\n        SELECTORS.TIMELINE.LIKE_BUTTON,\n        el => el.getAttribute('aria-pressed') === 'true',\n      )\n\n      if (!isAlreadyLiked) {\n        await likeButton.click()\n        // Wait for the like to register\n        await page.waitForFunction(\n          `document.querySelector('${SELECTORS.TIMELINE.LIKE_BUTTON}')?.getAttribute('aria-pressed') === 'true'`,\n          { timeout: 5000 },\n        )\n      }\n\n      return true","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/integrations/twitter-services/src/core/services/tweet.ts#L99-L135","documentation":"Thrown by likeTweet when page.$(SELECTORS.TIMELINE.LIKE_BUTTON) returns null — after the tweet page loaded (waitForSelector for the tweet passed) but the like button element was not found in the DOM. This is a plain Error, distinct from the surrounding catch-all (it is thrown inside the try, so it gets re-wrapped by the 'Failed to like tweet' catch).","triggerScenarios":"Navigating to /i/status/<tweetId> where the tweet node renders but the like-button selector does not match: SELECTORS.TIMELINE.LIKE_BUTTON is stale after an X UI change; the tweet is protected/deleted so the action row is absent; the page shows a 'This post is unavailable' state; ARIA/aria-pressed attribute moved to a nested element.","commonSituations":"X redesigned the action bar so the data-testid/selector changed; tweet id is invalid or belongs to a deleted/protected post; the logged-in session lacks permission to see the action row; slow render meant the button existed but page.$ ran before paint (rare, since waitForSelector for TWEET already passed).","solutions":["Update SELECTORS.TIMELINE.LIKE_BUTTON to the current X data-testid (commonly '[data-testid=\"like\"]').","Verify the tweetId resolves to an existing, viewable tweet.","Ensure the session is authenticated and the tweet is not protected/age-restricted.","Re-query the button with a short waitForSelector(LIKE_BUTTON) before page.$ to handle render lag."],"exampleFix":"// before\nconst likeButton = await page.$(SELECTORS.TIMELINE.LIKE_BUTTON)\nif (!likeButton) {\n  throw new Error('Like button not found')\n}\n\n// after — wait for the button and confirm the tweet is viewable\nawait page.waitForSelector(SELECTORS.TIMELINE.LIKE_BUTTON, { timeout: 5000 })\nconst likeButton = await page.$(SELECTORS.TIMELINE.LIKE_BUTTON)\nif (!likeButton) {\n  throw new Error('Like button not found (tweet may be unavailable)')\n}","handlingStrategy":"validation","validationCode":"await page.waitForSelector(SELECTORS.TIMELINE.LIKE_BUTTON, { timeout: 5000 })\nconst likeButton = await page.$(SELECTORS.TIMELINE.LIKE_BUTTON)\nif (!likeButton) throw new Error('Like button not found (tweet may be unavailable)')","typeGuard":"function isLikeButtonNotFoundError(e: unknown): boolean {\n  // Note: likeTweet wraps this into 'Failed to like tweet: Like button not found'\n  return e instanceof Error && /Like button not found/.test(e.message)\n}","tryCatchPattern":"try {\n  await tweetServices.tweet.likeTweet(tweetId)\n} catch (e) {\n  if (e instanceof Error && /Like button not found/.test(e.message)) {\n    // update SELECTORS.TIMELINE.LIKE_BUTTON or verify the tweet is viewable\n  } else throw e\n}","preventionTips":["Update LIKE_BUTTON selector to the current X data-testid when the UI changes.","Verify the tweetId is valid, existing, and viewable before liking.","Ensure the session is authenticated so the action row renders.","waitForSelector on the like button before querying it to absorb render lag."],"tags":["twitter","puppeteer","selector","like","selector-drift"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}