@pencil-agent/nano-pencil 2.0.1 → 2.0.2
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/model/custom-providers.js +1 -1
- package/dist/core/model-registry.js +5 -5
- 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/controllers/input-submit-controller.js +2 -2
- package/dist/modes/interactive/controllers/stream-render-controller.js +2 -2
- package/dist/modes/interactive/interactive-mode.js +19 -19
- 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/ai/dist/cli.js +0 -0
- package/dist/node_modules/@pencil-agent/ai/dist/models.generated.js +1 -1
- 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 +851 -0
- package/docs/SDK-TESTING.md +364 -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/mem-core/346/212/200/346/234/257/346/226/207/346/241/243.md +593 -0
- package/docs/models.md +27 -27
- 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/sdk.md +27 -27
- package/docs/skills.md +27 -27
- package/docs/startup-performance-optimization.md +301 -0
- package/docs/themes.md +27 -27
- package/docs/tui.md +27 -27
- package/docs//350/256/244/347/237/245/345/234/260/345/233/276.md +47 -0
- package/package.json +190 -190
- package/docs/cc-agent-design.md +0 -1297
- package/docs/cc-tui-design.md +0 -1333
- package/docs/nanoPencil-/345/255/246/344/271/240/350/256/241/345/210/222.md +0 -170
- package/docs/scan-report.md +0 -3820
- package/docs//345/257/271/346/240/207Claude-Code.md +0 -1775
- 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 +0 -261
|
@@ -1,168 +1,168 @@
|
|
|
1
|
-
# Expedia — Browser Automation
|
|
2
|
-
|
|
3
|
-
Field-tested against expedia.co.in on 2026-04-27 using `browser-harness` CDP
|
|
4
|
-
helpers (`goto`, `js`, `click`, `type_text`, `screenshot`).
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## TL;DR
|
|
9
|
-
|
|
10
|
-
**Build your search via URL parameters, not the UI.** The date picker, destination
|
|
11
|
-
autocomplete, and traveller widgets are fragile—coordinate clicks frequently
|
|
12
|
-
dismiss them or mis-target. Encode everything you can into a `goto()` URL, then
|
|
13
|
-
use JS clicks only for what the URL can't express (child ages, price filters).
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Hotel Search URL Template
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
https://www.expedia.co.in/Hotel-Search?destination={DEST}&startDate={YYYY-MM-DD}&endDate={YYYY-MM-DD}&rooms={N}&adults={N}&children={N}&childrenAges={age1,age2,...}
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Example — 2 adults, 2 children (ages 5 and 7), Tokyo, June 2026:
|
|
24
|
-
|
|
25
|
-
```python
|
|
26
|
-
goto("https://www.expedia.co.in/Hotel-Search?"
|
|
27
|
-
"destination=Central+Tokyo,+Tokyo+Prefecture&"
|
|
28
|
-
"startDate=2026-06-01&endDate=2026-06-07&"
|
|
29
|
-
"rooms=1&adults=2&children=2&childrenAges=5,7")
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
**Note:** `childrenAges` in the URL may not always populate the age dropdowns on
|
|
33
|
-
the results page. Verify with a screenshot and set them via JS if needed.
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## Date Picker — DO NOT USE
|
|
38
|
-
|
|
39
|
-
The calendar widget is extremely unreliable with coordinate-based clicks:
|
|
40
|
-
|
|
41
|
-
- Clicking a date cell frequently **closes the entire picker** instead of
|
|
42
|
-
selecting the date.
|
|
43
|
-
- The picker has month-navigation arrows that are tiny targets.
|
|
44
|
-
- "Flexible dates" mode has a different DOM structure with pill-shaped month
|
|
45
|
-
selectors that also mis-fire.
|
|
46
|
-
- Dozens of retry attempts across multiple strategies all failed.
|
|
47
|
-
|
|
48
|
-
**Workaround:** Always pass dates via URL parameters (`startDate`, `endDate`).
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
## Travellers Widget
|
|
53
|
-
|
|
54
|
-
The travellers stepper panel works with JS `.click()` on the increment/decrement
|
|
55
|
-
buttons.
|
|
56
|
-
|
|
57
|
-
### Opening the panel
|
|
58
|
-
|
|
59
|
-
```python
|
|
60
|
-
js("""
|
|
61
|
-
(()=>{
|
|
62
|
-
let btn = document.querySelector('button[data-testid="travelers-field-trigger"]')
|
|
63
|
-
|| [...document.querySelectorAll('button')].find(b => b.textContent.includes('traveller'));
|
|
64
|
-
if(btn){ btn.click(); return 'opened'; }
|
|
65
|
-
return 'not found';
|
|
66
|
-
})()
|
|
67
|
-
""")
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Incrementing children count
|
|
71
|
-
|
|
72
|
-
```python
|
|
73
|
-
js("""
|
|
74
|
-
(()=>{
|
|
75
|
-
let span = [...document.querySelectorAll('span')].find(s => s.textContent.trim() === 'Children');
|
|
76
|
-
if(!span) return 'no Children label';
|
|
77
|
-
let container = span.closest('div').parentElement;
|
|
78
|
-
let buttons = container.querySelectorAll('button');
|
|
79
|
-
let plus = buttons[buttons.length - 1]; // last button is "+"
|
|
80
|
-
plus.click();
|
|
81
|
-
return 'incremented';
|
|
82
|
-
})()
|
|
83
|
-
""")
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### Setting child ages
|
|
87
|
-
|
|
88
|
-
Child age dropdowns are `<select>` elements with `aria-label` like
|
|
89
|
-
"Child 1 age", "Child 2 age", etc.
|
|
90
|
-
|
|
91
|
-
```python
|
|
92
|
-
js("""
|
|
93
|
-
(()=>{
|
|
94
|
-
let selects = document.querySelectorAll('select');
|
|
95
|
-
// selects[0] = Child 1 age, selects[1] = Child 2 age, etc.
|
|
96
|
-
let setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
|
97
|
-
selects[0].value = '5';
|
|
98
|
-
selects[0].dispatchEvent(new Event('change', {bubbles:true}));
|
|
99
|
-
selects[1].value = '7';
|
|
100
|
-
selects[1].dispatchEvent(new Event('change', {bubbles:true}));
|
|
101
|
-
return 'ages set';
|
|
102
|
-
})()
|
|
103
|
-
""")
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### Closing the panel
|
|
107
|
-
|
|
108
|
-
```python
|
|
109
|
-
js("""
|
|
110
|
-
(()=>{
|
|
111
|
-
let btn = [...document.querySelectorAll('button')].find(b => b.textContent.trim() === 'Done');
|
|
112
|
-
if(btn){ btn.click(); return 'done'; }
|
|
113
|
-
return 'no Done button';
|
|
114
|
-
})()
|
|
115
|
-
""")
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
## Price Filter
|
|
121
|
-
|
|
122
|
-
On the results page, the nightly-price filter has two text inputs and two range
|
|
123
|
-
sliders.
|
|
124
|
-
|
|
125
|
-
| Element | Selector |
|
|
126
|
-
|---------|----------|
|
|
127
|
-
| Min text input | `#price-min` |
|
|
128
|
-
| Max text input | `#price-max` |
|
|
129
|
-
| Min range slider | `input[type="range"][aria-label*="Minimum"]` |
|
|
130
|
-
| Max range slider | `input[type="range"][aria-label*="Maximum"]` |
|
|
131
|
-
|
|
132
|
-
### Setting max price
|
|
133
|
-
|
|
134
|
-
The most reliable method is to click the input, select all, type the value, and
|
|
135
|
-
press Enter:
|
|
136
|
-
|
|
137
|
-
```python
|
|
138
|
-
click(x, y) # coordinates of #price-max
|
|
139
|
-
js("document.getElementById('price-max').select()")
|
|
140
|
-
type_text("20000")
|
|
141
|
-
press_key("Enter")
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
Setting the value purely via JS (`dispatchEvent`) does trigger a re-search but
|
|
145
|
-
coordinate-click + type is more reliable for actually applying the filter.
|
|
146
|
-
|
|
147
|
-
---
|
|
148
|
-
|
|
149
|
-
## Key Lessons
|
|
150
|
-
|
|
151
|
-
1. **URL-first strategy** — Encode destination, dates, room count, adults,
|
|
152
|
-
children, and child ages in the URL. Only use UI interaction for things the
|
|
153
|
-
URL cannot express.
|
|
154
|
-
|
|
155
|
-
2. **JS `.click()` over coordinate clicks** — For buttons inside panels
|
|
156
|
-
(traveller stepper, Done), find elements by text/attribute and call `.click()`
|
|
157
|
-
in JS. Coordinate clicks on overlay panels are unreliable.
|
|
158
|
-
|
|
159
|
-
3. **`dispatchEvent` with `{bubbles: true}`** — Required for React-controlled
|
|
160
|
-
inputs (selects, text fields). Without bubbling, React state won't update.
|
|
161
|
-
|
|
162
|
-
4. **Wait after navigation** — After `goto()` or pressing Search, call
|
|
163
|
-
`wait_for_load()` + `time.sleep(3)` before interacting. Expedia loads
|
|
164
|
-
results asynchronously.
|
|
165
|
-
|
|
166
|
-
5. **Indian locale** — `expedia.co.in` shows prices in ₹ (INR). The price
|
|
167
|
-
filter values include the ₹ symbol and commas in the text input but the
|
|
168
|
-
underlying range slider uses plain integers.
|
|
1
|
+
# Expedia — Browser Automation
|
|
2
|
+
|
|
3
|
+
Field-tested against expedia.co.in on 2026-04-27 using `browser-harness` CDP
|
|
4
|
+
helpers (`goto`, `js`, `click`, `type_text`, `screenshot`).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## TL;DR
|
|
9
|
+
|
|
10
|
+
**Build your search via URL parameters, not the UI.** The date picker, destination
|
|
11
|
+
autocomplete, and traveller widgets are fragile—coordinate clicks frequently
|
|
12
|
+
dismiss them or mis-target. Encode everything you can into a `goto()` URL, then
|
|
13
|
+
use JS clicks only for what the URL can't express (child ages, price filters).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Hotel Search URL Template
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
https://www.expedia.co.in/Hotel-Search?destination={DEST}&startDate={YYYY-MM-DD}&endDate={YYYY-MM-DD}&rooms={N}&adults={N}&children={N}&childrenAges={age1,age2,...}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Example — 2 adults, 2 children (ages 5 and 7), Tokyo, June 2026:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
goto("https://www.expedia.co.in/Hotel-Search?"
|
|
27
|
+
"destination=Central+Tokyo,+Tokyo+Prefecture&"
|
|
28
|
+
"startDate=2026-06-01&endDate=2026-06-07&"
|
|
29
|
+
"rooms=1&adults=2&children=2&childrenAges=5,7")
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Note:** `childrenAges` in the URL may not always populate the age dropdowns on
|
|
33
|
+
the results page. Verify with a screenshot and set them via JS if needed.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Date Picker — DO NOT USE
|
|
38
|
+
|
|
39
|
+
The calendar widget is extremely unreliable with coordinate-based clicks:
|
|
40
|
+
|
|
41
|
+
- Clicking a date cell frequently **closes the entire picker** instead of
|
|
42
|
+
selecting the date.
|
|
43
|
+
- The picker has month-navigation arrows that are tiny targets.
|
|
44
|
+
- "Flexible dates" mode has a different DOM structure with pill-shaped month
|
|
45
|
+
selectors that also mis-fire.
|
|
46
|
+
- Dozens of retry attempts across multiple strategies all failed.
|
|
47
|
+
|
|
48
|
+
**Workaround:** Always pass dates via URL parameters (`startDate`, `endDate`).
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Travellers Widget
|
|
53
|
+
|
|
54
|
+
The travellers stepper panel works with JS `.click()` on the increment/decrement
|
|
55
|
+
buttons.
|
|
56
|
+
|
|
57
|
+
### Opening the panel
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
js("""
|
|
61
|
+
(()=>{
|
|
62
|
+
let btn = document.querySelector('button[data-testid="travelers-field-trigger"]')
|
|
63
|
+
|| [...document.querySelectorAll('button')].find(b => b.textContent.includes('traveller'));
|
|
64
|
+
if(btn){ btn.click(); return 'opened'; }
|
|
65
|
+
return 'not found';
|
|
66
|
+
})()
|
|
67
|
+
""")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Incrementing children count
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
js("""
|
|
74
|
+
(()=>{
|
|
75
|
+
let span = [...document.querySelectorAll('span')].find(s => s.textContent.trim() === 'Children');
|
|
76
|
+
if(!span) return 'no Children label';
|
|
77
|
+
let container = span.closest('div').parentElement;
|
|
78
|
+
let buttons = container.querySelectorAll('button');
|
|
79
|
+
let plus = buttons[buttons.length - 1]; // last button is "+"
|
|
80
|
+
plus.click();
|
|
81
|
+
return 'incremented';
|
|
82
|
+
})()
|
|
83
|
+
""")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Setting child ages
|
|
87
|
+
|
|
88
|
+
Child age dropdowns are `<select>` elements with `aria-label` like
|
|
89
|
+
"Child 1 age", "Child 2 age", etc.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
js("""
|
|
93
|
+
(()=>{
|
|
94
|
+
let selects = document.querySelectorAll('select');
|
|
95
|
+
// selects[0] = Child 1 age, selects[1] = Child 2 age, etc.
|
|
96
|
+
let setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
|
97
|
+
selects[0].value = '5';
|
|
98
|
+
selects[0].dispatchEvent(new Event('change', {bubbles:true}));
|
|
99
|
+
selects[1].value = '7';
|
|
100
|
+
selects[1].dispatchEvent(new Event('change', {bubbles:true}));
|
|
101
|
+
return 'ages set';
|
|
102
|
+
})()
|
|
103
|
+
""")
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Closing the panel
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
js("""
|
|
110
|
+
(()=>{
|
|
111
|
+
let btn = [...document.querySelectorAll('button')].find(b => b.textContent.trim() === 'Done');
|
|
112
|
+
if(btn){ btn.click(); return 'done'; }
|
|
113
|
+
return 'no Done button';
|
|
114
|
+
})()
|
|
115
|
+
""")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Price Filter
|
|
121
|
+
|
|
122
|
+
On the results page, the nightly-price filter has two text inputs and two range
|
|
123
|
+
sliders.
|
|
124
|
+
|
|
125
|
+
| Element | Selector |
|
|
126
|
+
|---------|----------|
|
|
127
|
+
| Min text input | `#price-min` |
|
|
128
|
+
| Max text input | `#price-max` |
|
|
129
|
+
| Min range slider | `input[type="range"][aria-label*="Minimum"]` |
|
|
130
|
+
| Max range slider | `input[type="range"][aria-label*="Maximum"]` |
|
|
131
|
+
|
|
132
|
+
### Setting max price
|
|
133
|
+
|
|
134
|
+
The most reliable method is to click the input, select all, type the value, and
|
|
135
|
+
press Enter:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
click(x, y) # coordinates of #price-max
|
|
139
|
+
js("document.getElementById('price-max').select()")
|
|
140
|
+
type_text("20000")
|
|
141
|
+
press_key("Enter")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Setting the value purely via JS (`dispatchEvent`) does trigger a re-search but
|
|
145
|
+
coordinate-click + type is more reliable for actually applying the filter.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Key Lessons
|
|
150
|
+
|
|
151
|
+
1. **URL-first strategy** — Encode destination, dates, room count, adults,
|
|
152
|
+
children, and child ages in the URL. Only use UI interaction for things the
|
|
153
|
+
URL cannot express.
|
|
154
|
+
|
|
155
|
+
2. **JS `.click()` over coordinate clicks** — For buttons inside panels
|
|
156
|
+
(traveller stepper, Done), find elements by text/attribute and call `.click()`
|
|
157
|
+
in JS. Coordinate clicks on overlay panels are unreliable.
|
|
158
|
+
|
|
159
|
+
3. **`dispatchEvent` with `{bubbles: true}`** — Required for React-controlled
|
|
160
|
+
inputs (selects, text fields). Without bubbling, React state won't update.
|
|
161
|
+
|
|
162
|
+
4. **Wait after navigation** — After `goto()` or pressing Search, call
|
|
163
|
+
`wait_for_load()` + `time.sleep(3)` before interacting. Expedia loads
|
|
164
|
+
results asynchronously.
|
|
165
|
+
|
|
166
|
+
5. **Indian locale** — `expedia.co.in` shows prices in ₹ (INR). The price
|
|
167
|
+
filter values include the ₹ symbol and commas in the text input but the
|
|
168
|
+
underlying range slider uses plain integers.
|