159 lines
8.2 KiB
Python
159 lines
8.2 KiB
Python
"""Foreground Chromium interaction checks, screenshots and downloadable recording."""
|
|
import functools
|
|
import http.server
|
|
import json
|
|
import threading
|
|
from pathlib import Path
|
|
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
EVIDENCE = ROOT / 'evidence'
|
|
|
|
|
|
class Quiet(http.server.SimpleHTTPRequestHandler):
|
|
def log_message(self, *_args):
|
|
pass
|
|
|
|
|
|
def main():
|
|
EVIDENCE.mkdir(exist_ok=True)
|
|
handler = functools.partial(Quiet, directory=str(ROOT / 'dist'))
|
|
server = http.server.ThreadingHTTPServer(('127.0.0.1', 0), handler)
|
|
thread = threading.Thread(target=server.serve_forever)
|
|
thread.start()
|
|
results = {}
|
|
try:
|
|
with sync_playwright() as p:
|
|
browser = p.chromium.launch()
|
|
url = f'http://127.0.0.1:{server.server_port}'
|
|
context = browser.new_context(viewport={'width': 1280, 'height': 800},
|
|
record_video_dir=str(EVIDENCE),
|
|
record_video_size={'width': 1280, 'height': 800})
|
|
page = context.new_page()
|
|
errors = []
|
|
page.on('pageerror', lambda error: errors.append(str(error)))
|
|
page.goto(url)
|
|
page.wait_for_function('!document.querySelector("#play").disabled')
|
|
assert page.locator('#source').evaluate('(v) => v.paused')
|
|
page.click('#play')
|
|
# Keep every layer visible for three full cycles before interaction checks.
|
|
duration = page.locator('#source').evaluate('(v) => v.duration')
|
|
page.evaluate("""() => {
|
|
window.observedLoops = []; window.lastVideoTime = 0;
|
|
const v = document.querySelector('#source');
|
|
v.addEventListener('timeupdate', () => {
|
|
if (v.currentTime < window.lastVideoTime) window.observedLoops.push(performance.now());
|
|
window.lastVideoTime = v.currentTime;
|
|
});
|
|
}""")
|
|
page.wait_for_timeout((duration * 3 + 0.8) * 1000)
|
|
observed = page.evaluate('window.observedLoops')
|
|
assert len(observed) >= 3, observed
|
|
results['continuous_three_cycles'] = {'duration': duration, 'wraps_ms': observed}
|
|
page.wait_for_timeout(800)
|
|
alpha = page.locator('#motion').evaluate('(c) => {const x=c.getContext("2d"); return [x.getImageData(0,0,1,1).data[3], x.getImageData(300,220,1,1).data[3]]}')
|
|
assert alpha[0] == 0 and alpha[1] > 240, alpha
|
|
results['vp9_alpha_decoded_by_chromium'] = alpha
|
|
page.wait_for_function('document.querySelector("#source").currentTime > 0.9 && document.querySelector("#source").currentTime < 1.4')
|
|
positions = page.evaluate('''() => {
|
|
const video=document.querySelector('#source'); const before=video.currentTime;
|
|
const source=video.currentSrc; document.querySelector('#lighting').click();
|
|
return {before, after:video.currentTime, sourcePreserved: source===video.currentSrc};
|
|
}''')
|
|
assert positions['before'] == positions['after'] and positions['sourcePreserved']
|
|
results['environment_toggle_no_seek'] = positions
|
|
page.wait_for_timeout(650)
|
|
assert page.locator('#source').evaluate('(v) => !v.paused')
|
|
page.click('#show-person')
|
|
assert page.locator('#motion').is_hidden() and page.locator('#person').is_hidden()
|
|
page.wait_for_timeout(600)
|
|
page.screenshot(path=str(EVIDENCE / 'environment-only.png'))
|
|
page.click('#show-person')
|
|
page.click('#show-occlusion')
|
|
assert page.locator('#occlusion').is_hidden()
|
|
page.click('#show-occlusion')
|
|
page.click('#show-shadow')
|
|
assert page.locator('#shadow').is_hidden()
|
|
page.click('#show-shadow')
|
|
page.click('#show-mask')
|
|
assert page.locator('#mask').is_visible() and page.locator('#environment').is_hidden()
|
|
page.wait_for_timeout(500)
|
|
page.click('#show-mask')
|
|
results['independent_layers'] = True
|
|
page.click('#closeup')
|
|
assert page.locator('#talk').is_visible()
|
|
assert page.locator('#source').evaluate('(v) => !v.paused')
|
|
page.wait_for_timeout(700)
|
|
page.screenshot(path=str(EVIDENCE / 'static-near.png'))
|
|
page.click('#closeup')
|
|
assert page.locator('#talk').is_hidden()
|
|
page.wait_for_timeout(6400) # Includes the forward and generated return.
|
|
results['near_is_static_player_continues'] = True
|
|
page.click('#play')
|
|
t = page.locator('#source').evaluate('(v) => v.currentTime')
|
|
page.wait_for_timeout(250)
|
|
assert page.locator('#source').evaluate('(v) => v.currentTime') == t
|
|
results['manual_pause'] = True
|
|
# Chromium lifecycle API freezes the page after making it hidden.
|
|
session = context.new_cdp_session(page)
|
|
session.send('Page.setWebLifecycleState', {'state': 'active'})
|
|
# Visibility handler unit probe. Browser screenshot/recording checks above are real.
|
|
page.click('#play')
|
|
page.evaluate('''() => {
|
|
Object.defineProperty(document, 'hidden', {configurable:true, get:()=>true});
|
|
document.dispatchEvent(new Event('visibilitychange'));
|
|
}''')
|
|
assert page.locator('#source').evaluate('(v) => v.paused')
|
|
page.evaluate('''() => {
|
|
delete document.hidden; document.dispatchEvent(new Event('visibilitychange'));
|
|
}''')
|
|
page.wait_for_function('!document.querySelector("#source").paused')
|
|
results['visibility_pause_resume_handler_synthetic_event'] = True
|
|
for width, height in [(1600, 900), (1280, 800), (390, 844)]:
|
|
page.set_viewport_size({'width': width, 'height': height})
|
|
page.wait_for_timeout(180)
|
|
bounds = page.evaluate('''() => {
|
|
const r=document.querySelector('#motion').getBoundingClientRect();
|
|
const s=document.querySelector('#stage').getBoundingClientRect();
|
|
return {faceX:r.x+305/552*r.width, faceY:r.y+135/552*r.height,
|
|
stage:{left:s.left,right:s.right,top:s.top,bottom:s.bottom},
|
|
scroll:document.documentElement.scrollWidth, width:innerWidth};
|
|
}''')
|
|
assert bounds['scroll'] <= width
|
|
assert bounds['stage']['left'] <= bounds['faceX'] <= bounds['stage']['right']
|
|
assert bounds['stage']['top'] <= bounds['faceY'] <= bounds['stage']['bottom']
|
|
page.screenshot(path=str(EVIDENCE / f'viewport-{width}x{height}.png'))
|
|
results[f'viewport_{width}x{height}'] = bounds
|
|
page.emulate_media(reduced_motion='reduce')
|
|
page.wait_for_timeout(100)
|
|
assert page.locator('#source').evaluate('(v) => v.paused')
|
|
assert page.locator('#person').is_visible() and page.locator('#motion').is_hidden()
|
|
results['reduced_motion_static_fallback'] = True
|
|
page.emulate_media(reduced_motion='no-preference')
|
|
assert page.locator('#source').evaluate('(v) => v.paused')
|
|
video_path = Path(page.video.path())
|
|
context.close()
|
|
video_path.rename(EVIDENCE / 'browser-continuous.webm')
|
|
missing = browser.new_page()
|
|
missing.route('**/reading-alpha.webm', lambda route: route.abort())
|
|
missing.goto(url)
|
|
missing.wait_for_function('!document.querySelector("#fallback").hidden')
|
|
assert missing.locator('#person').is_visible()
|
|
assert missing.locator('#play').is_disabled()
|
|
results['missing_motion_static_fallback'] = True
|
|
missing.close()
|
|
assert not errors, errors
|
|
results['page_errors'] = errors
|
|
browser.close()
|
|
finally:
|
|
server.shutdown()
|
|
thread.join()
|
|
server.server_close()
|
|
(EVIDENCE / 'browser-results.json').write_text(json.dumps(results, ensure_ascii=False, indent=2))
|
|
print(json.dumps(results, ensure_ascii=False, indent=2))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|