BROWSER USE

- Browser Use Agents: give Browser Use a task and receive completed work. API V4 is current for new integrations.
- Browser Infrastructure: connect your agent or automation to managed browsers through SDK, REST, or CDP. Starts at $0.02/browser-hour.
- Developer tools: Open Source, Browser Harness, SDK, and MCP support the two products above.

[Developer Index](https://browser-use.com/index.md)
[Product Map](https://browser-use.com/llms.txt)
[Full Product Context](https://browser-use.com/llms-full.txt)
[Pricing](https://browser-use.com/pricing.md)
[Cloud Docs](https://docs.browser-use.com/cloud/quickstart)
[Open Source Docs](https://docs.browser-use.com/open-source/introduction)

---

# Automate X (Twitter) with an AI agent

Source: https://browser-use.com/skills/x. 2 files from github.com/browser-use/browser-harness, MIT licensed. Written for browser-harness: code calls its helpers (http_get, js, goto_url). Sites change; these notes describe what worked when last updated.

---

<!-- https://github.com/browser-use/browser-harness/blob/main/agent-workspace/domain-skills/x/article-source-recovery.md (updated 2026-07-13) -->

# Recover canonical sources from public X article cards

Use this when a public `x.com/<handle>/status/<id>` post contains an X Article card, but opening the card itself redirects an anonymous browser to login.

## Public status extraction

A public status page can render the post and article card without authentication even when the article route is gated. Inspect links in the post before clicking:

```javascript
Array.from(document.querySelectorAll('article a')).map(a => ({
  text: (a.innerText || '').trim(),
  href: a.href,
})).filter(x => x.href)
```

Article cards commonly point to `https://x.com/i/article/<id>`. An anonymous visit to that route may redirect to `x.com/i/jf/onboarding/web?...mode=login`; do not interpret this as evidence that the status itself is private or deleted.

## Cross-post fallback

Authors sometimes cross-post the same article on LinkedIn or another public profile and link the canonical external page there. A LinkedIn sign-in modal may cover the page visually while the public post text and anchors remain in the DOM.

Inspect the public DOM rather than entering credentials:

```javascript
({
  description: document.querySelector('meta[name="description"]')?.content || null,
  body: (document.body.innerText || '').slice(0, 12000),
  links: Array.from(document.querySelectorAll('a')).map(a => ({
    text: (a.innerText || '').trim(),
    href: a.href,
  })),
})
```

LinkedIn wraps external links as `https://www.linkedin.com/redir/redirect?url=<encoded-url>&...`. Decode the `url` query parameter, open that canonical URL directly, and validate the article title and content there.

## Guardrails

- Treat the X status and the author's canonical page as primary sources; comments and search snippets are secondary context.
- If no author-controlled canonical page is recoverable, cite the public status and mark the X Article body as inaccessible rather than reconstructing it from commentary.
- Never type credentials from screenshots or attempt to bypass a login wall.

---

<!-- https://github.com/browser-use/browser-harness/blob/main/agent-workspace/domain-skills/x/posting.md (updated 2026-05-04) -->

# X (Twitter) — Posting & Auth

## Login

Navigate to `https://x.com/i/flow/login` (not `https://x.com/`) to get the full sign-in modal. The homepage may render a truncated version of the auth buttons.

### Google sign-in (FedCM) — cannot be automated

The "Sign in as <user>" Google button uses Chrome's Federated Credential Management (FedCM) API. CDP mouse events (`Input.dispatchMouseEvent`) do **not** count as a trusted user gesture for FedCM, and the button lives in a sandboxed cross-origin iframe (`accounts.google.com`). Clicking it via automation has no effect.

**If the user wants Google sign-in, ask them to click the button themselves**, then wait for confirmation before proceeding.

### Username / password flow

Use the standard form on `https://x.com/i/flow/login`. Ask the user for credentials — do not guess or read them from screenshots.

## Composing and posting a tweet

Once logged in, the home feed is at `https://x.com/home`.

```python
import json

# Find the compose box and type
result = js(r'''
  var el = document.querySelector("[data-testid=\"tweetTextarea_0\"]");
  if (!el) return null;
  var r = el.getBoundingClientRect();
  return JSON.stringify({x: Math.round(r.x + r.width/2), y: Math.round(r.y + r.height/2)});
''')
if result is None:
    raise RuntimeError("compose textarea not found — are you logged in and on x.com/home?")
pos = json.loads(result)
click_at_xy(pos["x"], pos["y"])
type_text("hello world!")

# Find and click Post
btn = js(r'''
  var b = document.querySelector("[data-testid=\"tweetButtonInline\"]")
       || document.querySelector("[data-testid=\"tweetButton\"]");
  if (!b) return null;
  var r = b.getBoundingClientRect();
  return JSON.stringify({x: Math.round(r.x + r.width/2), y: Math.round(r.y + r.height/2)});
''')
if btn is None:
    raise RuntimeError("post button not found — did the compose surface fail to open?")
pos = json.loads(btn)
click_at_xy(pos["x"], pos["y"])
```

### Stable selectors

| Element | Selector |
|---|---|
| Compose textbox | `[data-testid="tweetTextarea_0"]` |
| Post button (home feed inline) | `[data-testid="tweetButtonInline"]` |
| Post button (modal) | `[data-testid="tweetButton"]` |

### Confirmation

A toast "Your post was sent." appears at the bottom of the page after a successful post. Verify with a screenshot.

## Gotchas

- The home feed compose area and the sidebar "Post" button both open a compose surface, but `tweetButtonInline` is specific to the inline feed composer. If you open a modal (e.g. via the sidebar "Post" button), use `tweetButton` instead.
- If the Grok side panel opens unexpectedly, click a neutral area (e.g. left sidebar) to dismiss it before interacting with the compose box.
- `twitter.com` redirects to `x.com` — all canonical URLs use `x.com`.
