LazyBoy2/tests/browser_flow.py

137 lines
8.0 KiB
Python
Raw Normal View History

2026-09-13 16:38:32 +00:00
"""Real headless Chromium against local fixtures; no external websites or API keys."""
import json
import os
from pathlib import Path
import queue
import subprocess
import tempfile
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
HELPER = Path(__file__).resolve().parents[1] / 'tools/playwright/browser_helper.mjs'
HTML = '''<!doctype html><title>Fixture</title><body style="min-height:2500px">
<p id="article">Research evidence: this paragraph is readable independently of the control snapshot.</p>
<input id="text"><input type="password" value="must-not-appear"><span id="result"></span>
<select id="choice"><option value="a">Alpha</option><option value="b">Beta</option></select>
<button id="later" onclick="setTimeout(()=>{document.querySelector('#result').textContent='ready'},200)">Later</button>
<a id="popup" target="_blank" href="/popup">Open popup</a><iframe id="child" src="/frame"></iframe>
<input id="file" type="file"><a id="download" download="payload.txt" href="/payload">Download</a>
<script>document.querySelector('#text').onkeydown=e=>{if(e.key==='Enter')document.querySelector('#result').textContent='entered'};</script>
</body>'''
class Site(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/payload':
body, kind = b'fixture download\n', 'application/octet-stream'
elif self.path == '/frame':
body, kind = b'<body><p>Frame evidence</p><input id="inside"></body>', 'text/html'
elif self.path == '/popup':
body, kind = b'<title>Popup</title><body>Popup evidence</body>', 'text/html'
else:
body, kind = HTML.encode(), 'text/html'
self.send_response(200)
self.send_header('Content-Type', kind)
self.send_header('Content-Length', str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *_): pass
class Helper:
def __init__(self, root):
self.queue = queue.Queue()
self.process = subprocess.Popen(['node', str(HELPER)], cwd=root, env={**os.environ,
'GROKBOY_BROWSER_HEADED':'0', 'GROKBOY_BROWSER_PROFILE':str(Path(root)/'profile')},
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
threading.Thread(target=lambda:[self.queue.put(line) for line in self.process.stdout], daemon=True).start()
self.errors=[]
threading.Thread(target=lambda:[self.errors.append(line) for line in self.process.stderr], daemon=True).start()
self.next=0
def call(self, op, **args):
self.next+=1
self.process.stdin.write(json.dumps({'id':str(self.next),'op':op,**args})+'\n')
self.process.stdin.flush()
try: result=json.loads(self.queue.get(timeout=40))
except queue.Empty: raise AssertionError(('helper timeout', self.errors))
assert result['id']==str(self.next), result
assert result.get('ok'), result
return result
def close(self):
if self.process.poll() is None:
try: self.call('close')
finally:
self.process.stdin.close()
self.process.wait(timeout=10)
def main():
with tempfile.TemporaryDirectory(prefix='grokboy-browser-') as root, ThreadingHTTPServer(('127.0.0.1',0), Site) as site:
threading.Thread(target=site.serve_forever,daemon=True).start()
helper=Helper(root)
try:
url=f'http://127.0.0.1:{site.server_port}/'
helper.call('navigate',url=url)
snap=helper.call('snapshot')
assert 'must-not-appear' not in json.dumps(snap)
assert snap['frames'][0]['selector']=='#child',snap
page=helper.call('read_page',limit=3000)
assert 'Research evidence' in page['text'] and page['links']
assert helper.call('read_page',offset=0,limit=10)['truncated']
helper.call('type',selector='#text',text='hello')
helper.call('press',selector='#text',key='Enter')
assert helper.call('eval',expression="document.querySelector('#result').textContent")['result']=='entered'
helper.call('select',selector='#choice',values=['b'])
assert helper.call('eval',expression="document.querySelector('#choice').value")['result']=='b'
helper.call('click',selector='#later')
helper.call('wait',text='ready',timeout_ms=2000)
print('PASS body, links, segmentation, password omission, keyboard, select, dynamic wait')
helper.call('type',frame='#child',selector='#inside',text='in frame')
assert helper.call('eval',frame='#child',expression="document.querySelector('#inside').value")['result']=='in frame'
helper.call('click',selector='#popup')
tabs=helper.call('tabs')['tabs']
popup=next(t for t in tabs if t['url'].endswith('/popup'))
helper.call('tabs',action='switch',tab_id=popup['tab_id'])
assert 'Popup evidence' in helper.call('read_page')['text']
helper.call('tabs',action='close',tab_id=popup['tab_id'])
helper.call('scroll',delta_y=700)
assert helper.call('eval',expression='window.scrollY')['result']>0
print('PASS iframe, popup, tab switching/closing, scrolling')
Path(root,'upload.txt').write_text('upload content')
helper.call('upload',selector='#file',path='upload.txt')
assert helper.call('eval',expression="document.querySelector('#file').files[0].name")['result']=='upload.txt'
helper.call('download',selector='#download',path='downloads/file.txt')
assert Path(root,'downloads/file.txt').read_text()=='fixture download\n'
print('PASS workspace upload and actual download')
helper.call('eval',expression="localStorage.setItem('proof','saved'); document.cookie='login=yes; path=/'")
helper.close()
helper=Helper(root)
helper.call('navigate',url=url)
assert helper.call('eval',expression="localStorage.getItem('proof')")['result']=='saved'
assert 'login=yes' in helper.call('eval',expression='document.cookie')['result']
print('PASS persistent profile after helper restart')
if '--handoff' in __import__('sys').argv:
helper.call('eval',expression="sessionStorage.setItem('oauth_state','keep-me')")
original=helper.call('tabs')['tabs'][0]['tab_id']
helper.call('tabs',action='new',url=url+'popup')
helper.call('tabs',action='switch',tab_id=original)
assert helper.call('eval',expression="sessionStorage.getItem('oauth_state')")['result']=='keep-me'
prepared=helper.call('handoff_prepare')
assert prepared['headed'] and prepared['relaunched'],prepared
assert prepared['url']==url,prepared
state=helper.call('eval',expression="sessionStorage.getItem('oauth_state')")
assert state['result']=='keep-me',(state,prepared)
assert 'login=yes' in helper.call('eval',expression='document.cookie')['result']
assert helper.call('eval',expression="localStorage.getItem('proof')")['result']=='saved'
assert len(helper.call('tabs')['tabs'])>=2
assert helper.call('handoff_prepare')['relaunched'] is False
helper.call('eval',expression="sessionStorage.setItem('oauth_state','new-value')")
helper.call('navigate',url=url)
assert helper.call('eval',expression="sessionStorage.getItem('oauth_state')")['result']=='new-value'
helper.call('eval',expression="document.cookie='manual_login=done; path=/'")
assert 'manual_login=done' in helper.call('eval',expression='document.cookie')['result']
print('PASS visible handoff preserves active tab, cookies, localStorage, sessionStorage and subsequent browser operations')
finally:
helper.close()
site.shutdown()
if __name__=='__main__': main()