Files
Codeman/test/mobile/helpers/touch-sim.ts
T
Codeman maintainer f604deba3e chore: remove backend code nothing references
Found with knip (exports, types, class members) and confirmed by a text
search of src, test, scripts and packages before each removal.

- Per-CLI get<Cli>NotFoundMessage() (nine of them) with their message
  constants: missingCliMessage() builds that text from the registry now.
- Registry leftovers: renderCliCommand, asCliId, cliIds, isCliRunnable,
  createCliResolverForTest, profileExists, and two name lists that only
  repeated schema enums.
- A task-description cache in Session that was written on every chunk of
  terminal output (one regex pass per line) and never read since the
  subagent watcher takes descriptions from transcripts. The cache module,
  its pattern and both parse methods go with it.
- Session.parentAgentId / childAgentIds: never set by anything, so the two
  optional SessionState fields were never on the wire.
- Unreferenced methods on TmuxManager (pipe-pane pair, syncMouseMode),
  RespawnController, StateStore.recoverFromBackup (load() already falls
  back to the .bak file), TeamWatcher, SseStreamManager and the AI
  checkers' getErrorVerdict.
- updateCaseEnvVars: the settings.local.json env path that envOverrides
  replaced.
- Re-exports in the utils, cli-registry, routes and prompts barrels that
  no importer used, and unused test helpers (MockStateStore, the mobile
  throttle helpers and others).

Eight mobile tests imported the WebServer type from a path that does not
exist (../src instead of ../../src); type-only, so nothing failed.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-13 13:58:22 +01:00

246 lines
6.8 KiB
TypeScript

import type { Page } from 'playwright';
import { getCDP, dispatchTouchEvent } from './cdp.js';
import { SWIPE } from './constants.js';
export type SwipeDirection = 'left' | 'right' | 'up' | 'down';
export interface SwipeOptions {
/** Distance in pixels (default: SWIPE.MIN_DISTANCE + 20 = 100) */
distance?: number;
/** Duration in ms (default: 150, must be < SWIPE.MAX_TIME) */
duration?: number;
/** Number of intermediate touch move points (default: 5) */
steps?: number;
/** Start position override (default: center of target element) */
startPoint?: { x: number; y: number };
}
// ─── CDP Touch (Chromium, trusted events) ───
/** Perform a swipe gesture via CDP Input.dispatchTouchEvent (trusted, Chromium only).
* Target element defaults to '.main' (where SwipeHandler listens). */
export async function swipeViaCDP(
page: Page,
direction: SwipeDirection,
options: SwipeOptions = {},
): Promise<void> {
const {
distance = SWIPE.MIN_DISTANCE + 20,
duration = 150,
steps = 5,
} = options;
const cdp = await getCDP(page);
const targetSelector = '.main';
const box = await page.locator(targetSelector).boundingBox();
if (!box) throw new Error(`Element ${targetSelector} not found`);
let startX: number, startY: number, endX: number, endY: number;
if (options.startPoint) {
startX = options.startPoint.x;
startY = options.startPoint.y;
} else {
startX = box.x + box.width / 2;
startY = box.y + box.height / 2;
}
// Calculate end point based on direction
switch (direction) {
case 'left':
startX = box.x + box.width * 0.75;
endX = startX - distance;
endY = startY;
break;
case 'right':
startX = box.x + box.width * 0.25;
endX = startX + distance;
endY = startY;
break;
case 'up':
endX = startX;
endY = startY - distance;
break;
case 'down':
endX = startX;
endY = startY + distance;
break;
}
// Touch start
await dispatchTouchEvent(cdp, 'touchStart', [{ x: startX, y: startY }]);
// Intermediate moves
const stepDelay = duration / steps;
for (let i = 1; i <= steps; i++) {
const progress = i / steps;
const x = startX + (endX! - startX) * progress;
const y = startY + (endY! - startY) * progress;
await dispatchTouchEvent(cdp, 'touchMove', [{ x, y }]);
await page.waitForTimeout(stepDelay);
}
// Touch end
await dispatchTouchEvent(cdp, 'touchEnd', []);
}
// ─── Synthetic Touch (cross-engine fallback) ───
/** Perform a swipe gesture via synthetic TouchEvent dispatch.
* Events have isTrusted=false but still trigger handler callbacks. */
export async function swipeViaSynthetic(
page: Page,
direction: SwipeDirection,
options: SwipeOptions = {},
): Promise<void> {
const {
distance = SWIPE.MIN_DISTANCE + 20,
duration = 150,
steps = 5,
} = options;
await page.evaluate(
({ dir, dist, dur, numSteps }) => {
const main = document.querySelector('.main');
if (!main) return;
const rect = main.getBoundingClientRect();
let startX = rect.left + rect.width / 2;
let startY = rect.top + rect.height / 2;
let endX = startX;
let endY = startY;
switch (dir) {
case 'left':
startX = rect.left + rect.width * 0.75;
endX = startX - dist;
break;
case 'right':
startX = rect.left + rect.width * 0.25;
endX = startX + dist;
break;
case 'up':
endY = startY - dist;
break;
case 'down':
endY = startY + dist;
break;
}
function createTouch(x: number, y: number) {
return new Touch({
identifier: 0,
target: main!,
clientX: x,
clientY: y,
pageX: x,
pageY: y,
});
}
// touchstart
main.dispatchEvent(new TouchEvent('touchstart', {
touches: [createTouch(startX, startY)],
changedTouches: [createTouch(startX, startY)],
bubbles: true,
cancelable: true,
}));
// Intermediate moves
const stepDelay = dur / numSteps;
for (let i = 1; i <= numSteps; i++) {
const progress = i / numSteps;
const x = startX + (endX - startX) * progress;
const y = startY + (endY - startY) * progress;
setTimeout(() => {
main.dispatchEvent(new TouchEvent('touchmove', {
touches: [createTouch(x, y)],
changedTouches: [createTouch(x, y)],
bubbles: true,
cancelable: true,
}));
}, stepDelay * i);
}
// touchend
setTimeout(() => {
main.dispatchEvent(new TouchEvent('touchend', {
touches: [],
changedTouches: [createTouch(endX, endY)],
bubbles: true,
cancelable: true,
}));
}, dur + 10);
},
{ dir: direction, dist: distance, dur: duration, numSteps: steps },
);
// Wait for the full gesture + a little buffer
await page.waitForTimeout(duration + 100);
}
// ─── Unified API ───
/** Perform a swipe gesture using the best available method.
* Uses CDP for Chromium (trusted events), synthetic for WebKit. */
export async function swipe(
page: Page,
direction: SwipeDirection,
options: SwipeOptions & { isChromium?: boolean } = {},
): Promise<void> {
const { isChromium = true, ...swipeOpts } = options;
if (isChromium) {
try {
await swipeViaCDP(page, direction, swipeOpts);
return;
} catch {
// Fall through to synthetic
}
}
await swipeViaSynthetic(page, direction, swipeOpts);
}
/** Tap an element via CDP (trusted touch) */
export async function tapViaCDP(page: Page, selector: string): Promise<void> {
const cdp = await getCDP(page);
const box = await page.locator(selector).boundingBox();
if (!box) throw new Error(`Element ${selector} not found for tap`);
const x = box.x + box.width / 2;
const y = box.y + box.height / 2;
await dispatchTouchEvent(cdp, 'touchStart', [{ x, y }]);
await page.waitForTimeout(50);
await dispatchTouchEvent(cdp, 'touchEnd', []);
}
/** Tap an element via synthetic touch (cross-engine) */
export async function tapViaSynthetic(page: Page, selector: string): Promise<void> {
await page.evaluate((sel) => {
const el = document.querySelector(sel);
if (!el) return;
const rect = el.getBoundingClientRect();
const x = rect.left + rect.width / 2;
const y = rect.top + rect.height / 2;
const touch = new Touch({
identifier: 0,
target: el,
clientX: x,
clientY: y,
});
el.dispatchEvent(new TouchEvent('touchstart', {
touches: [touch],
changedTouches: [touch],
bubbles: true,
}));
el.dispatchEvent(new TouchEvent('touchend', {
touches: [],
changedTouches: [touch],
bubbles: true,
}));
}, selector);
}