78 lines
4.4 KiB
Python
78 lines
4.4 KiB
Python
"""Real Docker contract: shared desktop profile, reconnect, restart, transfer paths.
|
|
Build grokboy-box:surface-test first; uses an isolated container and temporary data.
|
|
"""
|
|
import json, subprocess, time, uuid, select
|
|
NAME = 'grokboy-surface-test-' + uuid.uuid4().hex[:10]
|
|
IMAGE = 'grokboy-box:surface-test'
|
|
|
|
def docker(*args, **kw):
|
|
return subprocess.run(['docker', *args], check=True, capture_output=True, text=True, **kw).stdout.strip()
|
|
|
|
def ready():
|
|
for _ in range(120):
|
|
try:
|
|
docker('exec', NAME, 'python3', '-c', 'import urllib.request; urllib.request.urlopen("http://127.0.0.1:9222/json/version",timeout=1)')
|
|
return
|
|
except subprocess.CalledProcessError:
|
|
time.sleep(.5)
|
|
raise AssertionError('Chromium did not become ready')
|
|
|
|
def fixture():
|
|
docker('exec', NAME, 'sh', '-c', 'mkdir -p /workspace/fixture; printf "<html><body>fixture<input type=file id=upload><a id=download href=download.bin download>download</a></body></html>" > /workspace/fixture/index.html; printf fixture-bytes > /workspace/fixture/download.bin; printf upload-fixture > /workspace/upload.txt')
|
|
docker('exec', '-d', NAME, 'python3', '-m', 'http.server', '8765', '--directory', '/workspace/fixture')
|
|
|
|
def helper():
|
|
return subprocess.Popen(['docker','exec','-i','-w','/workspace','-e','GROKBOY_BROWSER_CDP=http://127.0.0.1:9222',NAME,'flock','-n','/home/box/.grokboy-browser.lock','node','/opt/grokboy/playwright/browser_helper.mjs'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
|
|
def request(p, op, **args):
|
|
p.stdin.write(json.dumps({'id':'fixture', 'op':op, **args})+'\n'); p.stdin.flush()
|
|
assert select.select([p.stdout], [], [], 30)[0], f'helper timed out: {op}'
|
|
line=p.stdout.readline(); assert line, p.stderr.read()
|
|
value=json.loads(line); assert value.get('ok'), value
|
|
return value
|
|
|
|
def close(p):
|
|
request(p,'close'); p.stdin.close(); p.wait(timeout=10)
|
|
|
|
try:
|
|
docker('run','-d','--name',NAME,'--shm-size=1g',IMAGE)
|
|
ready(); fixture()
|
|
p=helper()
|
|
request(p,'navigate',url='http://127.0.0.1:8765')
|
|
request(p,'eval',expression='document.cookie="login=fixture; max-age=3600; path=/"; localStorage.setItem("account","fixture"); sessionStorage.setItem("tab","same"); true')
|
|
request(p,'upload',selector='#upload',path='/workspace/upload.txt')
|
|
assert request(p,'eval',expression='document.querySelector("#upload").files[0].name')['result']=='upload.txt'
|
|
request(p,'download',selector='#download',path='/workspace/saved.bin')
|
|
assert docker('exec',NAME,'cat','/workspace/saved.bin')=='fixture-bytes'
|
|
print('PASS upload/download use Docker workspace', flush=True)
|
|
info=request(p,'handoff_prepare'); assert info['headed'] and not info['relaunched'], info
|
|
close(p)
|
|
ready() # releasing the tool must not close the desktop browser
|
|
p=helper()
|
|
result=request(p,'eval',expression='[document.cookie,localStorage.getItem("account"),sessionStorage.getItem("tab")]')['result']
|
|
assert result==['login=fixture','fixture','same'], result
|
|
invalid=request(p,'status'); assert invalid['headed']
|
|
other=helper()
|
|
other.communicate(json.dumps({'id':'busy','op':'ping'})+'\n',timeout=5)
|
|
assert other.returncode != 0, 'second helper stole the browser lease'
|
|
p.kill(); p.wait(timeout=5)
|
|
p.stdin.close(); p.stdout.close(); p.stderr.close()
|
|
for _ in range(30):
|
|
check=subprocess.run(['docker','exec',NAME,'flock','-n','/home/box/.grokboy-browser.lock','true'],capture_output=True)
|
|
if check.returncode == 0: break
|
|
time.sleep(.1)
|
|
assert check.returncode == 0, 'cancelled helper leaked its browser lease'
|
|
p=helper()
|
|
assert request(p,'eval',expression='localStorage.getItem("account")')['result']=='fixture'
|
|
close(p)
|
|
print('PASS competing helpers excluded; cancellation releases the lease', flush=True)
|
|
print('PASS handoff and helper reconnect retain same browser, cookie, localStorage, sessionStorage', flush=True)
|
|
docker('restart', NAME); ready(); fixture()
|
|
p=helper(); request(p,'navigate',url='http://127.0.0.1:8765')
|
|
result=request(p,'eval',expression='[document.cookie,localStorage.getItem("account")]')['result']
|
|
assert result==['login=fixture','fixture'], result
|
|
close(p)
|
|
print('PASS Docker restart retains Chromium login profile', flush=True)
|
|
finally:
|
|
subprocess.run(['docker','rm','-f',NAME],capture_output=True)
|