Files
codex-bot 4845826deb
Shanhe source and offline package checks / verify (pull_request) Failing after 5s
feat: sync Shanhe 2.2 source and add remote package verification
2026-11-01 17:13:46 +08:00

24 lines
10 KiB
JavaScript

import {chooseProvince,setDifficulty,startJourney} from './helpers.mjs';
import {chromium} from 'playwright';import assert from 'node:assert/strict';import fs from 'node:fs';
const browser=await chromium.launch({executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',headless:true});const results=[],errors=[],network=[];
const watch=p=>{p.on('pageerror',e=>errors.push(e.message));p.on('request',r=>{if(!r.url().startsWith('http://localhost:4173'))network.push(r.url());});};
const state=p=>p.evaluate(()=>window.shanheDebug.state());
const context=await browser.newContext({viewport:{width:1440,height:1000},deviceScaleFactor:1});const page=await context.newPage();watch(page);
await page.goto('http://localhost:4173');await page.waitForFunction(()=>window.shanheDebug);assert.equal((await state(page)).status,'entry');assert.equal((await state(page)).speed,4);assert.equal(await page.locator('#entry-screen').isVisible(),true);assert.equal(await page.locator('#game-screen').isVisible(),false);await page.waitForTimeout(300);assert.equal((await state(page)).dropped,0);await page.screenshot({path:'artifacts/desktop.png',fullPage:true});results.push('入口先选地区与吸附难度,未开局不生成碎片');
await chooseProvince(page,'440000');await setDifficulty(page,{snap:2});assert.equal((await state(page)).level,'440000');await startJourney(page);assert.equal((await state(page)).snap,2);assert.equal((await state(page)).status,'playing');assert.equal(await page.locator('#entry-screen').isVisible(),false);assert.equal(await page.locator('.controls button').count(),1);assert.equal(await page.locator('#rotate,#drop,#shake,#hint,#shuffle,#left,#right,#back,#up,#board-name,.game-tools').count(),0);assert.equal(await page.locator('#total').textContent(),'21');results.push('选广东21市和严丝合缝后确认开局,底部仅一个圆形暂停图标,旧操作按钮和容器小字已移除');
const bounds=await page.locator('#tank').boundingBox();assert.ok(bounds.width<=440);const frameStyle=await page.locator('#tank').evaluate(e=>({left:parseFloat(getComputedStyle(e).borderLeftWidth),bottom:parseFloat(getComputedStyle(e).borderBottomWidth)}));assert.ok(frameStyle.left>=7&&frameStyle.bottom>=9);
await page.keyboard.down('ArrowLeft');await page.waitForFunction(()=>{const s=window.shanheDebug.state();return s.shakeOffset<0&&s.shakeCount>=2;});const a=await state(page);assert.ok(a.shakeOffset<0&&a.shakeCount>=2);const x=await page.locator('#tank').evaluate(e=>getComputedStyle(e).transform);assert.notEqual(x,'none');await page.keyboard.up('ArrowLeft');await page.waitForTimeout(350);const released=(await state(page)).shakeCount;await page.waitForTimeout(350);assert.equal((await state(page)).shakeCount,released);await page.keyboard.press('ArrowUp');assert.equal((await state(page)).shakeCount,released+1);await page.keyboard.press('ArrowDown');await page.keyboard.press('s');assert.equal((await state(page)).shakeCount,released+1);assert.ok((await state(page)).regions.every(r=>Number.isFinite(r.angle)&&Math.abs(r.angularVelocity)<=.0251));results.push('按住左右键驱动整个框,松开停止输入,上键轻颠,旧旋转/加速/S快捷键无效');
await page.locator('#board').press('Enter');await page.locator('#board').press('Enter');await page.waitForFunction(()=>shanheDebug.state().dropped>=2);await page.screenshot({path:'artifacts/city-desktop.png',fullPage:true});
await page.locator('#pause').click();assert.equal((await state(page)).status,'paused');const paused=await state(page);await page.mouse.move(bounds.x+bounds.width/2,bounds.y+160);await page.mouse.down();await page.mouse.move(bounds.x+bounds.width/2+60,bounds.y+220);await page.mouse.up();await page.waitForTimeout(200);assert.deepEqual((await state(page)).regions,paused.regions);await page.getByRole('button',{name:'继续游戏',exact:true}).click();assert.equal((await state(page)).status,'playing');results.push('暂停冻结物理,容器滑动不产生输入');
await page.locator('#pause').click();await page.getByRole('button',{name:'继续游戏',exact:true}).click();assert.equal((await state(page)).status,'playing');await page.locator('#pause').click();await page.getByRole('button',{name:'结束旅程',exact:true}).click();assert.equal((await state(page)).screen,'result');await page.locator('#result-back').click();assert.equal((await state(page)).status,'entry');assert.equal((await state(page)).speed,4);assert.equal((await state(page)).dropped,0);await chooseProvince(page,null);await startJourney(page);assert.equal((await state(page)).level,'100000');assert.equal(await page.locator('#total').textContent(),'34');await page.locator('#pause').click();await page.getByRole('button',{name:'重新开局',exact:true}).click();assert.equal((await state(page)).status,'paused');await page.locator('#confirm-journey').click();assert.equal((await state(page)).dropped,0);assert.equal((await state(page)).shakeCount,0);results.push('返回确认、重新选择、重新开局均清理旧局与按键状态');await context.close();
const mobile=await browser.newContext({viewport:{width:390,height:844},deviceScaleFactor:2,isMobile:true,hasTouch:true});const mp=await mobile.newPage();watch(mp);await mp.goto('http://localhost:4173');await mp.waitForFunction(()=>window.shanheDebug);assert.ok(await mp.evaluate(()=>document.documentElement.scrollHeight<=innerHeight+1));await mp.screenshot({path:'artifacts/mobile.png',fullPage:true});await startJourney(mp);assert.equal((await state(mp)).status,'playing');const board=await mp.locator('#board').boundingBox();const session=await mobile.newCDPSession(mp);await session.send('Input.dispatchTouchEvent',{type:'touchStart',touchPoints:[{x:board.x+60,y:board.y+board.height*.7}]});await session.send('Input.dispatchTouchEvent',{type:'touchMove',touchPoints:[{x:board.x+160,y:board.y+board.height*.7-30}]});await session.send('Input.dispatchTouchEvent',{type:'touchEnd',touchPoints:[]});await mp.waitForTimeout(90);assert.ok((await state(mp)).swipeCount>0);assert.equal((await state(mp)).swiping,false);await mp.screenshot({path:'artifacts/mobile-playing.png',fullPage:true});
for(const viewport of [{width:390,height:844},{width:320,height:568},{width:740,height:390}]){await mp.setViewportSize(viewport);await mp.waitForFunction(()=>{const r=document.querySelector('.controls').getBoundingClientRect();return r.bottom<=innerHeight+1;});const b=await mp.locator('.controls').boundingBox();assert.ok(b.y+b.height<=viewport.height+1,JSON.stringify(viewport)+' controls on screen');assert.ok(await mp.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));const saved=await state(mp);assert.equal(saved.status,'playing');assert.ok(saved.scale>0);}
results.push('390/320窄屏及横屏:框体与圆形菜单按钮同屏,无横向溢出,缩放保留局面');await mobile.close();
const responsive=await browser.newContext({viewport:{width:320,height:568},isMobile:true,hasTouch:true});const rp=await responsive.newPage();watch(rp);await rp.goto('http://localhost:4173');await rp.waitForFunction(()=>window.shanheDebug);
for(const viewport of [{width:320,height:568},{width:375,height:667},{width:390,height:844},{width:740,height:390}]){await rp.setViewportSize(viewport);for(const chosen of [null,'330000']){await chooseProvince(rp,chosen);const fit=await rp.evaluate(()=>({scroll:document.documentElement.scrollHeight,viewport:innerHeight,start:document.getElementById('start').getBoundingClientRect().bottom}));assert.ok(fit.scroll<=fit.viewport+1&&fit.start<=fit.viewport,JSON.stringify({viewport,chosen,fit}));}await rp.screenshot({path:'artifacts/entry-'+viewport.width+'x'+viewport.height+'.png'});}
await rp.setViewportSize({width:320,height:568});await rp.addStyleTag({content:':root{--safe-area-inset-top:44px;--safe-area-inset-bottom:34px}'});await rp.evaluate(()=>dispatchEvent(new Event('resize')));await chooseProvince(rp,null);assert.equal(await rp.locator('#reference [data-province]').count(),34);await chooseProvince(rp,'710000');assert.equal(await rp.locator('#start').isEnabled(),true);assert.equal(await rp.locator('#atlas-mode').textContent(),'县市拼图');assert.ok((await rp.locator('#atlas-tip').textContent()).includes('移位示意'));await chooseProvince(rp,'330000');assert.equal((await state(rp)).level,'330000');results.push('入口全国/省区在320/375/390及横屏一屏显示;含44/34安全区;34省区均可点选,台湾县市关卡可玩且标注移位示意');await responsive.close();
const sdk=await browser.newContext();const sp=await sdk.newPage();watch(sp);await sp.addInitScript(()=>{Storage.prototype.getItem=()=>{throw Error('Storage denied');};window.xhs={miniTool:{getLaunchOptions:async()=>({miniToolEnv:{buildVersion:9462004}}),getStorage:async()=>({data:JSON.stringify({'100000:4':999})}),setStorage:async()=>({})}};});await sp.goto('http://localhost:4173');await sp.locator('#journey-records').click();await sp.waitForFunction(()=>document.querySelector('[data-journal-level="100000"] .journal-best strong').textContent==='999');results.push('原生Storage在入口展示所选关卡最佳纪录');await sdk.close();
const migration=await browser.newContext();const mg=await migration.newPage();watch(mg);await mg.addInitScript(()=>{localStorage.setItem('shanhe-records-v1',JSON.stringify({'100000:4':1234}));window.savedNative=null;window.xhs={launchOptions:{miniToolEnv:{buildVersion:9460001}},miniTool:{getStorage:async()=>({data:JSON.stringify({'100000:4':999})}),setStorage:async(o)=>{window.savedNative=JSON.parse(o.data);}}};});await mg.goto('http://localhost:4173');await mg.waitForFunction(()=>window.savedNative&&window.savedNative.records&&window.savedNative.records['100000:4']===1234);results.push('旧纪录升级迁移到原生Storage并保留较高分');await migration.close();
const noStorage=await browser.newContext();const np=await noStorage.newPage();watch(np);await np.addInitScript(()=>{Storage.prototype.getItem=()=>{throw Error('denied');};Storage.prototype.setItem=()=>{throw Error('denied');};});await np.goto('http://localhost:4173');await np.waitForFunction(()=>window.shanheDebug);await startJourney(np);assert.equal((await state(np)).status,'playing');results.push('无SDK且存储被禁时,仍可从入口开局');await noStorage.close();
assert.deepEqual(errors,[]);assert.deepEqual(network,[]);results.push('运行无页面异常、无外部网络请求');fs.writeFileSync('artifacts/browser-report.json',JSON.stringify({passed:true,results,errors,externalRequests:network},null,2));console.log(results.join('\n'));await browser.close();