const space = Number(process.env.GARDEN_SPACE); if (!space || !process.env.GARDEN_QA_DIR) throw new Error('请设置 GARDEN_SPACE 与 GARDEN_QA_DIR,复用当前 QA 空间。'); const task = await taskSpace(space), page = task.page('p1'); const fs = await import('node:fs/promises'); const root = process.env.GARDEN_QA_DIR; await page.reload(); await page.waitForFunction(() => !!window.__garden); const saved = await page.evaluate(() => JSON.parse(localStorage.getItem('mode:02-connect-1234:v1'))); if (Object.keys(saved.levels).length !== 10) throw new Error('completion not persisted'); let before = await page.evaluate(() => window.__garden.snapshot()); const touchHint = async () => { const data = await page.evaluate(() => { const g=window.__garden, s=g.snapshot(), t=g.transform(), r=document.getElementById('board').getBoundingClientRect(); return { merges:s.stats.merges, points:g.hint().map(id=>{const b=s.balls.find(b=>b.id===id);return {x:r.left+t.x+b.x*t.scale,y:r.top+t.y+b.y*t.scale};})}; }); for (let i=0;iwindow.__garden.snapshot().stats.merges===n+1&&!window.__garden.animating(),data.merges,{timeout:5000}); }; await touchHint(); const after = await page.evaluate(() => window.__garden.snapshot()); await page.click('#undo'); if (JSON.stringify(await page.evaluate(()=>window.__garden.snapshot()))!==JSON.stringify(before)) throw new Error('undo not exact'); await touchHint(); if (JSON.stringify(await page.evaluate(()=>window.__garden.snapshot()))!==JSON.stringify(after)) throw new Error('redo not deterministic'); await page.click('#undo'); await page.click('#shuffle'); const shuffled = await page.evaluate(()=>window.__garden.snapshot()); if (shuffled.shuffles!==0 || shuffled.movesLeft!==before.movesLeft || JSON.stringify(shuffled.balls.map(b=>b.n).sort())!==JSON.stringify(before.balls.map(b=>b.n).sort())) throw new Error('shuffle mismatch'); await page.click('#undo'); if (JSON.stringify(await page.evaluate(()=>window.__garden.snapshot()))!==JSON.stringify(before)) throw new Error('shuffle undo mismatch'); await page.click('#levels'); await page.click('[data-action="endless"]'); await touchHint(); if ((await page.evaluate(()=>window.__garden.snapshot())).movesLeft!==-1) throw new Error('endless consumed move'); await page.click('#levels'); await page.click('[data-level="8"]'); const layouts=[]; for (const [width,height,mobile] of [[320,568,true],[360,740,true],[390,844,true],[430,932,true],[844,390,true],[1280,900,false]]) { await page.cdp('Emulation.setDeviceMetricsOverride',{width,height,deviceScaleFactor:2,mobile}); await page.evaluate(()=>new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)))); const layout=await page.evaluate(()=>({width:innerWidth,height:innerHeight,overflow:document.documentElement.scrollWidth>innerWidth+1,bodyHeight:document.documentElement.scrollHeight,buttons:[...document.querySelectorAll('.tools button,.header-actions button')].map(b=>({id:b.id,w:b.getBoundingClientRect().width,h:b.getBoundingClientRect().height}))})); if(layout.overflow)throw new Error(`horizontal overflow at ${width}`); if(layout.buttons.some(b=>b.w<44||b.h<44))throw new Error(`small touch target at ${width}`); layouts.push(layout); if(width===320||width===1280)await page.screenshot({path:`${root}/${width===320?'mobile-320':'desktop'}.png`}); } await page.cdp('Emulation.setDeviceMetricsOverride',{width:390,height:844,deviceScaleFactor:2,mobile:true}); await page.click('#help'); await page.waitForFunction(()=>!document.getElementById('sheet').getAnimations().some(a=>a.playState==='running')); await page.screenshot({path:`${root}/mobile-help.png`}); await page.keyboard.press('Escape'); if(!(await page.evaluate(()=>document.getElementById('modal').hidden)))throw new Error('escape not closed'); await fs.writeFile(`${root}/layout.json`,JSON.stringify({checks:['刷新保留十关成绩','撤销精确恢复盘面及补球','重走结果确定一致','打乱保留数字和步数','撤销恢复打乱库存','自由模式无步数限制','帮助可用 Escape 关闭'],layouts},null,2)); console.log(JSON.stringify(layouts)); console.log(await page.snapshot());