@pencil-agent/nano-pencil 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +267 -267
- package/dist/build-meta.json +3 -3
- package/dist/core/export-html/AGENT.md +11 -11
- package/dist/core/export-html/template.css +971 -971
- package/dist/core/export-html/template.html +54 -54
- package/dist/core/mcp/mcp-client.d.ts +3 -1
- package/dist/core/mcp/mcp-client.js +6 -6
- package/dist/core/mcp/mcp-config.d.ts +3 -3
- package/dist/core/mcp/mcp-config.js +1 -1
- package/dist/core/mcp/mcp-manager.d.ts +5 -1
- package/dist/core/mcp/mcp-manager.js +1 -1
- package/dist/core/platform/config/resource-loader.d.ts +2 -0
- package/dist/core/platform/config/resource-loader.js +2 -2
- package/dist/core/runtime/agent-session.d.ts +12 -0
- package/dist/core/runtime/agent-session.js +8 -8
- package/dist/core/runtime/sdk.d.ts +8 -0
- package/dist/core/runtime/sdk.js +1 -1
- package/dist/extensions/builtin/AGENT.md +115 -115
- package/dist/extensions/builtin/browser/AGENT.md +17 -17
- package/dist/extensions/builtin/browser/agent-workspace/agent_helpers.py +12 -12
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/amazon/product-search.md +198 -198
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/archive-org/scraping.md +341 -341
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/arxiv/scraping.md +311 -311
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/arxiv-bulk/scraping.md +333 -333
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/atlas/overview.md +70 -70
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/booking-com/scraping.md +578 -578
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/capterra/scraping.md +440 -440
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/centilebrain/generate-estimates.md +110 -110
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coingecko/scraping.md +325 -325
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coinmarketcap/scraping.md +463 -463
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coursera/scraping.md +360 -360
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/craigslist/scraping.md +390 -390
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/crossref/scraping.md +568 -568
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/dev-to/scraping.md +323 -323
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/duckduckgo/scraping.md +349 -349
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/ebay/scraping.md +435 -435
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/etsy/scraping.md +506 -506
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/eventbrite/scraping.md +363 -363
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/expedia/automation.md +168 -168
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/facebook/groups.md +236 -236
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/facebook/pages.md +295 -295
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/framer/editor.md +108 -108
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/fred/scraping.md +493 -493
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/g2/scraping.md +580 -580
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/genius/scraping.md +511 -511
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/github/repo-actions.md +65 -65
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/github/scraping.md +184 -184
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/glassdoor/scraping.md +543 -543
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/gmail/compose.md +122 -122
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/goodreads/scraping.md +461 -461
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/gutenberg/scraping.md +383 -383
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/hackernews/scraping.md +243 -243
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/howlongtobeat/scraping.md +473 -473
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/imdb/scraping.md +271 -271
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/itch-io/scraping.md +436 -436
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/job-boards/indeed-glassdoor.md +1021 -1021
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/letterboxd/scraping.md +349 -349
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/linkedin/invitation-manager.md +109 -109
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/loom/folder-enumeration.md +170 -170
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/macrotrends/scraping.md +537 -537
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/medium/article-hydration.md +120 -120
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/medium/scraping.md +414 -414
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/metacritic/scraping.md +477 -477
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/musicbrainz/scraping.md +478 -478
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/nasa/scraping.md +339 -339
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/news-aggregation/multi-source.md +205 -205
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/open-library/scraping.md +472 -472
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/openalex/scraping.md +470 -470
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/openstreetmap/scraping.md +490 -490
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/package-registries/npm-pypi.md +478 -478
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/polymarket/scraping.md +234 -234
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/producthunt/scraping.md +307 -307
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/pubmed/scraping.md +421 -421
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/quora/scraping.md +364 -364
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/rawg/scraping.md +352 -352
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/reddit/scraping.md +124 -124
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/rest-countries/scraping.md +233 -233
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/sec-edgar/scraping.md +361 -361
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/README.md +36 -36
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/embedded-apps.md +72 -72
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/knowledge-base.md +109 -109
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/polaris-inputs.md +137 -137
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/soundcloud/scraping.md +362 -362
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/spotify/scraping.md +339 -339
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/stackoverflow/scraping.md +435 -435
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/steam/scraping.md +575 -575
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/substack/scraping.md +338 -338
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/thetechgeeks/pricing.md +52 -52
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/tiktok/upload.md +107 -107
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/tradingview/scraping.md +309 -309
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/trello/boards-and-lists.md +88 -88
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/trustpilot/scraping.md +375 -375
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/walmart/scraping.md +444 -444
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/wayback-machine/scraping.md +306 -306
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/weather/scraping.md +398 -398
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/wellfound/scraping.md +596 -596
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/world-bank/scraping.md +356 -356
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/xiaohongshu/scraping.md +84 -84
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/youtube/scraping.md +418 -418
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/zillow/scraping.md +433 -433
- package/dist/extensions/builtin/browser/browser.md +73 -73
- package/dist/extensions/builtin/browser/install.md +142 -142
- package/dist/extensions/builtin/browser/interaction-skills/connection.md +48 -48
- package/dist/extensions/builtin/browser/interaction-skills/cookies.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/cross-origin-iframes.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/dialogs.md +64 -64
- package/dist/extensions/builtin/browser/interaction-skills/downloads.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/drag-and-drop.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/dropdowns.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/iframes.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/network-requests.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/print-as-pdf.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/profile-sync.md +90 -90
- package/dist/extensions/builtin/browser/interaction-skills/screenshots.md +17 -17
- package/dist/extensions/builtin/browser/interaction-skills/scrolling.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/shadow-dom.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/tabs.md +69 -69
- package/dist/extensions/builtin/browser/interaction-skills/uploads.md +1 -1
- package/dist/extensions/builtin/browser/interaction-skills/viewport.md +3 -3
- package/dist/extensions/builtin/browser/src/browser_harness/AGENT.md +15 -15
- package/dist/extensions/builtin/browser/src/browser_harness/__init__.py +8 -8
- package/dist/extensions/builtin/browser/src/browser_harness/_ipc.py +90 -90
- package/dist/extensions/builtin/browser/src/browser_harness/admin.py +722 -722
- package/dist/extensions/builtin/browser/src/browser_harness/daemon.py +328 -328
- package/dist/extensions/builtin/browser/src/browser_harness/helpers.py +396 -396
- package/dist/extensions/builtin/browser/src/browser_harness/run.py +103 -103
- package/dist/extensions/builtin/discipline/skills/brainstorming/SKILL.md +33 -33
- package/dist/extensions/builtin/discipline/skills/executing-plans/SKILL.md +25 -25
- package/dist/extensions/builtin/discipline/skills/finishing-development-branch/SKILL.md +25 -25
- package/dist/extensions/builtin/discipline/skills/receiving-code-review/SKILL.md +22 -22
- package/dist/extensions/builtin/discipline/skills/requesting-code-review/SKILL.md +31 -31
- package/dist/extensions/builtin/discipline/skills/systematic-debugging/SKILL.md +28 -28
- package/dist/extensions/builtin/discipline/skills/test-driven-development/SKILL.md +32 -32
- package/dist/extensions/builtin/discipline/skills/using-git-worktrees/SKILL.md +25 -25
- package/dist/extensions/builtin/discipline/skills/verification-before-completion/SKILL.md +27 -27
- package/dist/extensions/builtin/discipline/skills/writing-plans/SKILL.md +26 -26
- package/dist/extensions/builtin/goal/README.md +67 -67
- package/dist/extensions/builtin/grub/README.md +112 -112
- package/dist/extensions/builtin/link-world/agent-workspace/README.md +16 -16
- package/dist/extensions/builtin/link-world/internet-search/internet-search.md +65 -65
- package/dist/extensions/builtin/link-world/link-world-agent.md +82 -82
- package/dist/extensions/builtin/link-world/linkworld.md +313 -313
- package/dist/extensions/builtin/link-world/network-routing/network-routing.md +67 -67
- package/dist/extensions/builtin/loop/README.md +92 -92
- package/dist/extensions/builtin/mcp/figma-design.md +68 -68
- package/dist/extensions/builtin/mcp/mcp-management.md +85 -85
- package/dist/extensions/builtin/recap/AGENT.md +15 -15
- package/dist/extensions/builtin/sal/README.md +72 -72
- package/dist/extensions/builtin/security-audit/README.md +289 -289
- package/dist/extensions/builtin/team/AGENT.md +112 -112
- package/dist/extensions/builtin/team/TESTING.md +299 -299
- package/dist/extensions/builtin/token-save/README.md +56 -56
- package/dist/extensions/optional/AGENT.md +10 -10
- package/dist/modes/interactive/interactive-mode.js +36 -36
- package/dist/modes/interactive/theme/dark.json +85 -85
- package/dist/modes/interactive/theme/light.json +84 -84
- package/dist/modes/interactive/theme/theme-schema.json +335 -335
- package/dist/modes/interactive/theme/warm.json +81 -81
- package/dist/node_modules/@pencil-agent/agent-core/dist/agent-loop.js +3 -2
- package/dist/node_modules/@pencil-agent/agent-core/dist/structured-adaptive-agent-loop.js +2 -1
- package/dist/node_modules/@pencil-agent/ai/dist/cli.js +0 -0
- package/docs/cc-agent-design.md +1297 -0
- package/docs/cc-tui-design.md +1333 -0
- package/docs/codex-goal-command-impl.md +1055 -1055
- package/docs/codex-goal-vs-grub.md +500 -500
- package/docs/custom-provider.md +27 -27
- package/docs/extensions.md +27 -27
- package/docs/keybindings.md +27 -27
- package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/200/273/347/273/223.md" +250 -250
- package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/212/245/345/221/212.md" +122 -122
- package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210.md" +1222 -1222
- package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210/345/256/236/347/216/260/346/212/245/345/221/212.md" +158 -158
- package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210/345/257/271/346/257/224/345/210/206/346/236/220.md" +128 -128
- package/docs/loop /351/207/215/346/236/204/350/256/241/345/210/222.md" +320 -320
- package/docs/loop-usage-examples.md +214 -214
- package/docs/models.md +27 -27
- package/docs/nanoPencil-/345/255/246/344/271/240/350/256/241/345/210/222.md +170 -0
- package/docs/packages.md +27 -27
- package/docs/pi-design-philosophy.md +457 -457
- package/docs/planmode.md +1987 -1987
- package/docs/prompt-templates.md +27 -27
- package/docs/providers.md +27 -27
- package/docs/scan-report.md +3820 -0
- package/docs/sdk.md +27 -27
- package/docs/skills.md +27 -27
- package/docs/themes.md +27 -27
- package/docs/tui.md +27 -27
- package/docs//345/257/271/346/240/207Claude-Code.md +1775 -0
- package/docs//351/230/277/351/207/214/345/267/264/345/267/264/350/264/242/346/212/245/345/210/206/346/236/220/344/271/246.md +261 -0
- package/package.json +190 -190
- package/docs/ACP/345/215/217/350/256/256/351/233/206/346/210/220/345/274/200/345/217/221/346/226/207/346/241/243.md +0 -851
- package/docs/SDK-TESTING.md +0 -364
- package/docs/mem-core/346/212/200/346/234/257/346/226/207/346/241/243.md +0 -593
- package/docs/startup-performance-optimization.md +0 -301
- package/docs//350/256/244/347/237/245/345/234/260/345/233/276.md +0 -47
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
# Gmail — Compose and send
|
|
2
|
-
|
|
3
|
-
URL: `https://mail.google.com`
|
|
4
|
-
|
|
5
|
-
## Prerequisites
|
|
6
|
-
|
|
7
|
-
- Logged into Gmail in the attached Chrome profile.
|
|
8
|
-
- Keyboard shortcuts enabled (Gmail default for most accounts).
|
|
9
|
-
|
|
10
|
-
## Open compose
|
|
11
|
-
|
|
12
|
-
```python
|
|
13
|
-
press_key("c") # Gmail shortcut — opens a new compose dialog with the "To" field focused
|
|
14
|
-
wait(1)
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Multiple compose dialogs stack — pick the visible one
|
|
18
|
-
|
|
19
|
-
Gmail keeps minimized drafts as dialogs at the bottom of the page. `document.querySelectorAll('div[role="dialog"]')` returns **all** of them (minimized *and* open). The minimized ones have small bounding rects (~`h ≤ 40`) and their inner inputs report `offsetParent === null`.
|
|
20
|
-
|
|
21
|
-
Always pick the visible dialog by size, not index:
|
|
22
|
-
|
|
23
|
-
```python
|
|
24
|
-
idx = js("""(() => {
|
|
25
|
-
const ds = [...document.querySelectorAll('div[role="dialog"]')];
|
|
26
|
-
return ds.findIndex(d => d.getBoundingClientRect().height > 200);
|
|
27
|
-
})()""")
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
…and scope every subsequent query to `dialogs[idx]`. Using index 1 blindly works *sometimes* but breaks the moment the user has a second minimized draft already sitting at the bottom.
|
|
31
|
-
|
|
32
|
-
## Trap: Tab inserts a literal `\t` into the "To" field
|
|
33
|
-
|
|
34
|
-
After `press_key("c")`, focus is on `[aria-label="To recipients"]`. `press_key("Tab")` does **not** advance focus — it inserts a tab character into the input. Confirmed by reading back `value` and finding `"\t"`.
|
|
35
|
-
|
|
36
|
-
Either click the next field directly, or commit the recipient as a chip first (e.g. by typing a valid address; Gmail chips it automatically once the input loses focus or you type a separator).
|
|
37
|
-
|
|
38
|
-
The recipient does become a chip once you click away. Read chips from `[role="dialog"] [data-hovercard-id]` — **not** from the input's `value`.
|
|
39
|
-
|
|
40
|
-
## Fill the fields
|
|
41
|
-
|
|
42
|
-
```python
|
|
43
|
-
# After press_key("c"), "To" is focused
|
|
44
|
-
type_text("someone@example.com")
|
|
45
|
-
|
|
46
|
-
# Don't Tab — click subject directly
|
|
47
|
-
sub = js("""(() => {
|
|
48
|
-
const d = [...document.querySelectorAll('div[role="dialog"]')].find(d => d.getBoundingClientRect().height > 200);
|
|
49
|
-
const s = d.querySelector('input[name="subjectbox"]');
|
|
50
|
-
const r = s.getBoundingClientRect();
|
|
51
|
-
return {x: r.x + r.width/2, y: r.y + r.height/2};
|
|
52
|
-
})()""")
|
|
53
|
-
click(sub["x"], sub["y"])
|
|
54
|
-
type_text("Subject here")
|
|
55
|
-
|
|
56
|
-
body = js("""(() => {
|
|
57
|
-
const d = [...document.querySelectorAll('div[role="dialog"]')].find(d => d.getBoundingClientRect().height > 200);
|
|
58
|
-
const b = d.querySelector('div[aria-label="Message Body"], div[role="textbox"]');
|
|
59
|
-
const r = b.getBoundingClientRect();
|
|
60
|
-
return {x: r.x + 40, y: r.y + 30};
|
|
61
|
-
})()""")
|
|
62
|
-
click(body["x"], body["y"])
|
|
63
|
-
type_text("Body text goes here.")
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Attachments — use `DOM.setFileInputFiles` on the *visible* compose's input
|
|
67
|
-
|
|
68
|
-
The paperclip button opens a native file picker that browser-harness can't drive. Instead, set files directly on Gmail's hidden file input.
|
|
69
|
-
|
|
70
|
-
**Gotcha:** there is one `input[type="file"][name="Filedata"]` per compose dialog. If you use `upload_file('input[type="file"][name="Filedata"]', ...)`, the default `DOM.querySelector` returns the *first* match — usually belongs to a stale/minimized compose, and Gmail ignores it. Always target the input scoped to the **visible** compose:
|
|
71
|
-
|
|
72
|
-
```python
|
|
73
|
-
doc = cdp("DOM.getDocument", depth=-1)
|
|
74
|
-
ids = cdp("DOM.querySelectorAll", nodeId=doc["root"]["nodeId"],
|
|
75
|
-
selector='input[type="file"][name="Filedata"]')["nodeIds"]
|
|
76
|
-
# Pick the one whose ancestor dialog has height > 200
|
|
77
|
-
# (quickest: the last one is usually the newest compose)
|
|
78
|
-
cdp("DOM.setFileInputFiles", files=["/abs/path.png"], nodeId=ids[-1])
|
|
79
|
-
wait(3)
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
After upload, `input.files` reads back as empty — Gmail consumes the FileList immediately. Don't treat that as failure. Instead, verify by screenshot or by searching the compose for the filename chip:
|
|
83
|
-
|
|
84
|
-
```python
|
|
85
|
-
ok = js("""(() => {
|
|
86
|
-
const d = [...document.querySelectorAll('div[role="dialog"]')].find(d => d.getBoundingClientRect().height > 200);
|
|
87
|
-
return [...d.querySelectorAll('*')].some(e => /\\.\\w+ \\(\\d+[KMG]?\\)/.test(e.textContent || ''));
|
|
88
|
-
})()""")
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
The attachment chip format is `filename.ext (61K)` — size appears only once Gmail has finished ingesting the file.
|
|
92
|
-
|
|
93
|
-
## Send
|
|
94
|
-
|
|
95
|
-
```python
|
|
96
|
-
send = js("""(() => {
|
|
97
|
-
const d = [...document.querySelectorAll('div[role="dialog"]')].find(d => d.getBoundingClientRect().height > 200);
|
|
98
|
-
const b = [...d.querySelectorAll('[role="button"]')].find(b => (b.getAttribute('aria-label')||'').startsWith('Send'));
|
|
99
|
-
const r = b.getBoundingClientRect();
|
|
100
|
-
return {x: r.x + r.width/2, y: r.y + r.height/2};
|
|
101
|
-
})()""")
|
|
102
|
-
click(send["x"], send["y"])
|
|
103
|
-
wait(2)
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Verify by looking for the "Message sent" toast at the bottom-left, or by checking that the visible compose dialog's height has collapsed. `⌘+Enter` also sends but requires keyboard-shortcut support in the current account.
|
|
107
|
-
|
|
108
|
-
## Stable selectors
|
|
109
|
-
|
|
110
|
-
- To field: `[aria-label="To recipients"]`
|
|
111
|
-
- Subject: `input[name="subjectbox"]`
|
|
112
|
-
- Body: `div[aria-label="Message Body"]` (also matches `div[role="textbox"]` inside the dialog)
|
|
113
|
-
- Send button: `[role="dialog"] [role="button"][aria-label^="Send"]`
|
|
114
|
-
- Attach file input: `input[type="file"][name="Filedata"]` (one per dialog)
|
|
115
|
-
- Recipient chip: `[data-hovercard-id]` inside the dialog
|
|
116
|
-
|
|
117
|
-
## Traps
|
|
118
|
-
|
|
119
|
-
- Tab in the "To" field inserts `\t` — never Tab between fields, click them.
|
|
120
|
-
- `input.files` is cleared by Gmail after `setFileInputFiles` — don't use it as a success check.
|
|
121
|
-
- The first match of `input[type="file"]` can belong to a stale/minimized compose; pick by dialog, not by index.
|
|
122
|
-
- `press_key("c")` only works if keyboard shortcuts are enabled in the account. If it no-ops, fall back to clicking the left-rail Compose pencil.
|
|
1
|
+
# Gmail — Compose and send
|
|
2
|
+
|
|
3
|
+
URL: `https://mail.google.com`
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Logged into Gmail in the attached Chrome profile.
|
|
8
|
+
- Keyboard shortcuts enabled (Gmail default for most accounts).
|
|
9
|
+
|
|
10
|
+
## Open compose
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
press_key("c") # Gmail shortcut — opens a new compose dialog with the "To" field focused
|
|
14
|
+
wait(1)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Multiple compose dialogs stack — pick the visible one
|
|
18
|
+
|
|
19
|
+
Gmail keeps minimized drafts as dialogs at the bottom of the page. `document.querySelectorAll('div[role="dialog"]')` returns **all** of them (minimized *and* open). The minimized ones have small bounding rects (~`h ≤ 40`) and their inner inputs report `offsetParent === null`.
|
|
20
|
+
|
|
21
|
+
Always pick the visible dialog by size, not index:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
idx = js("""(() => {
|
|
25
|
+
const ds = [...document.querySelectorAll('div[role="dialog"]')];
|
|
26
|
+
return ds.findIndex(d => d.getBoundingClientRect().height > 200);
|
|
27
|
+
})()""")
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
…and scope every subsequent query to `dialogs[idx]`. Using index 1 blindly works *sometimes* but breaks the moment the user has a second minimized draft already sitting at the bottom.
|
|
31
|
+
|
|
32
|
+
## Trap: Tab inserts a literal `\t` into the "To" field
|
|
33
|
+
|
|
34
|
+
After `press_key("c")`, focus is on `[aria-label="To recipients"]`. `press_key("Tab")` does **not** advance focus — it inserts a tab character into the input. Confirmed by reading back `value` and finding `"\t"`.
|
|
35
|
+
|
|
36
|
+
Either click the next field directly, or commit the recipient as a chip first (e.g. by typing a valid address; Gmail chips it automatically once the input loses focus or you type a separator).
|
|
37
|
+
|
|
38
|
+
The recipient does become a chip once you click away. Read chips from `[role="dialog"] [data-hovercard-id]` — **not** from the input's `value`.
|
|
39
|
+
|
|
40
|
+
## Fill the fields
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
# After press_key("c"), "To" is focused
|
|
44
|
+
type_text("someone@example.com")
|
|
45
|
+
|
|
46
|
+
# Don't Tab — click subject directly
|
|
47
|
+
sub = js("""(() => {
|
|
48
|
+
const d = [...document.querySelectorAll('div[role="dialog"]')].find(d => d.getBoundingClientRect().height > 200);
|
|
49
|
+
const s = d.querySelector('input[name="subjectbox"]');
|
|
50
|
+
const r = s.getBoundingClientRect();
|
|
51
|
+
return {x: r.x + r.width/2, y: r.y + r.height/2};
|
|
52
|
+
})()""")
|
|
53
|
+
click(sub["x"], sub["y"])
|
|
54
|
+
type_text("Subject here")
|
|
55
|
+
|
|
56
|
+
body = js("""(() => {
|
|
57
|
+
const d = [...document.querySelectorAll('div[role="dialog"]')].find(d => d.getBoundingClientRect().height > 200);
|
|
58
|
+
const b = d.querySelector('div[aria-label="Message Body"], div[role="textbox"]');
|
|
59
|
+
const r = b.getBoundingClientRect();
|
|
60
|
+
return {x: r.x + 40, y: r.y + 30};
|
|
61
|
+
})()""")
|
|
62
|
+
click(body["x"], body["y"])
|
|
63
|
+
type_text("Body text goes here.")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Attachments — use `DOM.setFileInputFiles` on the *visible* compose's input
|
|
67
|
+
|
|
68
|
+
The paperclip button opens a native file picker that browser-harness can't drive. Instead, set files directly on Gmail's hidden file input.
|
|
69
|
+
|
|
70
|
+
**Gotcha:** there is one `input[type="file"][name="Filedata"]` per compose dialog. If you use `upload_file('input[type="file"][name="Filedata"]', ...)`, the default `DOM.querySelector` returns the *first* match — usually belongs to a stale/minimized compose, and Gmail ignores it. Always target the input scoped to the **visible** compose:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
doc = cdp("DOM.getDocument", depth=-1)
|
|
74
|
+
ids = cdp("DOM.querySelectorAll", nodeId=doc["root"]["nodeId"],
|
|
75
|
+
selector='input[type="file"][name="Filedata"]')["nodeIds"]
|
|
76
|
+
# Pick the one whose ancestor dialog has height > 200
|
|
77
|
+
# (quickest: the last one is usually the newest compose)
|
|
78
|
+
cdp("DOM.setFileInputFiles", files=["/abs/path.png"], nodeId=ids[-1])
|
|
79
|
+
wait(3)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
After upload, `input.files` reads back as empty — Gmail consumes the FileList immediately. Don't treat that as failure. Instead, verify by screenshot or by searching the compose for the filename chip:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
ok = js("""(() => {
|
|
86
|
+
const d = [...document.querySelectorAll('div[role="dialog"]')].find(d => d.getBoundingClientRect().height > 200);
|
|
87
|
+
return [...d.querySelectorAll('*')].some(e => /\\.\\w+ \\(\\d+[KMG]?\\)/.test(e.textContent || ''));
|
|
88
|
+
})()""")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The attachment chip format is `filename.ext (61K)` — size appears only once Gmail has finished ingesting the file.
|
|
92
|
+
|
|
93
|
+
## Send
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
send = js("""(() => {
|
|
97
|
+
const d = [...document.querySelectorAll('div[role="dialog"]')].find(d => d.getBoundingClientRect().height > 200);
|
|
98
|
+
const b = [...d.querySelectorAll('[role="button"]')].find(b => (b.getAttribute('aria-label')||'').startsWith('Send'));
|
|
99
|
+
const r = b.getBoundingClientRect();
|
|
100
|
+
return {x: r.x + r.width/2, y: r.y + r.height/2};
|
|
101
|
+
})()""")
|
|
102
|
+
click(send["x"], send["y"])
|
|
103
|
+
wait(2)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Verify by looking for the "Message sent" toast at the bottom-left, or by checking that the visible compose dialog's height has collapsed. `⌘+Enter` also sends but requires keyboard-shortcut support in the current account.
|
|
107
|
+
|
|
108
|
+
## Stable selectors
|
|
109
|
+
|
|
110
|
+
- To field: `[aria-label="To recipients"]`
|
|
111
|
+
- Subject: `input[name="subjectbox"]`
|
|
112
|
+
- Body: `div[aria-label="Message Body"]` (also matches `div[role="textbox"]` inside the dialog)
|
|
113
|
+
- Send button: `[role="dialog"] [role="button"][aria-label^="Send"]`
|
|
114
|
+
- Attach file input: `input[type="file"][name="Filedata"]` (one per dialog)
|
|
115
|
+
- Recipient chip: `[data-hovercard-id]` inside the dialog
|
|
116
|
+
|
|
117
|
+
## Traps
|
|
118
|
+
|
|
119
|
+
- Tab in the "To" field inserts `\t` — never Tab between fields, click them.
|
|
120
|
+
- `input.files` is cleared by Gmail after `setFileInputFiles` — don't use it as a success check.
|
|
121
|
+
- The first match of `input[type="file"]` can belong to a stale/minimized compose; pick by dialog, not by index.
|
|
122
|
+
- `press_key("c")` only works if keyboard shortcuts are enabled in the account. If it no-ops, fall back to clicking the left-rail Compose pencil.
|