diff --git a/domain-skills/openai-ads/ads-manager.md b/domain-skills/openai-ads/ads-manager.md new file mode 100644 index 00000000..34652f1d --- /dev/null +++ b/domain-skills/openai-ads/ads-manager.md @@ -0,0 +1,44 @@ +# OpenAI Ads Manager (ads.openai.com) + +Beta product for ads shown in ChatGPT. Account-scoped URLs: `/manage/campaigns?act=`. + +## Structure + +Campaign → Ad group → Ad. Settings live at different levels: + +- **Campaign**: name, type (Standard/product feed), objective (Clicks), locations, daily budget (defaults to `100.00`). +- **Ad group**: name, max CPC bid (defaults to `4.00`, shows a live "Strong/Weak Delivery" competitiveness signal), Default Ad Destination URL, context hints textarea (semantic conversation matching, not exact-match keywords). +- **Ad**: name, headline (50 max), description (100 max), destination URL (inherits ad group default), one square image (PNG/JPG ≥256×256). + +## Create flows + +- `+ Create` button (top right of Campaigns page) → dropdown: Create campaign / Create ad group / Create ad / Upload bulk. +- "Create campaign" opens a 3-step wizard (Campaign → Ad Group & Ads → Review) in a full-screen modal. Final button is **Publish** with an "agree to the Advertising Tool Terms" note; publishing shows "Applying changes / Creating ads… (0/1)" then a toast: "Campaign created successfully with 1 ad group and 1 ad." +- "Create ad" alone opens a small modal that requires picking parent campaign + ad group — no budget/CPC controls there. + +## Locating fields + +Inputs have no stable ids; locate by placeholder / value: + +- Campaign name: placeholder `New traffic campaign` +- Budget: input with value `100.00` (select-all + retype) +- Ad group name: placeholder `New ad group` +- Context hints: textarea whose placeholder contains `context hints` +- Ad name: placeholder `New ad` +- Description: placeholder `Enter ad description` + +Headlines >~40 chars trigger "Copy may be truncated in some placements." (soft warning, non-blocking). Same warning appears for long descriptions. + +## Trap: stuck image attachment + +The "Ad images" slot can show a grey square tile with an X above it. That tile is a **stuck upload** (`_ImageAttachment_` with a frozen `_TrackProgress_` spinner), not an upload button: + +- While it exists there is **no `input[type=file]` in the DOM**, and clicking the tile does nothing (no `Page.fileChooserOpened` even with interception enabled). +- Fix: click the tile's X (small button absolutely positioned at its top-right corner). A real `input[type=file]` plus an "Upload image" label appear; then `DOM.setFileInputFiles` on that input works (`upload_file()` helper). Uploaded state: tile shows the image, preview card thumbnail updates. + +## Misc + +- Table cells (Status, Impressions, …) load async as skeleton placeholders; poll row text until `Serving` appears before reading state. +- Account display name, timezone, and advertiser logo: Settings → General → Account Info (Edit). The logo is the small avatar shown next to the advertiser name on ad preview cards. +- A yellow "Complete business verification to keep ads serving" banner persists account-wide until verification is done; ads serve during the grace period. +- Ad previews render the image as a tiny (~72px) square thumbnail next to the copy — creative is closer to a search-ad favicon than a Meta-style banner. diff --git a/interaction-skills/uploads.md b/interaction-skills/uploads.md index a84e6a66..fee1d0d0 100644 --- a/interaction-skills/uploads.md +++ b/interaction-skills/uploads.md @@ -1 +1,24 @@ # Uploads + +Order of attack for file uploads: + +1. **Existing `input[type=file]`** — even hidden ones. `upload_file('input[type=file]', '/abs/path')` (CDP `DOM.setFileInputFiles`) works without any dialog appearing. +2. **No file input in the DOM** (input created on demand): enable interception *before* clicking the trigger, then feed the chooser from the event: + + ```python + cdp("Page.setInterceptFileChooserDialog", enabled=True) + click(x, y) # the Upload button/tile + wait(1) + evs = [e for e in drain_events() if e.get("method") == "Page.fileChooserOpened"] + if evs: + cdp("DOM.setFileInputFiles", files=["/abs/path"], + backendNodeId=evs[-1]["params"]["backendNodeId"]) + cdp("Page.setInterceptFileChooserDialog", enabled=False) + ``` + + Without interception the native macOS chooser opens and CDP cannot drive it. +3. **Click produced no `fileChooserOpened` event**: the thing you clicked is not the trigger. Common cause: a stale/stuck attachment preview occupying the slot — remove it first (look for a small X button) and a real `input[type=file]` often appears (see `domain-skills/openai-ads/ads-manager.md` for a field example). + +Verify after upload: re-query the preview element (`img.naturalWidth > 0`, `complete === true`) or re-screenshot — do not assume the set call worked. + +Drag-and-drop-only dropzones: see `drag-and-drop.md`; synthesize a `DataTransfer` via JS or fall back to `DOM.setFileInputFiles` on the dropzone's hidden input.