browserDragSmoke.test.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. // @vitest-environment node
  2. import {expect,it} from 'vitest'
  3. import {randomUUID} from 'node:crypto'
  4. import {mkdtemp,realpath,rm} from 'node:fs/promises'
  5. import {tmpdir} from 'node:os'
  6. import {join} from 'node:path'
  7. import {chromium,type Page} from 'playwright'
  8. import {BrowserRuntime,type BrowserScope,type BrowserAction} from '../../src/runner/browser/runtime.js'
  9. // Real Chromium, isolated profile and synthetic page. No external website or account.
  10. it.runIf(process.env.YAOYAO_BROWSER_SMOKE==='1')('real Chromium: sliders, text selection, HTML drop and live holds with cancellation',async()=>{
  11. const root=await realpath(await mkdtemp(join(tmpdir(),'yaoyao-browser-drag-')))
  12. const scope:BrowserScope={ownerKey:'drag-test',environmentId:'drag-bot',profile:'temporary'}
  13. let page!:Page
  14. const runtime=new BrowserRuntime({root,enabled:true,launcher:{launchPersistentContext:async(directory,options)=>{
  15. const context=await chromium.launchPersistentContext(directory,options);page=context.pages()[0]!
  16. await page.setContent(`<!doctype html><style>
  17. body{margin:40px;font:20px monospace}input{width:400px;height:30px;margin:0}p{width:700px;margin:30px 0}
  18. #source,#target{display:inline-block;width:180px;height:100px;background:#ddd;margin-right:80px}
  19. </style><input type="range" min="0" max="100" value="0"><p>Drag this sentence to select the text.</p>
  20. <div id="source" draggable="true">Drag me</div><div id="target">Drop here</div><output id="drops">0</output>
  21. <script>
  22. window.buttons=0;window.releases=0;
  23. document.addEventListener('mousemove',e=>window.buttons=e.buttons);
  24. document.addEventListener('mouseup',()=>window.releases++);
  25. source.addEventListener('dragstart',e=>e.dataTransfer.setData('text/plain','fixture'));
  26. target.addEventListener('dragover',e=>e.preventDefault());
  27. target.addEventListener('drop',e=>{e.preventDefault();if(e.dataTransfer.getData('text/plain')==='fixture')drops.textContent=String(+drops.textContent+1)});
  28. </script>`)
  29. return context
  30. }}})
  31. let generation=0
  32. const execute=(action:BrowserAction)=>runtime.execute(scope,{generation,operationId:randomUUID(),action})
  33. try{
  34. generation=(await runtime.open(scope)).generation
  35. const slider=(await page.locator('input').boundingBox())!,y=slider.y+slider.height/2
  36. await execute({kind:'drag',fromX:slider.x+8,fromY:y,toX:slider.x+390,toY:y})
  37. expect(Number(await page.locator('input').inputValue())).toBeGreaterThan(95)
  38. const text=(await page.locator('p').boundingBox())!
  39. await execute({kind:'drag',fromX:text.x+1,fromY:text.y+10,toX:text.x+240,toY:text.y+10})
  40. expect(await page.evaluate(()=>getSelection()?.toString())).toContain('Drag this sentence')
  41. const source=(await page.locator('#source').boundingBox())!,target=(await page.locator('#target').boundingBox())!
  42. const start={x:source.x+40,y:source.y+40},end={x:target.x+40,y:target.y+40}
  43. await execute({kind:'drag',fromX:start.x,fromY:start.y,toX:end.x,toY:end.y})
  44. expect(await page.locator('#drops').innerText()).toBe('1')
  45. const gestureId=randomUUID()
  46. await execute({kind:'pointer',gestureId,phase:'start',x:slider.x+390,y})
  47. const releases=await page.evaluate(()=>(window as any).releases)
  48. await execute({kind:'pointer',gestureId,phase:'move',x:slider.x+200,y})
  49. // This observation happens while the button is still held, before pointer end.
  50. expect(Number(await page.locator('input').inputValue())).toBeGreaterThan(45)
  51. expect(Number(await page.locator('input').inputValue())).toBeLessThan(55)
  52. expect(await page.evaluate(()=>(window as any).buttons)).toBe(1)
  53. expect(await page.evaluate(()=>(window as any).releases)).toBe(releases)
  54. await execute({kind:'screenshot'})
  55. await execute({kind:'pointer',gestureId,phase:'end',x:slider.x+300,y})
  56. expect(Number(await page.locator('input').inputValue())).toBeGreaterThan(70)
  57. expect(await page.evaluate(()=>(window as any).releases)).toBe(releases+1)
  58. const dropId=randomUUID()
  59. await execute({kind:'pointer',gestureId:dropId,phase:'start',...start})
  60. await execute({kind:'pointer',gestureId:dropId,phase:'move',...end})
  61. await execute({kind:'pointer',gestureId:dropId,phase:'end',...end})
  62. expect(await page.locator('#drops').innerText()).toBe('2')
  63. const cancelId=randomUUID()
  64. await execute({kind:'pointer',gestureId:cancelId,phase:'start',...start})
  65. await execute({kind:'pointer',gestureId:cancelId,phase:'move',...end})
  66. await execute({kind:'pointer',gestureId:cancelId,phase:'cancel',...end})
  67. expect(await page.locator('#drops').innerText()).toBe('2')
  68. await page.mouse.move(100,700)
  69. expect(await page.evaluate(()=>(window as any).buttons)).toBe(0)
  70. }finally{await runtime.shutdown();await rm(root,{recursive:true,force:true})}
  71. },30000)