25592fa6c2
ci / test (push) Successful in 6m14s
History-free snapshot of the private development repository at 77a82eb. Code, docs and the public catalogue only: no plant or job knowledge layers, no client, job or producer names (tools/scrub.py clean against the widened term list; a second word-level grep clean). The intro deck is left out because its slide images cannot be checked for job drawings.
91 lines
3.6 KiB
Python
Executable File
91 lines
3.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Render a WillTools web page headless and screenshot every tab -- the
|
|
browser dry-run this machine could not do until 2026-09-07 (owner: "浏览器你
|
|
能不能在debian上面自己渲染一个?").
|
|
|
|
python tools/webshot.py pa --reads _scratch/pa_reads_x.json --out /tmp/shots
|
|
python tools/webshot.py ba --reads beamcalc/fixtures/rba5_16x22.json --out /tmp/shots
|
|
|
|
Starts tools/pa_web.py or tools/ba_web.py on a free port, drives Chrome
|
|
headless (google-chrome / chromium on PATH) to /?tab=N&expand=all for each
|
|
tab, writes <out>/<tool>-tab<N>.png plus <out>/<tool>-console.txt (the page's
|
|
console errors, which is the half a screenshot cannot show), then closes the
|
|
server through POST /api/close. Exit 1 if any tab logged a console error.
|
|
CPython 3 only; no Revit anywhere.
|
|
"""
|
|
import argparse, json, os, shutil, subprocess, sys, time, urllib.request
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
|
|
def chrome():
|
|
for c in ('google-chrome', 'chromium', 'chromium-browser', 'chrome'):
|
|
p = shutil.which(c)
|
|
if p:
|
|
return p
|
|
sys.exit('no chrome/chromium on PATH')
|
|
|
|
|
|
def start_server(tool, reads):
|
|
args = [sys.executable, os.path.join(ROOT, 'tools', '%s_web.py' % tool), '--port', '0']
|
|
if reads:
|
|
args += ['--reads', reads]
|
|
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=ROOT)
|
|
url = None
|
|
for _ in range(200):
|
|
line = p.stdout.readline()
|
|
if not line:
|
|
break
|
|
if '_WEB_URL=' in line:
|
|
url = line.split('=', 1)[1].strip()
|
|
break
|
|
if not url:
|
|
err = p.stderr.read()
|
|
sys.exit('server did not print its URL\n' + err)
|
|
return p, url
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument('tool', choices=['pa', 'ba'])
|
|
ap.add_argument('--reads')
|
|
ap.add_argument('--out', default='/tmp/webshot')
|
|
ap.add_argument('--tabs', type=int, default=5)
|
|
ap.add_argument('--width', type=int, default=1400)
|
|
ap.add_argument('--height', type=int, default=2200)
|
|
a = ap.parse_args()
|
|
os.makedirs(a.out, exist_ok=True)
|
|
p, url = start_server(a.tool, a.reads)
|
|
exe = chrome()
|
|
errors = []
|
|
try:
|
|
spec = json.load(urllib.request.urlopen(url + 'api/spec', timeout=30))
|
|
tabs = len(spec.get('pages', [])) or a.tabs
|
|
for n in range(1, tabs + 1):
|
|
png = os.path.join(a.out, '%s-tab%d.png' % (a.tool, n))
|
|
log = os.path.join(a.out, '%s-tab%d.console.txt' % (a.tool, n))
|
|
cmd = [exe, '--headless=new', '--no-sandbox', '--disable-gpu', '--hide-scrollbars',
|
|
'--window-size=%d,%d' % (a.width, a.height), '--virtual-time-budget=4000',
|
|
'--enable-logging=stderr', '--v=0',
|
|
'--screenshot=' + png, '%s?tab=%d&expand=all' % (url, n)]
|
|
r = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
console = [l for l in r.stderr.splitlines() if 'CONSOLE' in l]
|
|
with open(log, 'w') as fh:
|
|
fh.write('\n'.join(console))
|
|
bad = [l for l in console if 'error' in l.lower() or 'uncaught' in l.lower()]
|
|
errors += bad
|
|
print('tab %d -> %s (%d console lines, %d errors)' % (n, png, len(console), len(bad)))
|
|
finally:
|
|
try:
|
|
urllib.request.urlopen(urllib.request.Request(url + 'api/close', method='POST', data=b'{}'), timeout=5)
|
|
except Exception:
|
|
pass
|
|
p.terminate()
|
|
if errors:
|
|
print('CONSOLE ERRORS:\n' + '\n'.join(errors[:20]))
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|