| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- /**
- * 移动端输入框高度复位验证(2026-11-10 用户反馈)
- *
- * 现象:发文件 / 发多行文本后,输入框保持撑开的高度不恢复。
- *
- * 用法:`node scripts/verify-mobile-composer.mjs`
- */
- import { chromium } from 'playwright';
- import { mkdirSync, writeFileSync } from 'node:fs';
- import { resolve } from 'node:path';
- import { loadDevLogin } from './dev-login.mjs';
- const OUT = resolve('test-results/mobile-composer-20261110');
- mkdirSync(OUT, { recursive: true });
- const login = loadDevLogin();
- if (!login.enabled) throw new Error(`开发态登录不可用:${login.reason}`);
- const results = [];
- const check = (n, ok, d = '') => {
- results.push({ n, ok });
- console.log(`${ok ? '✅' : '❌'} ${n}${d ? ` (${d})` : ''}`);
- };
- const FILE = resolve('/tmp', `m-composer-${Date.now()}.md`);
- writeFileSync(FILE, `# 移动端测试\n\n${Date.now()}\n`);
- const b = await chromium.launch({ headless: true, channel: 'chromium' });
- const ctx = await b.newContext({
- viewport: { width: 390, height: 844 },
- isMobile: true,
- hasTouch: true,
- });
- const page = await ctx.newPage();
- await page.addInitScript((d) => {
- localStorage.setItem('fmode_session_token', d.token);
- localStorage.setItem('fmode_me', JSON.stringify(d.me));
- }, login.payload);
- await page.goto('http://localhost:4200/mobile/', { waitUntil: 'domcontentloaded' });
- await page.waitForTimeout(6000);
- await page.screenshot({ path: `${OUT}/01-mobile-home.png` });
- /* 进入一个会话 */
- const item = page.locator('.m-session, .session-item, [data-session-id]').first();
- if ((await item.count()) > 0) await item.click({ timeout: 8000 }).catch(() => undefined);
- else await page.getByText(/写一份团队周报/).first().click({ timeout: 8000 }).catch(() => undefined);
- await page.waitForTimeout(5000);
- await page.screenshot({ path: `${OUT}/02-chat.png` });
- const ta = page.locator('textarea').first();
- check('① 找到输入框', (await ta.count()) > 0);
- if ((await ta.count()) === 0) await finish();
- const baseH = await ta.evaluate((el) => el.getBoundingClientRect().height);
- console.log(` 初始高度: ${baseH.toFixed(1)}px`);
- /* 输入多行文本,确认它确实会长高(证明自适应是生效的) */
- await ta.fill('第一行\n第二行\n第三行\n第四行\n第五行');
- await page.waitForTimeout(500);
- const grownH = await ta.evaluate((el) => el.getBoundingClientRect().height);
- check('② 多行输入时输入框会撑高', grownH > baseH + 5, `${baseH.toFixed(0)} → ${grownH.toFixed(0)}px`);
- /* 发送,然后看高度是否回落 */
- await ta.press('Enter');
- await page.waitForTimeout(3000);
- const afterH = await ta.evaluate((el) => el.getBoundingClientRect().height);
- check(
- '③ 发送后输入框高度恢复',
- Math.abs(afterH - baseH) < 8,
- `恢复到 ${afterH.toFixed(0)}px(初始 ${baseH.toFixed(0)}px)`,
- );
- await page.screenshot({ path: `${OUT}/03-after-send.png` });
- /* 再验一次附件场景 */
- const input = page.locator('input[type="file"]').first();
- if ((await input.count()) > 0) {
- await ta.fill('附件说明\n第二行\n第三行');
- await page.waitForTimeout(400);
- const h2 = await ta.evaluate((el) => el.getBoundingClientRect().height);
- await input.setInputFiles(FILE);
- await page.waitForTimeout(1500);
- await ta.press('Enter');
- await page.waitForTimeout(8000);
- const h3 = await ta.evaluate((el) => el.getBoundingClientRect().height);
- check(
- '④ 发附件后高度同样恢复',
- Math.abs(h3 - baseH) < 8,
- `撑到 ${h2.toFixed(0)}px → 恢复到 ${h3.toFixed(0)}px`,
- );
- await page.screenshot({ path: `${OUT}/04-after-file-send.png` });
- } else {
- check('④ 发附件后高度同样恢复', false, '未找到 file input');
- }
- await finish();
- async function finish() {
- const passed = results.filter((r) => r.ok).length;
- console.log(`\n=== 结果:${passed}/${results.length} 通过 ===`);
- console.log(`截图:${OUT}`);
- await b.close();
- process.exit(passed === results.length ? 0 : 1);
- }
|