name: facebook-automation description: Facebook automation including posting with privacy settings, interactions, messaging, and navigation workflows allowed-tools: browser_navigate browser_click browser_type browser_fill_form browser_get_snapshot browser_screenshot browser_wait browser_navigate_back browser_get_page_info browser_hover browser_press_key browser_evaluate browser_scroll write_todos browser_extract_posts
Facebook Automation Skill
Automation guide for Facebook web interface - posts, privacy settings, interactions, and navigation.
When to Use This Skill
- Create posts with specific privacy settings (Public, Friends, Only me)
- Navigate Facebook pages and features
- Interact with posts (like, comment, share)
- Send messages via Messenger
If you received a Success Plan with working_selectors, try those patterns first.
Critical Rules
🚫 NEVER USE browser_evaluate FOR CLICKING (CAUSES INFINITE LOOPS)
STOP! If you're about to use browser_evaluate to click, DON'T.
This is the #1 cause of agent failures. browser_evaluate for clicking:
- Returns null (element not found in DOM)
- Agent retries with different JS variations
- Loops 25+ times, never works
If browser_evaluate returns null ONCE:
- STOP immediately
- Call
browser_get_snapshot() - Use
browser_click(ref="eXX", force=True)with ref from snapshot - If element not in snapshot, the dialog may not have opened - see "Dialog Not Opening" below
⚠️ REFS BECOME STALE AFTER EVERY ACTION
After ANY click, type, or navigation:
- ALL refs from previous snapshot are INVALID
- You MUST call
browser_get_snapshot()to get fresh refs - The same ref number (e.g.,
e78) now points to a DIFFERENT element
Example of what goes wrong:
browser_get_snapshot()
# e78 = button "What's on your mind?"
browser_click(ref="e78", force=True) # Opens dialog
browser_click(ref="e78", force=True) # WRONG! e78 is now "Live video"!
Correct pattern:
browser_get_snapshot()
# e78 = button "What's on your mind?"
browser_click(ref="e78", force=True)
browser_get_snapshot() # GET FRESH REFS!
# Now e78 might be something completely different
# e42 = button "Friends" (privacy button)
browser_click(ref="e42", force=True)
� DIALOG NOT OPENING?
If you clicked to open a dialog but snapshot doesn't show dialog elements:
-
browser_wait(time=2)- give React time to render -
browser_get_snapshot()- check again - If still no dialog, try clicking again with
force=True - If still fails after 2 attempts, report the issue
🔄 LOOP PREVENTION
If same action fails 2 times:
- STOP trying that approach
- Try DIFFERENT strategy (different tool, different selector)
- Get fresh snapshot
- Example: If
browser_evaluatereturns null 2x → Usebrowser_clickwith ref
DO NOT repeat the same failed pattern more than 2 times.
1. ARIA Snapshot & Ref System
The browser_get_snapshot() tool returns a YAML accessibility tree using Playwright's locator.aria_snapshot() API:
- navigation "Facebook":
- link "Home" [ref=e0]
- button "Search" [ref=e1]
- main:
- button "What's on your mind?" [ref=e15]
- dialog "Create post":
- textbox "What's on your mind?" [ref=e20]
- button "Public" [ref=e21]
- button "Post" [ref=e25]
Format explanation:
-
role "accessible name" [ref=eN]- Each element with unique ref - Roles:
button,link,textbox,checkbox,radio,heading,dialog, etc. - Attributes:
[checked],[disabled],[expanded],[pressed=true]
2. Mandatory Workflow (Observe → Think → Act → Verify)
BEFORE ANY ACTION:
# OBSERVE - Get FRESH snapshot
browser_get_snapshot()
# THINK - MUST explicitly list elements you see:
# I see in this snapshot:
# - button "Close composer dialog" [ref=e31] ← NOT what I want!
# - button "Friends" [ref=e42] ← This is the privacy button (shows current setting)
# - button "Photo/video" [ref=e43] ← NOT privacy
# - button "Post" [ref=e50] ← Submit button
#
# I need to change privacy. The button showing "Friends" is ref=e42.
# ACT - Use the ref from THIS snapshot
browser_click(ref="e42", force=True)
# VERIFY - Get NEW snapshot (previous refs are now stale!)
browser_wait(time=1)
browser_get_snapshot() # REQUIRED after every action
# Now analyze the NEW refs before next action
3. Must-Follow Principles
| Rule | Why |
|---|---|
| Get fresh snapshot after EVERY action | Refs become stale immediately |
Always force=True on clicks |
Facebook has invisible overlays |
browser_wait(time=1-2) after actions |
React needs time to re-render |
| List elements before clicking | Prevents clicking wrong button |
| Complete ALL dialog steps | Selecting ≠ confirming |
| Verify with snapshot before "done" | Task isn't complete until verified |
| Read button names carefully | "Close composer" ≠ privacy button! |
| Max 2 retries per approach | If it fails twice, try different strategy |
Tool Reference
browser_get_snapshot
Get ARIA accessibility snapshot with element refs.
browser_get_snapshot()
Output: YAML accessibility tree with [ref=eN] markers on each element.
browser_click
Click element using ref (preferred) or selector.
# Using ref (RECOMMENDED)
browser_click(ref="e42", force=True)
# Using selector (fallback)
browser_click(selector="button=Post", force=True)
Always use force=True on Facebook.
browser_type
Type text into an input field.
# Using ref
browser_type(ref="e23", text="Hello world")
# Using selector
browser_type(selector="div[contenteditable='true'][role='textbox']", text="Hello")
browser_wait
Wait for UI updates after actions.
browser_wait(time=1) # 1 second for simple actions
browser_wait(time=2) # 2 seconds for navigation/dialogs
browser_press_key
Press keyboard keys.
browser_press_key(key="Enter")
browser_press_key(key="Escape")
browser_hover
Hover to reveal hidden UI (like reactions).
browser_hover(ref="e28")
Selector Patterns
Facebook Post Selectors (CRITICAL - 2025)
⚠️ WRONG SELECTORS (Don't Use):
-
article- Facebook doesn't use semantic HTML articles -
div[role="article"]- Posts don't have this ARIA role -
div[data-ad-preview="message"]- Only works for ads, not organic posts
✅ CORRECT SELECTORS FOR FACEBOOK POSTS (2025):
Method 1: data-testid Selectors (Most Reliable)
# Feed container
"div[role='feed']"
# Individual posts
"div[data-testid='feed_story']"
# Post content elements
"div[data-testid='post_message']" # Post text
"a[data-testid='story_author_link']" # Author link
"span[data-testid='story_timestamp']" # Timestamp
"img[data-testid='story_photo']" # Post images
"div[data-testid='feed_loading_indicator']" # Loading spinner (lazy load)
Method 2: ARIA Role-Based Selectors (Resilient)
# Use getByRole pattern (most stable against DOM changes)
page.get_by_role("feed") # Feed container
page.get_by_role("article") # Individual posts (if present)
page.get_by_role("link", name="author") # Author links
page.get_by_role("img") # Images
Method 3: ARIA Snapshot Refs (Preferred for Interaction)
# 1. Get snapshot
browser_get_snapshot()
# 2. Parse snapshot YAML to find post refs
# Posts appear as "generic" or custom roles with accessible names
# 3. Use refs for precise interaction
browser_click(ref="e42", force=True)
Post Extraction Workflow (Full Example)
# Step 1: Navigate to feed
browser_navigate(url="https://www.facebook.com")
browser_wait(time=2)
# Step 2: Get initial snapshot
browser_get_snapshot()
# Step 3: Scroll to load more posts (lazy loading)
browser_scroll(direction="down", amount=500)
browser_wait(time=2) # Wait for lazy load to complete
# Step 4: Get fresh snapshot with new posts
browser_get_snapshot()
# Step 5: Extract posts using correct selectors
posts = await page.locator("div[data-testid='feed_story']").all()
# Step 6: Parse each post for content
for post in posts:
author = post.locator("a[data-testid='story_author_link']")
text = post.locator("div[data-testid='post_message']")
timestamp = post.locator("span[data-testid='story_timestamp']")
Lazy Loading Detection
# Check if more posts are loading
loading = await page.locator("div[data-testid='feed_loading_indicator']").count() > 0
# Scroll until no new content appears
prev_count = 0
while True:
await page.locator("div[data-testid='feed_story']").count() == prev_count:
browser_scroll(direction="down", amount=500)
browser_wait(time=2)
current_count = await page.locator("div[data-testid='feed_story']").count()
if current_count == prev_count:
break # No new posts loaded
prev_count = current_count
Selector Priority (Use This Order)
-
data-testid attributes (most stable) -
div[data-testid='feed_story'] -
ARIA roles -
div[role='feed'] -
ARIA snapshot refs - Use after
browser_get_snapshot() -
User-facing names -
button="Post",text="Like" - CSS selectors (last resort) - Brittle, breaks often
Selector Patterns
Priority order (most reliable first):
-
ref="e42"- From snapshot (BEST) -
button=Name- Buttons by accessible name -
radio=Option- Radio buttons in dialogs -
[aria-label="Label"]- By aria-label -
role=textbox- For input fields - CSS selectors - Last resort
Common Facebook Selectors
| Element | Selector |
|---|---|
| Open composer | button=What's on your mind? |
| Text input | div[contenteditable='true'][role='textbox'] or role=textbox |
| Post button | button=Post |
| Next button | button=Next |
| Done button | button=Done |
Privacy Selectors
⚠️ CRITICAL: Privacy button has DYNAMIC name
The privacy button's accessible name includes the current setting PLUS dynamic friend names:
- Full name:
"Edit privacy. Sharing with [setting]" - Setting can be: "Public", "Friends", "Only me", "Friends except: [names]"
ALWAYS use the STABLE PREFIX (never match dynamic suffix):
| Current Privacy | Full Accessible Name | ALWAYS Use This Selector |
|---|---|---|
| Public | "Edit privacy. Sharing with Public" | button="Edit privacy. Sharing with" |
| Friends | "Edit privacy. Sharing with Friends" | button="Edit privacy. Sharing with" |
| Only me | "Edit privacy. Sharing with Only me" | button="Edit privacy. Sharing with" |
| Friends except | "Edit privacy. Sharing with Friends except: John, Jane..." | button="Edit privacy. Sharing with" |
WRONG: ❌ button="Friends except..." (dynamic suffix, will fail)
WRONG: ❌ button="Public" (doesn't match full name)
CORRECT: ✅ button="Edit privacy. Sharing with" (stable prefix)
In privacy dialog (after clicking privacy button):
| Option | Selector |
|---|---|
| Public | radio=Public |
| Friends | radio=Friends |
| Only me | radio=Only me |
| Custom | radio=Custom |
Core Workflows
Create Post with Privacy Setting
# 1. Navigate to Facebook
browser_navigate(url="https://www.facebook.com")
browser_wait(time=2)
# 2. Get snapshot to find composer
browser_get_snapshot()
# Find button containing "What's on your mind"
# 3. Open composer
browser_click(selector='button=What\'s on your mind', force=True)
browser_wait(time=1)
# 4. Get snapshot - find privacy button (shows current setting)
browser_get_snapshot()
# Look for button starting with "Edit privacy. Sharing with" [ref=eXX]
# 5. Click privacy button using STABLE PREFIX selector
browser_click(selector='button="Edit privacy. Sharing with"', force=True)
browser_wait(time=0.5)
# 6. Select privacy option
browser_click(selector='radio=Only me', force=True)
browser_wait(time=0.5)
# 7. CRITICAL - Confirm with Done
browser_click(selector='button=Done', force=True)
browser_wait(time=0.5)
# 8. Type post content
browser_type(selector="role=textbox", text="Your post content")
browser_wait(time=1)
# 8b. CRITICAL - Refresh snapshot after typing (refs become stale)
browser_get_snapshot()
# 9. Click Next (if shown)
browser_click(selector='button=Next', force=True)
browser_wait(time=2)
# 10. Get snapshot, find Post button
browser_get_snapshot()
# 11. Click Post
browser_click(selector='button=Post', force=True)
browser_wait(time=3)
# 12. VERIFY - Must see post in feed
browser_get_snapshot()
Like a Post
browser_get_snapshot()
# Find Like button ref
browser_click(ref="e28", force=True) # Like button ref
browser_wait(time=0.5)
browser_get_snapshot() # Verify liked
React with Emoji (Love, Haha, etc.)
browser_get_snapshot()
# Hover to reveal reactions
browser_hover(ref="e28") # Like button ref
browser_wait(time=1)
browser_get_snapshot()
# Click specific reaction
browser_click(ref="e35", force=True) # Love reaction ref
Comment on Post
browser_get_snapshot()
browser_click(ref="e32", force=True) # Comment button
browser_wait(time=0.5)
browser_type(ref="e45", text="Great post!") # Comment input
browser_press_key(key="Enter")
browser_wait(time=1)
browser_get_snapshot() # Verify comment appears
Troubleshooting
Common Failure Patterns
| Problem | What Happened | Fix |
|---|---|---|
| Clicked wrong button | Used stale ref (e78 was "What's on your mind" but became "Live video") | ALWAYS browser_get_snapshot() after any action |
| Closed dialog accidentally | Clicked "Close composer dialog" instead of privacy button | Read button names in snapshot before clicking |
| Got stuck in loop | Clicking same ref repeatedly after page changed | Refs are per-snapshot; get fresh snapshot first |
| Privacy not changed | Selected option but didn't click "Done" | Complete full dialog: select → Done → verify |
Quick Fixes
| Problem | Solution |
|---|---|
| Ref not found | browser_get_snapshot() to refresh refs |
| Click not working | Add force=True, try browser_hover first |
| Element not visible | browser_wait(time=2), check for overlays |
| Dialog not responding | Close with browser_press_key(key="Escape") |
| Page stale | browser_navigate to refresh |
Best Practices
- Always snapshot first - Get refs before any action
- Use refs for disambiguation - Multiple "Post" buttons? Use exact ref
- Wait after actions - 1-2 seconds for React re-renders
- Verify before "done" - Take final snapshot to confirm result
- Follow dialog sequences - select → confirm → submit → verify
- Scroll down/up to load more posts - if you want more posts beyond what's in the snapshot, you should scroll down/up the webpage
Facebook UI Notes
- React/SPA - Elements re-render; always refresh snapshot after changes
- Portals - Dialogs render outside parent containers
-
Overlays - Multiple modals can stack; use
force=True - Lazy loading - Scroll to load more content
- Privacy button - Shows CURRENT setting, not a fixed label
chat Comments (0)
Sign in to join the discussion and leave a comment.
Skill Details
Related Skills
Build your own?
Join 12,000+ developers contributing to the Claude ecosystem.
No comments yet. Be the first to share your thoughts!