browser-environment.test.mjs 5.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041
  1. import {test} from 'node:test'
  2. import assert from 'node:assert/strict'
  3. import {mkdtemp,writeFile,rm,mkdir} from 'node:fs/promises'
  4. import {tmpdir} from 'node:os'
  5. import {join,resolve} from 'node:path'
  6. import {pathToFileURL} from 'node:url'
  7. import {createServer} from 'node:http'
  8. import {randomUUID} from 'node:crypto'
  9. import {_electron} from '@playwright/test'
  10. test('real isolated Chromium supports refs, tabs, screenshots and separate persisted profiles',{timeout:60000},async()=>{
  11. const home=await mkdtemp(join(tmpdir(),'yaoyao-browser-')),marker=randomUUID(),server=createServer((req,res)=>{res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<title>Browser ${marker}</title><body style="background:#edf4ff;font:24px sans-serif;padding:50px"><h1>独立浏览器验证</h1><label>随机内容 <input aria-label="验证输入"></label><button onclick="document.querySelector('output').textContent=document.querySelector('input').value">确认</button><output></output><p>${marker}</p><a href="/next">下一页</a></body>`)})
  12. await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve));const url=`http://127.0.0.1:${server.address().port}/`,moduleURL=pathToFileURL(resolve('desktop/browser-environment.mjs')).href
  13. await writeFile(join(home,'main.mjs'),`import {app,BrowserWindow} from 'electron';app.setPath('userData',${JSON.stringify(join(home,'app'))});app.whenReady().then(async()=>{const hostWindow=new BrowserWindow({show:false});await hostWindow.loadURL('about:blank');const {BrowserEnvironment}=await import(${JSON.stringify(moduleURL)});globalThis.botBrowser=new BrowserEnvironment(${JSON.stringify(join(home,'profiles'))});app.on('window-all-closed',()=>{});});`)
  14. let app
  15. try{
  16. app=await _electron.launch({args:[join(home,'main.mjs')],env:Object.fromEntries(Object.entries(process.env).filter(([key])=>key!=='ELECTRON_RUN_AS_NODE'))})
  17. await app.evaluate(async()=>{for(let i=0;i<100&&!globalThis.botBrowser;i++)await new Promise(resolve=>setTimeout(resolve,20));if(!globalThis.botBrowser)throw new Error('browser host did not initialize')})
  18. const call=async(method,...args)=>app.evaluate(async(_,{method,args})=>globalThis.botBrowser[method](...args),{method,args})
  19. const a='a'.repeat(64),b='b'.repeat(64),t='c'.repeat(64)
  20. assert.equal((await call('state',a)).open,false)
  21. await call('action',a,'persistent',{kind:'navigate',url})
  22. const snapshot=await call('action',a,'persistent',{kind:'snapshot'})
  23. assert.ok(snapshot.text.includes(marker));const input=snapshot.elements.find(e=>e.label==='验证输入');assert.ok(input)
  24. await call('action',a,'persistent',{kind:'fill',ref:input.ref,snapshotId:snapshot.snapshotId,text:marker})
  25. await assert.rejects(call('action',a,'persistent',{kind:'click',ref:input.ref,snapshotId:snapshot.snapshotId}),/变化/)
  26. const next=await call('action',a,'persistent',{kind:'snapshot'}),button=next.elements.find(e=>e.label==='确认')
  27. await call('action',a,'persistent',{kind:'click',ref:button.ref,snapshotId:next.snapshotId})
  28. assert.equal(await app.evaluate(()=>globalThis.botBrowser.current(globalThis.botBrowser.browsers.values().next().value).webContents.executeJavaScript('document.querySelector("output").textContent')),marker)
  29. const frame=await call('view',a);assert.ok(frame.width>=1000);assert.ok(Buffer.from(frame.data,'base64').length>10000)
  30. await mkdir(resolve('test-results/desktop'),{recursive:true});await writeFile(resolve('test-results/desktop/bot-browser.png'),Buffer.from(frame.data,'base64'))
  31. await call('action',a,'persistent',{kind:'new-tab',url:url+'next'});const tabs=(await call('state',a)).tabs;assert.equal(tabs.length,2)
  32. await call('action',a,'persistent',{kind:'close-tab',tabId:tabs.find(t=>t.active).id});assert.equal((await call('state',a)).tabs.length,1)
  33. await assert.rejects(call('action',a,'persistent',{kind:'navigate',url:'file:///etc/passwd'}),/http/)
  34. await app.evaluate(async(_,{a,url})=>globalThis.botBrowser.browsers.get(a).session.cookies.set({url,name:'owner',value:'a',expirationDate:Date.now()/1000+3600}),{a,url})
  35. await call('action',b,'persistent',{kind:'navigate',url});assert.deepEqual(await app.evaluate(async(_,b)=>globalThis.botBrowser.browsers.get(b).session.cookies.get({name:'owner'}),b),[])
  36. await call('action',t,'temporary',{kind:'navigate',url});await app.evaluate(async(_,{t,url})=>globalThis.botBrowser.browsers.get(t).session.cookies.set({url,name:'temporary',value:'yes',expirationDate:Date.now()/1000+3600}),{t,url})
  37. await call('close');await app.close();app=await _electron.launch({args:[join(home,'main.mjs')],env:Object.fromEntries(Object.entries(process.env).filter(([key])=>key!=='ELECTRON_RUN_AS_NODE'))});await app.evaluate(async()=>{for(let i=0;i<100&&!globalThis.botBrowser;i++)await new Promise(resolve=>setTimeout(resolve,20))});await call('open',a,'persistent');assert.equal((await app.evaluate(async(_,a)=>globalThis.botBrowser.browsers.get(a).session.cookies.get({name:'owner'}),a))[0].value,'a')
  38. await call('open',t,'temporary');assert.deepEqual(await app.evaluate(async(_,t)=>globalThis.botBrowser.browsers.get(t).session.cookies.get({name:'temporary'}),t),[])
  39. }finally{await app?.close();await new Promise(resolve=>server.close(resolve));await rm(home,{recursive:true,force:true})}
  40. })