110 lines
8.2 KiB
JavaScript
110 lines
8.2 KiB
JavaScript
import { chromium } from '../tools/playwright/node_modules/playwright/index.mjs';
|
|
import { readFileSync } from 'node:fs';
|
|
import assert from 'node:assert/strict';
|
|
const source = readFileSync(new URL('../crates/lazyboy-core/src/novnc_skin.rs', import.meta.url), 'utf8');
|
|
const skin = source.split('r###"')[1].split('"###;')[0];
|
|
const browser = await chromium.launch({headless:true});
|
|
try {
|
|
for (const mobile of [false,true]) {
|
|
const context=await browser.newContext({viewport:mobile?{width:390,height:740}:{width:1100,height:800},hasTouch:mobile,isMobile:mobile});
|
|
const page=await context.newPage();page.setDefaultTimeout(10000);
|
|
const errors=[];page.on('pageerror',e=>{errors.push(e.message);console.error(e.message)});
|
|
await page.route('http://desktop.test/**', route=>route.request().url().endsWith('/host.html')
|
|
? route.fulfill({contentType:'text/html',body:'<iframe src="/vnc.html" style="width:380px;height:600px"></iframe>'})
|
|
: route.request().url().endsWith('/core/rfb.js')
|
|
? route.fulfill({contentType:'application/javascript',body:'export default {messages:{extendedClipboardProvide(sock,formats,text){window.pastes.push(text[0])},extendedClipboardRequest(){}}};'})
|
|
: route.request().url().endsWith('/app/ui.js')
|
|
? route.fulfill({contentType:'application/javascript',body:`
|
|
const session = new EventTarget();
|
|
Object.assign(session, {_canvas:document.querySelector('canvas'),_screen:document.querySelector('#screen'),_display:{_fbWidth:1200,_fbHeight:800},_rfbConnectionState:'connected',_sendMouse(x,y,mask){window.pointerEvents.push({x,y,mask})},clipboardPasteFrom(text){window.pastes.push(text)},sendKey(...key){window.keys.push(key)}});
|
|
window.session=session; window.pastes=[];window.keys=[];window.pointerEvents=[];
|
|
export default {rfb:session,toggleVirtualKeyboard(){window.keyboardOpened=true}};`})
|
|
: route.fulfill({contentType:'text/html',body:`<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><style>#noVNC_container{border-bottom-right-radius:800px 600px}</style><body><div id="noVNC_container"><div id="screen" style="display:flex;width:100%;height:100%"><canvas width="1200" height="800"></canvas></div></div><textarea id="noVNC_clipboard_text" hidden></textarea>${skin}</body>`}));
|
|
await page.goto('http://desktop.test/vnc.html');
|
|
await page.waitForFunction(()=>window.session?.__lazyboyBound);
|
|
const bounds=()=>page.locator('canvas').boundingBox();
|
|
assert.equal(await page.locator("#noVNC_container").evaluate(el=>getComputedStyle(el).borderBottomRightRadius),"0px");
|
|
const initial=await bounds();assert.ok(Math.abs(initial.width/initial.height-1.5)<.01);
|
|
assert.equal(await page.locator('#lazyboy-tools').isVisible(),mobile);
|
|
if(mobile) await page.getByRole('button',{name:'放大',exact:true}).click();
|
|
else await page.keyboard.press('Control+Alt+Equal');
|
|
const zoomed=await bounds(); assert.ok(zoomed.width>initial.width*1.2);
|
|
const coords=await page.evaluate(()=>[session._display.absX(session._canvas.clientWidth/2),session._display.absY(session._canvas.clientHeight/2)]);
|
|
assert.deepEqual(coords,[600,400]);
|
|
if(mobile){
|
|
await page.getByRole('button',{name:'移動畫面'}).click();
|
|
assert.equal(await page.locator('#lazyboy-pan').isVisible(),true);
|
|
await page.getByRole('button',{name:'移動畫面'}).click();
|
|
}
|
|
if(mobile) await page.getByRole('button',{name:'剪貼簿 / 輸入'}).click();
|
|
else await page.keyboard.press('Control+Alt+KeyB');
|
|
const input=page.locator('#lazyboy-clipboard-text');
|
|
await input.fill('手機中文貼上 test');
|
|
await page.getByRole('button',{name:'貼到電腦',exact:true}).click();
|
|
await page.waitForTimeout(300);
|
|
await page.getByRole('button',{name:'貼到電腦',exact:true}).click();
|
|
await page.waitForTimeout(100);
|
|
assert.deepEqual(await page.evaluate(()=>pastes),['手機中文貼上 test','手機中文貼上 test']);
|
|
assert.equal(await page.evaluate(()=>keys.filter(k=>k[1]==='KeyV'&&k[2]).length),2);
|
|
await page.evaluate(()=>session.dispatchEvent(new CustomEvent('clipboard',{detail:{text:'電腦回傳 ✓'}})));
|
|
assert.equal(await input.inputValue(),'電腦回傳 ✓');
|
|
await page.getByRole('button',{name:'複製到本機'}).click();
|
|
assert.equal(await input.evaluate(el=>el.selectionEnd-el.selectionStart),'電腦回傳 ✓'.length);
|
|
await page.getByRole('button',{name:'關閉',exact:true}).click();
|
|
if(mobile) {
|
|
await page.getByRole('button',{name:'鍵盤',exact:true}).click();
|
|
assert.equal(await page.evaluate(()=>keyboardOpened),true);
|
|
await page.getByRole('button',{name:'Enter',exact:true}).click();
|
|
assert.ok(await page.evaluate(()=>keys.some(k=>k[1]==='Enter'&&k[2])));
|
|
const pad=page.locator('#lazyboy-trackpad');
|
|
const box=await pad.boundingBox(), x=box.x+box.width/2,y=box.y+box.height/2;
|
|
await page.mouse.move(x,y);await page.mouse.down();await page.mouse.move(x+30,y+10,{steps:4});await page.mouse.up();
|
|
assert.ok(await page.evaluate(()=>pointerEvents.some(e=>e.mask===0 && e.x>session._canvas.clientWidth/2)));
|
|
await page.getByRole('button',{name:'右鍵',exact:true}).click();
|
|
assert.deepEqual(await page.evaluate(()=>pointerEvents.slice(-2).map(e=>e.mask)),[4,0]);
|
|
await page.getByRole('button',{name:'拖曳',exact:true}).click();
|
|
await page.mouse.move(x,y);await page.mouse.down();await page.mouse.move(x-30,y,{steps:3});await page.mouse.up();
|
|
assert.equal(await page.evaluate(()=>pointerEvents.at(-1).mask),1);
|
|
await page.getByRole('button',{name:'放開拖曳',exact:true}).click();
|
|
assert.equal(await page.evaluate(()=>pointerEvents.at(-1).mask),0);
|
|
const cdp=await context.newCDPSession(page);
|
|
await cdp.send('Input.dispatchTouchEvent',{type:'touchStart',touchPoints:[{x:x-20,y},{x:x+20,y}]});
|
|
await cdp.send('Input.dispatchTouchEvent',{type:'touchMove',touchPoints:[{x:x-20,y:y-30},{x:x+20,y:y-30}]});
|
|
await cdp.send('Input.dispatchTouchEvent',{type:'touchEnd',touchPoints:[]});
|
|
assert.ok(await page.evaluate(()=>pointerEvents.some(e=>e.mask===16)),'two fingers scroll');
|
|
await page.evaluate(()=>pointerEvents=[]);
|
|
await cdp.send('Input.dispatchTouchEvent',{type:'touchStart',touchPoints:[{x:x-20,y},{x:x+20,y}]});
|
|
await cdp.send('Input.dispatchTouchEvent',{type:'touchEnd',touchPoints:[]});
|
|
assert.deepEqual(await page.evaluate(()=>pointerEvents.map(e=>e.mask)),[4,0],'two finger tap is only right click');
|
|
await page.getByRole('button',{name:'拖曳',exact:true}).click();
|
|
await page.evaluate(()=>window.dispatchEvent(new Event('blur')));
|
|
assert.equal(await page.evaluate(()=>pointerEvents.at(-1).mask),0,'blur releases held button');
|
|
await page.evaluate(()=>{pointerEvents=[];session._viewOnly=true});
|
|
await page.getByRole('button',{name:'左鍵',exact:true}).click();
|
|
assert.equal(await page.evaluate(()=>pointerEvents.length),0);
|
|
await page.evaluate(()=>session._viewOnly=false);
|
|
await page.screenshot({path:'.run/mobile-trackpad-portrait.png'});
|
|
await page.setViewportSize({width:844,height:390});
|
|
await page.waitForFunction(()=>document.documentElement.dataset.landscape==='true');
|
|
const panel=await page.locator('#lazyboy-tools').boundingBox(),screen=await page.locator('#noVNC_container').boundingBox();
|
|
assert.ok(panel.x+panel.width<=screen.x+1,'landscape controls belong on the left');
|
|
await page.screenshot({path:'.run/mobile-trackpad-landscape.png'});
|
|
}
|
|
await page.evaluate(()=>{session._clipboardServerCapabilitiesFormats={1:true};session._clipboardServerCapabilitiesActions={[1<<28]:true};});
|
|
if(mobile) await page.getByRole('button',{name:'剪貼簿 / 輸入'}).click();
|
|
else await page.keyboard.press('Control+Alt+KeyB');
|
|
await input.fill('UTF-8 中文 ✓');
|
|
await page.getByRole('button',{name:'貼到電腦',exact:true}).click();
|
|
assert.equal(await page.evaluate(()=>pastes.at(-1)),'UTF-8 中文 ✓');
|
|
assert.deepEqual(errors,[]);
|
|
console.log(`PASS ${mobile?'mobile':'desktop'} scaling, coordinates, repeated paste, remote clipboard, fallback and keyboard`);
|
|
if(!mobile){
|
|
await page.goto('http://desktop.test/host.html');
|
|
const inner=page.frameLocator('iframe');
|
|
await inner.locator('#lazyboy-tools').waitFor({state:'attached'});
|
|
assert.equal(await inner.locator('#lazyboy-tools').isVisible(),false,'narrow desktop iframe is not a phone');
|
|
}
|
|
await context.close();
|
|
}
|
|
} finally {await browser.close();}
|