/** * 日历左右滑动手势验收(2026-09-14 用户:「日历页面还不支持左右滑动手势」)。 * * 判据必须一起看,否则"滑一下就翻页"也能算过: * ① 左滑 → 下一段(月/周/日三种刻度都走同一个 shift()) * ② 右滑 → 回到上一段(可逆) * ③ **反向对照**:纵向拖动不翻页 —— 移动端最容易犯的手势错误就是把滚动当翻页 * * 探针踩过的坑:标题真实格式是「2026 年 9 月」(**数字与"年月"之间有空格**), * 我第一版正则写成 /^(\d{4})年(\d{1,2})月/ 匹配不到 ⇒ 返回 null ⇒ 三个值全是 null, * 看起来"滑动没生效",其实是探针瞎了。所以这里先断言标题读得到。 * * 用法:AGENTMAIL_DIST= ADMIN_USER=.. ADMIN_PW=.. node test/manual/calendar-swipe-verify.mjs */ import { openApp } from './narrow-probe-helper.mjs'; const { page, ctx } = await openApp({ width: 390, height: 844 }); const results = []; const record = (name, ok, note) => { results.push({ name, ok }); console.log(` ${ok ? '通过' : '失败'} ${name}${note ? ' — ' + note : ''}`); }; try { await page.waitForSelector('.narrow-nav', { timeout: 25000 }); await page.click('.narrow-nav button:has-text("日历")'); await page.waitForTimeout(1500); const title = () => page.evaluate(() => { const rx = /\d{4}\s*年\s*\d{1,2}\s*月/; const c = [...document.querySelectorAll('*')] .filter(e => rx.test(e.textContent) && e.children.length <= 1) .map(e => e.textContent.trim()) .filter(t => t.length < 40) .sort((a, b) => a.length - b.length); return c[0] ?? null; }); const swipe = (x1, y1, x2, y2) => page.evaluate( ([x1, y1, x2, y2]) => { const mk = (type, x, y) => new TouchEvent(type, { bubbles: true, cancelable: true, touches: type === 'touchend' ? [] : [new Touch({ identifier: 1, target: document.body, clientX: x, clientY: y })], changedTouches: [ new Touch({ identifier: 1, target: document.body, clientX: x, clientY: y }) ] }); const el = document.elementFromPoint(x1, y1); el.dispatchEvent(mk('touchstart', x1, y1)); el.dispatchEvent(mk('touchend', x2, y2)); }, [x1, y1, x2, y2] ); const before = await title(); record('前置:日历标题读得到(否则本判据无意义)', !!before, `标题=${before}`); await swipe(320, 420, 80, 420); await page.waitForTimeout(900); const afterLeft = await title(); record('① 左滑翻到下一段', !!afterLeft && afterLeft !== before, `${before} → ${afterLeft}`); await swipe(80, 420, 320, 420); await page.waitForTimeout(900); const afterRight = await title(); record('② 右滑滑回上一段', afterRight === before, `${afterLeft} → ${afterRight}`); await swipe(200, 300, 195, 620); await page.waitForTimeout(900); const afterVertical = await title(); record('③ 反向对照:纵向拖动不翻页', afterVertical === afterRight, `仍为 ${afterVertical}`); // ⑤ 翻页过渡动画(用户后续追问:「日历滑动页面为什么没有切换动画?」) const slide = () => page.evaluate(() => { const el = document.querySelector('[data-slide]'); if (!el) return null; return { slide: el.dataset.slide, anim: getComputedStyle(el).animationName, dur: getComputedStyle(el).animationDuration }; }); const rest = await slide(); record( '⑤ 反向对照:刚进页面时不跑翻页动画', !!rest && rest.anim === 'none', `animationName=${rest?.anim}` ); await page.click('button[aria-label="下一页"]'); await page.waitForTimeout(700); await page.click('button[aria-label="下一页"]'); await page.waitForTimeout(80); const during = await slide(); record( '⑤ 翻页时有方向正确的过渡动画', during?.slide === 'cal-slide-next' && during?.anim === 'cal-in-next' && parseFloat(during.dur) > 0, `class=${during?.slide} 动画=${during?.anim} ${during?.dur}` ); // 反方向:上一页应当是 cal-slide-prev(方向只有一个来源 = shift()) await page.waitForTimeout(900); await page.click('button[aria-label="上一页"]'); await page.waitForTimeout(80); const backDuring = await slide(); record( '⑤ 反向翻页的方向也是对的(prev)', backDuring?.slide === 'cal-slide-prev' && backDuring?.anim === 'cal-in-prev', `class=${backDuring?.slide}` ); // ④ 回到今天不该被手势判断干扰(按钮仍在) const todayBtn = await page.locator('button:has-text("今天")').count(); record('④ 手势没有吃掉「今天」按钮', todayBtn > 0, `找到 ${todayBtn} 个`); } finally { await page.close(); await ctx.close(); } const failed = results.filter(r => !r.ok); console.log(`\n 日历滑动:${results.length - failed.length}/${results.length} 通过`); if (failed.length) console.log(' 失败:' + failed.map(f => f.name).join('; ')); process.exitCode = failed.length ? 1 : 0;