Files
workbuddy_skills/browser-harness/agent-workspace/domain-skills/x/posting.md
T
admin 777f7fe5d0 重开仓库内容:改推五个技能(browser-harness / humanizer / humanizer-zh / product-planning / session-mechanism)
按授权清空原有内容后重新提交(原 oil-ui-pro 一并移出,可从历史恢复)。
browser-harness 剔除 .venv 等运行环境;根 .gitignore 补记 .venv/ 与 node_modules/。
2026-10-07 16:06:02 +08:00

2.7 KiB

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 " 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.

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.