No hacks
This commit is contained in:
parent
e3e8a4522e
commit
58bb3cb4f8
9 changed files with 49 additions and 174 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import type { Page } from 'playwright';
|
||||
import type { DashboardRecorder, HexagonClickTarget } from './dashboard.js';
|
||||
import {
|
||||
AI_ZOOM_SCALE,
|
||||
BRAND_NAME,
|
||||
|
|
@ -13,14 +14,9 @@ import {
|
|||
import {
|
||||
clearVignette,
|
||||
flashRect,
|
||||
getDemoMapSettleVersion,
|
||||
hideCaption,
|
||||
showCaption,
|
||||
showOutro,
|
||||
visualClick,
|
||||
waitForDemoMapSettled,
|
||||
waitForCurrentDemoMapSettled,
|
||||
waitForDemoSelectionReady,
|
||||
zoomReset,
|
||||
zoomTo,
|
||||
} from './dom.js';
|
||||
|
|
@ -28,6 +24,7 @@ import { fakeType, sleep, smoothMove, smoothDragSliderThumb } from './motion.js'
|
|||
|
||||
export interface SceneCtx {
|
||||
page: Page;
|
||||
dashboard: DashboardRecorder;
|
||||
cursor: { x: number; y: number };
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +33,7 @@ export interface SceneCtx {
|
|||
* stubbed, while the map filters and right pane are loaded from the real app.
|
||||
*/
|
||||
export async function sceneAiCloseUp(ctx: SceneCtx): Promise<void> {
|
||||
const { page } = ctx;
|
||||
const { page, dashboard } = ctx;
|
||||
|
||||
await clearVignette(page);
|
||||
await showCaption(page, 'Brief: flats or terraces under £450k near central Manchester.');
|
||||
|
|
@ -53,13 +50,13 @@ export async function sceneAiCloseUp(ctx: SceneCtx): Promise<void> {
|
|||
{ timeout: 1800 }
|
||||
)
|
||||
.catch(() => null);
|
||||
const mapVersion = await getDemoMapSettleVersion(page);
|
||||
const mapVersion = dashboard.getMapDataVersion();
|
||||
await page.evaluate(() => {
|
||||
document.querySelector<HTMLFormElement>('[data-tutorial="ai-filters"] form')?.requestSubmit();
|
||||
});
|
||||
await aiResponse;
|
||||
await sleep(160);
|
||||
await waitForDemoMapSettled(page, 15000, mapVersion);
|
||||
await dashboard.waitForMapSettled(mapVersion, 15000);
|
||||
await showCaption(page, 'The filters are already live on the map.');
|
||||
await sleep(560);
|
||||
await hideCaption(page);
|
||||
|
|
@ -91,7 +88,7 @@ export async function sceneZoomOutResults(ctx: SceneCtx): Promise<void> {
|
|||
* the selector or this scene will time out.
|
||||
*/
|
||||
export async function sceneTravelTimeSlider(ctx: SceneCtx): Promise<void> {
|
||||
const { page } = ctx;
|
||||
const { page, dashboard } = ctx;
|
||||
await showCaption(
|
||||
page,
|
||||
`Then tighten the commute: ${TT_DRAG_FROM_MIN} minutes down to ${TT_DRAG_TO_MIN}.`
|
||||
|
|
@ -109,7 +106,7 @@ export async function sceneTravelTimeSlider(ctx: SceneCtx): Promise<void> {
|
|||
|
||||
// Slider goes 0..120, target = 20 → fraction 0.166...
|
||||
const toFraction = TT_DRAG_TO_MIN / TT_SLIDER_MAX;
|
||||
const mapVersion = await getDemoMapSettleVersion(page);
|
||||
const mapVersion = dashboard.getMapDataVersion();
|
||||
|
||||
ctx.cursor = await smoothDragSliderThumb(
|
||||
page,
|
||||
|
|
@ -121,7 +118,7 @@ export async function sceneTravelTimeSlider(ctx: SceneCtx): Promise<void> {
|
|||
);
|
||||
|
||||
await sleep(220);
|
||||
await waitForDemoMapSettled(page, 16000, mapVersion);
|
||||
await dashboard.waitForMapSettled(mapVersion, 16000);
|
||||
await showCaption(page, 'The map redraws around the areas that still work.');
|
||||
await sleep(720);
|
||||
await hideCaption(page);
|
||||
|
|
@ -147,13 +144,8 @@ export async function sceneClusterClick(ctx: SceneCtx): Promise<void> {
|
|||
ctx.cursor = cluster;
|
||||
await sleep(220);
|
||||
|
||||
await zoomMapWithWheel(page, cluster);
|
||||
|
||||
const clicked = await clickHexagon(page, cluster);
|
||||
ctx.cursor = clicked;
|
||||
await openDemoHexagon(page);
|
||||
await page.locator('[data-tutorial="right-pane"]').waitFor({ state: 'visible', timeout: 5000 });
|
||||
await waitForDemoSelectionReady(page, 16000);
|
||||
await zoomMapWithWheel(ctx, cluster);
|
||||
ctx.cursor = await clickVisibleHexagon(ctx);
|
||||
await sleep(360);
|
||||
await showCaption(
|
||||
page,
|
||||
|
|
@ -163,35 +155,48 @@ export async function sceneClusterClick(ctx: SceneCtx): Promise<void> {
|
|||
await hideCaption(page);
|
||||
}
|
||||
|
||||
async function clickHexagon(
|
||||
page: Page,
|
||||
target: { x: number; y: number }
|
||||
): Promise<{ x: number; y: number }> {
|
||||
await visualClick(page, target);
|
||||
await sleep(140);
|
||||
return target;
|
||||
}
|
||||
|
||||
async function zoomMapWithWheel(page: Page, target: { x: number; y: number }): Promise<void> {
|
||||
async function zoomMapWithWheel(ctx: SceneCtx, target: { x: number; y: number }): Promise<void> {
|
||||
const { page, dashboard } = ctx;
|
||||
const mapVersion = dashboard.getMapDataVersion();
|
||||
await page.mouse.move(target.x, target.y);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await page.mouse.wheel(0, -120);
|
||||
await sleep(95);
|
||||
}
|
||||
await waitForCurrentDemoMapSettled(page, 16000);
|
||||
await dashboard.waitForMapSettled(mapVersion, 16000);
|
||||
await sleep(260);
|
||||
}
|
||||
|
||||
async function openDemoHexagon(page: Page): Promise<void> {
|
||||
const selected = await page.evaluate(
|
||||
() =>
|
||||
(
|
||||
window as typeof window & {
|
||||
__demoOpenBestHexagon?: () => string | null;
|
||||
}
|
||||
).__demoOpenBestHexagon?.() ?? null
|
||||
async function clickVisibleHexagon(ctx: SceneCtx): Promise<{ x: number; y: number }> {
|
||||
const candidates = await ctx.dashboard.visibleHexagonTargets(8);
|
||||
const startedAt = ctx.dashboard.getSelectionStatsVersion();
|
||||
let lastError: Error | null = null;
|
||||
|
||||
for (const target of candidates) {
|
||||
await moveAndClickHexagon(ctx, target);
|
||||
try {
|
||||
await ctx.dashboard.waitForSelectionReady(startedAt, 7000);
|
||||
return { x: target.x, y: target.y };
|
||||
} catch (error) {
|
||||
if (ctx.dashboard.getSelectionStatsVersion() > startedAt) {
|
||||
return { x: target.x, y: target.y };
|
||||
}
|
||||
lastError = error instanceof Error ? error : new Error(String(error));
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Could not open a map selection from the visible hexagons${
|
||||
lastError ? `: ${lastError.message}` : ''
|
||||
}`
|
||||
);
|
||||
if (!selected) throw new Error('Could not open a demo hexagon selection');
|
||||
}
|
||||
|
||||
async function moveAndClickHexagon(ctx: SceneCtx, target: HexagonClickTarget): Promise<void> {
|
||||
await smoothMove(ctx.page, ctx.cursor, { x: target.x, y: target.y }, { durationMs: 420 });
|
||||
ctx.cursor = { x: target.x, y: target.y };
|
||||
await ctx.page.mouse.click(target.x, target.y);
|
||||
await sleep(140);
|
||||
}
|
||||
|
||||
/** Export the current shortlist, then reveal the URL. */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue