// CDP 触摸手势驱动 v2: 把手立即拖动 + 长按路径, 组内/跨组落点 import WebSocket from 'ws' const C = JSON.parse((await (await fetch('http://127.0.0.1:9223/json/version')).text())) const ws = new WebSocket(C.webSocketDebuggerUrl, { perMessageDeflate: false }) let id = 0 const pending = new Map() const send = (method, params = {}, sessionId) => new Promise((res) => { const i = ++id pending.set(i, res) ws.send(JSON.stringify({ id: i, method, params, sessionId })) }) ws.on('message', (d) => { const m = JSON.parse(d) if (m.id && pending.has(m.id)) { pending.get(m.id)(m.result); pending.delete(m.id) } }) await new Promise((r) => ws.on('open', r)) const { targetId } = await send('Target.createTarget', { url: 'about:blank' }) const { sessionId } = await send('Target.attachToTarget', { targetId, flatten: true }) const S = (m, p) => send(m, p, sessionId) await S('Runtime.enable'); await S('Page.enable') await S('Emulation.setDeviceMetricsOverride', { width: 400, height: 800, deviceScaleFactor: 2, mobile: true }) await S('Emulation.setTouchEmulationEnabled', { enabled: true, maxTouchPoints: 5 }) await S('Page.navigate', { url: 'http://127.0.0.1:5199/dragtest.html' }) const sleep = (ms) => new Promise((res) => setTimeout(res, ms)) const ev = async (expr) => (await S('Runtime.evaluate', { expression: expr, returnByValue: true })).result.value const center = async (sel) => { const r = JSON.parse(await ev(`JSON.stringify(document.querySelector('${sel}').getBoundingClientRect())`)) return { x: r.left + r.width / 2, y: r.top + r.height / 2 } } for (let i = 0; i < 30; i++) { await sleep(1000); if (await ev(`!!document.querySelector('[data-habit-id="1"]')`)) break } const touch = (type, x, y) => S('Input.dispatchTouchEvent', { type, touchPoints: type === 'touchEnd' ? [] : [{ x, y, id: 1 }], }) // 用例 1: 按住 habit-1 的把手, 立即垂直拖到 habit-4 中部, 松手 → 应 DROP before/after 4 let p = await center('[data-habit-id="1"] .grip') await touch('touchStart', p.x, p.y) await sleep(60) const t = await center('[data-habit-id="4"]') await touch('touchMove', p.x, p.y + 20) await sleep(30) await touch('touchMove', t.x, t.y) await sleep(60) const cls1 = await ev(`document.querySelector('[data-habit-id="4"]').className`) console.log('case1 target class:', cls1) await touch('touchEnd') await sleep(300) // 用例 2: 按住 habit-1 把手拖到每周组 (habit-5) → 跨组, 应无落点无 DROP p = await center('[data-habit-id="1"] .grip') await touch('touchStart', p.x, p.y) await sleep(60) const w = await center('[data-habit-id="5"]') await touch('touchMove', w.x, w.y) await sleep(60) const cls2 = await ev(`document.querySelector('[data-habit-id="5"]').className`) console.log('case2 target class (应无 dragover):', cls2) await touch('touchEnd') await sleep(300) // 用例 3: 长按卡片主体 500ms 后拖到 habit-3 下半 → DROP after 3 p = await center('[data-habit-id="2"]') await touch('touchStart', p.x, p.y) await sleep(520) const t3 = await center('[data-habit-id="3"]') await touch('touchMove', t3.x, t3.y + 20) await sleep(60) await touch('touchEnd') await sleep(300) console.log('--- events:') console.log(await ev('window.__ev.join("\\n")')) ws.close()