Google Ads management via Playwright browser automation — navigation paths, campaign monitoring, conversion tracking, tag management, and performance analysis. Use when user says "check ads", "ad performance", "Google Ads", "campaign metrics", "conversion tracking", "ad budget", "check conversions", "Google tag", "ad management", "A/B test ads", "optimize ads", "ad spend", "bid management", "ad blocker dialog", or any Google Ads activity.
Google Ads Playwright Skill
Automate Google Ads management via Playwright browser control. This skill documents navigation paths, element targeting, and procedures for monitoring and managing {{PARENT_1}}'s Google Ads account.
✅ Browser Isolation — Resolved via playwright-services Extension
Each service now has its own isolated persistent browser profile via the playwright-services extension (.github/extensions/playwright-services/extension.mjs). Google Ads uses ~/.playwright-profiles/google-ads/ and LinkedIn uses ~/.playwright-profiles/linkedin/. They can run concurrently without conflict.
Preferred tools: Use playwright_service_open({ service: "google-ads" }), playwright_service_navigate, playwright_service_click, playwright_service_snapshot, etc. These route to the isolated Google Ads profile automatically.
Defense-in-depth: The cron schedule still places google-ads-daily-metrics at 17:43 CT (after LinkedIn's last cycle at 17:18 CT) as an extra safety layer, but the architectural isolation means concurrent access no longer causes navigation conflicts.
Architecture details: See .github/skills/linkedin-playwright/references/PLAYWRIGHT_MULTI_SESSION_ARCHITECTURE.md and data/playwright/services.json for the full service registry.
Critical Rules — Read These First
Authentication is manual. {{PARENT_1}} must be signed into Google Ads in the Playwright browser session before any automation runs. If not authenticated, skip gracefully and notify {{PARENT_1}}.
Always dismiss the ad blocker dialog first. Google Ads shows a persistent "Turn off ad blockers" warning. Dismiss it before interacting with any page elements.
Never change bids, budgets, or campaign settings without explicit approval. Read-only operations (metrics, conversions, tag status) are safe. Any write operation requires {{PARENT_1}}'s confirmation.
Pages are dynamic and slow. Always use waitForSelector or waitForTimeout after navigation. Google Ads loads content asynchronously — elements may not exist immediately.
Log every discovery. When the daily exploration cron finds new navigation paths or UI patterns, append them to this skill file and update the scripts.
Use ref attributes for targeting. Google Ads elements often have ref="e941" style attributes that are more stable than class names.
Campaign creation via Playwright works — passkey re-auth opens in a separate tab. Google requires passkey/biometric verification when saving the Keywords & ads step. The passkey challenge opens in a new browser tab while the original tab advances to the Budget step. The wizard can continue through Budget → Review → Publish without completing the passkey. Confirmed 2026-05-17: "Agentic DevOps Search" campaign (ID 23860997035) published successfully via Playwright despite passkey popup. The re-auth triggers on the step TRANSITION, not on content changes. Previous behavior (May 2026): passkey sometimes fully blocked the wizard — this may depend on session state or Google A/B testing.
Use page.fill() for form inputs, NOT DOM .value = x. Google Ads uses Angular which doesn't detect DOM-level value changes. page.fill() properly triggers change detection and input events.
Conversions are GA4-imported (not standalone Google Ads tags)
Google tag has 0 hits (freshly set up as of 2026-05-11)
Common Procedures
1. Dismiss Ad Blocker Dialog
Google Ads shows a persistent "Turn off ad blockers" dialog. This MUST be dismissed before any other interaction.
// See: data/scripts/google-ads/dismiss-ad-blocker.js// Try to dismiss any ad blocker warning dialogconst dismissBtn = await page.$('button[aria-label="Close"], [role="dialog"] button');
if (dismissBtn) {
await dismissBtn.click();
await page.waitForTimeout(500);
}
// Alternative: press Escapeawait page.keyboard.press('Escape');
await page.waitForTimeout(500);
2. Check Authentication
Before any operation, verify the user is logged in:
// Navigate to campaigns dashboardawait page.goto('https://ads.google.com/aw/campaigns');
await page.waitForTimeout(3000);
// Check if we're on a login pageconst url = page.url();
if (url.includes('accounts.google.com') || url.includes('signin')) {
console.log('NOT_AUTHENTICATED: {{PARENT_1}} needs to sign in manually');
return;
}
// Check for account selector or dashboard contentconst hasDashboard = await page.$('[class*="campaign"], [class*="dashboard"]');
if (!hasDashboard) {
console.log('AUTH_UNCLEAR: Page loaded but dashboard not detected');
}
3. View Campaign Performance
// Navigate to campaignsawait page.goto('https://ads.google.com/aw/campaigns');
await page.waitForTimeout(3000);
// Dismiss ad blocker if presentawait page.keyboard.press('Escape');
await page.waitForTimeout(500);
// Read campaign metrics from the tableconst metrics = await page.evaluate(() => {
const rows = document.querySelectorAll('table tbody tr');
returnArray.from(rows).map(row => {
const cells = row.querySelectorAll('td');
returnArray.from(cells).map(cell => cell.textContent?.trim());
});
});
console.log(JSON.stringify(metrics, null, 2));
URL filter input: [aria-label="Enter a site to filter unrelated keywords"]
Get results button: button:has-text("Get results")
Language selector: looks for "English (default)" text near translate icon
Location selector: "United States" near location_on icon
Interaction notes (2026-05-22):
Feature announcement modal (pane-id="KP--5" inside #base-root-overlay-container-KP) intercepts ALL pointer events on page load — must call .remove() on overlay before clicking
Ad blocker warning renders at bottom of page ("Turn off ad blockers") — may prevent search results API call from loading
Input values must be set via page.fill() (not DOM value assignment) — Angular-based form
The "Discover new keywords" panel is inline on the home page (not a separate route)
Search results navigate to a new URL path (observed pattern, not confirmed — ad blocker prevented)
Navigation within Tools > Planning menu:
Keyword Planner → /aw/keywordplanner/home
Performance Planner → (not yet explored)
Reach Planner → (not yet explored)
App advertising hub → (not yet explored)
Use role attributes: [role="button"], [role="dialog"], [role="tab"]
Use aria-label: [aria-label="Close"], [aria-label="Settings"]
Text selectors: text=Campaigns, text=Conversions
Avoid class names: Google Ads uses obfuscated class names that change between deploys
Handling Dynamic Content
// Wait for specific content to loadawait page.waitForSelector('table tbody tr', { timeout: 10000 });
// Wait for network idle (all API calls finished)await page.waitForLoadState('networkidle');
// Retry pattern for flaky elementsasyncfunctionwaitAndClick(page, selector, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
await page.waitForSelector(selector, { timeout: 5000 });
await page.click(selector);
returntrue;
} catch (e) {
await page.waitForTimeout(1000);
}
}
returnfalse;
}
Handling Iframes
Some Google Ads panels (like tag details) open inside iframes:
// Find and switch to iframeconst iframeEl = await page.waitForSelector('iframe', { timeout: 5000 });
const frame = await iframeEl.contentFrame();
if (frame) {
// Now interact with elements inside the iframeconst content = await frame.textContent('body');
console.log(content);
}
Title: "Search keywords - Htek Dev - Google Ads" Access: Campaigns menu > Audiences, keywords, and content > Keywords Note: Only shows Search campaign keywords. PMax keywords are managed via Asset Groups > Search themes.
Grid Columns
Keyword, Match type, Campaign, Ad group, Status, Final URL, Impr, Interactions, Interaction rate, Avg cost, Cost, Clicks, Conv rate, Conversions, Avg CPC, Cost/conv
Tabs
Keywords (default) — active keywords with performance metrics
Negative keywords — excluded terms
Keyword Statuses
Eligible — keyword is active and serving
Not eligible: Low search volume — keyword is paused by Google due to insufficient search volume
Filter Bar
Campaign status (Enabled, Paused)
Ad group status (Enabled, Paused)
Data Extraction Pattern
// Extract keyword data from the gridconst grid = document.querySelectorAll('[role="grid"]')[2]; // 3rd grid = main dataconst rows = grid.querySelectorAll('[role="row"]');
rows.forEach(row => {
const cells = row.querySelectorAll('[role="gridcell"], [role="columnheader"]');
// cells[2] = Keyword name, cells[3] = Match type, cells[6] = Status// cells[8] = Impressions, cells[13] = Clicks, cells[12] = Cost
});
Known Issues & Workarounds
Issue
Workaround
Ad blocker dialog blocks interaction
Dismiss with Escape key or close button click before any operation
Pages load slowly
Use waitForTimeout(3000) minimum after navigation
Dynamic class names
Target by ref, role, aria-label, or text content instead
iframes for tag details
Use contentFrame() to switch context into iframes
Authentication expires
Check URL after navigation — if redirected to accounts.google.com, abort and notify
Multiple Google accounts
Ensure correct account is selected — check for Customer ID {{PHONE_NUMBER}}
Daily Exploration Protocol
The daily cron job (google-ads-exploration) follows this procedure:
Auth check — Navigate to campaigns, verify login. If not authenticated, notify {{PARENT_1}} and stop.
Dismiss dialogs — Clear any ad blocker or onboarding popups.
Collect metrics — Read campaign performance data from the dashboard.
Check conversions — Navigate to conversions, read status and counts.
Explore new pages — Visit one unexplored page from the menu structure. Document:
URL path and how to reach it
Key elements and their selectors
What data is available
Any new dialogs or patterns
Update this skill — Append new findings to the Navigation Reference.
Report to {{PARENT_1}} — Send a Telegram summary with metrics and any new discoveries.
Exploration Queue (pages not yet documented in detail)
Asset Groups page — PMax asset groups, ad strength, audience signals (2026-05-14)
Keyword analysis (quality score trends, search term reports)
Audience performance breakdown
Cost per conversion tracking
ROI calculations by campaign
Phase 3: Optimization
Bid adjustment recommendations
Budget reallocation suggestions
Keyword pause/enable recommendations
Ad copy performance analysis
Landing page performance correlation
Phase 4: A/B Testing & Automation
Create A/B test ad variations
Monitor test results and declare winners
Automated bid adjustments (with approval gates)
Campaign creation from templates
Automated negative keyword management
Helper Scripts
Reusable Playwright code snippets are stored at data/scripts/google-ads/:
Script
Purpose
dismiss-ad-blocker.js
Handles the ad blocker warning dialog
navigate-to-campaigns.js
Opens campaigns dashboard with auth check
navigate-to-conversions.js
Opens conversions summary page
navigate-to-google-tag.js
Opens Google tag details page
check-ad-performance.js
Reads campaign metrics from dashboard
These scripts are designed to be copy-pasted into playwright_service_eval calls after opening playwright_service_open({ service: "google-ads" }). Each is self-contained with error handling and auth checking while staying inside the isolated Google Ads profile.
Agent Integration
Agent
Role
coding-agent
Maintains scripts, updates this skill, runs daily exploration
entrepreneur-coach
References ad performance for business coaching decisions