Improve gallery photo-review guidance for property assessment

This commit is contained in:
2026-03-27 12:02:47 -05:00
parent b13f272e48
commit 90635bf8f2
3 changed files with 54 additions and 4 deletions

View File

@@ -127,6 +127,21 @@ Example:
npx tsx flow.ts --instruction 'go to https://search.fiorinis.com then type "pippo" then press enter then wait 2s'
```
### Gallery/lightbox workflows
For real-estate listings and other image-heavy pages, prefer opening the gallery/lightbox explicitly and verifying that each navigation step produces a distinct image.
Practical rules:
- Do not treat a listing page hero image, gallery collage, or modal landing view as full photo review.
- After opening the gallery, capture the first image, then use the actual in-gallery next control.
- Confirm the image changed before counting the next screenshot as reviewed.
- If a generic `Next` control exits the gallery or returns to the listing shell, stop and adjust the selector/interaction; do not claim the photos were reviewed.
- Blind `ArrowRight` presses are not reliable enough unless you have already verified that they advance the gallery on that site.
- For smaller listings, review all photos when practical; otherwise review enough distinct photos to cover kitchen, baths, living areas, bedrooms, exterior, and any waterfront/balcony/deck elements.
- If automation cannot reliably advance the gallery, say so explicitly in the final answer.
Where possible, prefer specific controls over vague ones, for example a gallery-specific next-arrow button rather than a page-level `Next` button.
## Compatibility Aliases
- `CAMOUFOX_PROFILE_PATH` still works as a legacy alias for `CLOAKBROWSER_PROFILE_PATH`

View File

@@ -6,7 +6,7 @@ import { launchBrowser } from './browse';
type Step =
| { action: 'goto'; url: string }
| { action: 'click'; selector?: string; text?: string }
| { action: 'click'; selector?: string; text?: string; role?: string; name?: string }
| { action: 'type'; selector?: string; text: string }
| { action: 'press'; key: string; selector?: string }
| { action: 'wait'; ms: number }
@@ -41,7 +41,13 @@ function parseInstruction(instruction: string): Step[] {
continue;
}
// click on "text" or click #selector
// click on "text" or click #selector or click button "name"
const clickRole = p.match(/^click\s+(button|link|textbox|img|image|tab)\s+"([^"]+)"$/i);
if (clickRole) {
const role = clickRole[1].toLowerCase() === 'image' ? 'img' : clickRole[1].toLowerCase();
steps.push({ action: 'click', role, name: clickRole[2] });
continue;
}
const clickText = p.match(/^click(?: on)?\s+"([^"]+)"/i);
if (clickText) {
steps.push({ action: 'click', text: clickText[1] });
@@ -203,6 +209,8 @@ async function runSteps(page: Page, steps: Step[]) {
case 'click':
if (step.selector) {
await page.locator(step.selector).first().click({ timeout: 15000 });
} else if (step.role && step.name) {
await page.getByRole(step.role as any, { name: new RegExp(escapeRegExp(step.name), 'i') }).first().click({ timeout: 15000 });
} else if (step.text) {
const clicked = await clickByText(page, step.text);
if (!clicked) {
@@ -212,7 +220,7 @@ async function runSteps(page: Page, steps: Step[]) {
}
}
} else {
throw new Error('click step missing selector/text');
throw new Error('click step missing selector/text/role');
}
try {
await page.waitForLoadState('domcontentloaded', { timeout: 10000 });