playwright-cli/examples/run_cli_fixture.py
Version b85c7a736bb4.bb1 · Apache-2.0. This preview displays packaged text and does not execute code. Treat the contents as untrusted instructions.
← Return to resource and package checksum
"""Native CLI fixture; no AI client is executed. Apache-2.0, BB Skills 2026."""
import argparse
import datetime as dt
import hashlib
import json
import pathlib
import platform
import re
import subprocess
from urllib.parse import urlsplit
parser = argparse.ArgumentParser()
parser.add_argument('--node', default='node')
parser.add_argument('--cli-script', required=True)
parser.add_argument('--session', required=True, help='An already open, authorized browser session')
parser.add_argument('--output-dir', default='output/playwright/fixture-v46')
parser.add_argument('--base-url', default='http://127.0.0.1:8767')
args = parser.parse_args()
assert urlsplit(args.base_url).hostname in {'127.0.0.1', 'localhost'}, 'Fixture must be local'
output = pathlib.Path(args.output_dir)
output.mkdir(parents=True, exist_ok=True)
cases = []
owned = []
route_installed = False
prefix = [args.node, args.cli_script, '-s=' + args.session]
def invoke(*command, expected_error=False):
result = subprocess.run(prefix + list(command), capture_output=True, encoding='utf-8', errors='replace', timeout=35)
error = '### Error' in result.stdout or result.returncode != 0
if expected_error:
assert error, 'Expected CLI error was not observed'
else:
assert not error, 'Native CLI failed: ' + command[0]
# Never persist complete output: tab lists may include private user pages.
return result.stdout, result.returncode
def result_value(stdout):
match = re.search(r'### Result\r?\n', stdout)
assert match, 'Missing native CLI result'
return json.JSONDecoder().raw_decode(stdout[match.end():].lstrip())[0]
def evaluate(expression):
return result_value(invoke('eval', expression)[0])
def tabs():
text = invoke('tab-list')[0]
return {int(index):url for index,url in re.findall(r'^- (\d+):(?: \(current\))? \[.*?\]\((.*?)\)$', text, re.M)}
def observe(name, **details):
cases.append({'name':name, 'status':'passed', 'observations':details})
def snapshot(name):
path = output / name
invoke('snapshot', '--filename=' + str(path))
return path.read_text(encoding='utf-8')
def reference(text, label):
matches = re.findall(r'(?:textbox|combobox|checkbox|button) "' + re.escape(label) + r'" \[ref=([^\]]+)\]', text)
assert len(matches) == 1, 'Ambiguous fixture reference: ' + label
return matches[0]
before = tabs()
assert before, 'An authorized existing browser session is required; no fallback is opened'
cli_version = subprocess.check_output([args.node,args.cli_script,'--version'], encoding='utf-8').strip()
node_version = subprocess.check_output([args.node,'--version'], encoding='utf-8').strip()
try:
invoke('tab-new', args.base_url + '/index.html')
after = tabs()
added = set(after) - set(before)
assert len(added) == 1
fixture_tab = added.pop()
owned.append(fixture_tab)
text = snapshot('initial.yml')
assert 'Browser command fixture' in text
initial_motion = evaluate("({reduced:matchMedia('(prefers-reduced-motion: reduce)').matches, animation:getComputedStyle(document.querySelector('.dot')).animationName})")
browser_agent = evaluate('navigator.userAgent')
observe('snapshot_accessible_refs', display_name_ref_found=bool(reference(text,'Display name')), checkbox_ref_found=bool(reference(text,'Use synthetic data only')))
invoke('fill',reference(text,'Display name'),'Fixture Analyst')
assert evaluate("document.getElementById('name').value") == 'Fixture Analyst'
observe('fill_synthetic_text', value='Fixture Analyst')
invoke('select',reference(text,'Review role'),'Reviewer')
assert evaluate("document.getElementById('role').value") == 'Reviewer'
observe('select_native_option', value='Reviewer')
invoke('check',reference(text,'Use synthetic data only'))
assert evaluate("document.getElementById('scope').checked") is True
observe('check_synthetic_scope', checked=True)
invoke('click',reference(text,'Save local example'))
text = snapshot('saved.yml')
assert 'Saved locally for Fixture Analyst (Reviewer).' in text
observe('submit_local_form_status', status='Saved locally for Fixture Analyst (Reviewer).', external_submission=False)
focus_script = output / 'focus.js'
focus_script.write_text("async page => { await page.getByRole('textbox',{name:'Display name',exact:true}).focus(); return {focused:true}; }\n",encoding='utf-8')
invoke('run-code','--filename=' + str(focus_script))
invoke('press','Tab')
assert evaluate('document.activeElement.id') == 'role'
observe('keyboard_tab_focus', next_element='role')
mocked = {'ideas':['synthetic mocked idea']}
invoke('route',args.base_url + '/api/ideas','--body=' + json.dumps(mocked))
route_installed = True
text = snapshot('before-request.yml')
invoke('click',reference(text,'Load ideas'))
wait_script = output / 'wait-mock.js'
wait_script.write_text("async page => { await page.locator('#ideas-result').filter({hasText:'synthetic mocked idea'}).waitFor(); return JSON.parse(await page.locator('#ideas-result').innerText()); }\n",encoding='utf-8')
assert result_value(invoke('run-code','--filename=' + str(wait_script))[0]) == mocked
observe('route_local_json_response', mocked=mocked, third_party_network=False)
invoke('unroute',args.base_url + '/api/ideas')
route_installed = False
invoke('click',reference(text,'Load ideas'))
default_script = output / 'wait-default.js'
default_script.write_text("async page => { await page.locator('#ideas-result').filter({hasText:'local default'}).waitFor(); return JSON.parse(await page.locator('#ideas-result').innerText()); }\n",encoding='utf-8')
assert result_value(invoke('run-code','--filename=' + str(default_script))[0]) == {'ideas':['local default']}
observe('unroute_restores_local_server', actual={'ideas':['local default']})
invoke('set-reduced-motion','reduce')
assert evaluate("getComputedStyle(document.querySelector('.dot')).animationName") == 'none'
observe('reduced_motion_css', animation='none')
invoke('clear-reduced-motion')
restored_motion = evaluate("({reduced:matchMedia('(prefers-reduced-motion: reduce)').matches, animation:getComputedStyle(document.querySelector('.dot')).animationName})")
assert restored_motion == initial_motion
observe('media_override_restored', baseline_restored=True)
invoke('resize','390','844')
dimensions = evaluate('({viewport:innerWidth,document:document.documentElement.scrollWidth})')
assert dimensions['viewport'] == 390 and dimensions['document'] <= dimensions['viewport']
observe('mobile_fixture_no_horizontal_overflow', **dimensions)
invoke('screenshot','--filename=' + str(output / 'mobile.png'))
assert (output / 'mobile.png').stat().st_size > 1000
observe('fixture_screenshot_created', bytes=(output / 'mobile.png').stat().st_size)
invoke('click','e999999',expected_error=True)
observe('missing_reference_error_detected', expected_rejection=True)
invoke('tab-new',args.base_url + '/other.html')
latest = tabs()
second = set(latest) - set(after)
assert len(second) == 1
second_tab = second.pop()
owned.append(second_tab)
assert evaluate('document.title') == 'Second local fixture page'
invoke('tab-close',str(second_tab))
owned.remove(second_tab)
invoke('tab-select',str(fixture_tab))
assert evaluate('document.title') == 'BB Skills browser command fixture'
observe('second_fixture_tab_closed_individually', fixture_restored=True)
finally:
# Cleanup only the tabs and exact route created by this runner.
if route_installed:
invoke('tab-select',str(fixture_tab))
invoke('unroute',args.base_url + '/api/ideas')
if owned:
invoke('tab-select',str(fixture_tab))
invoke('clear-reduced-motion')
invoke('resize','1440','900')
for index in sorted(owned, reverse=True):
invoke('tab-close',str(index))
after_cleanup = tabs()
assert before == after_cleanup, 'Existing user tabs changed'
observe('existing_user_tabs_preserved', existing_tabs=len(before), remaining_tabs=len(after_cleanup))
root = pathlib.Path(__file__).parent
source_hashes = {name:hashlib.sha256((root / name).read_bytes()).hexdigest() for name in ['index.html','other.html','serve_fixture.py','run_cli_fixture.py']}
evidence = {
'executed_at':dt.datetime.now(dt.timezone.utc).isoformat(),
'native_browser_command_fixture_tested':True,
'ai_client_executed':False,
'runtime_tested':False,
'production_tested':False,
'live_account_login_tested':False,
'cookies_exported':False,
'profile_credentials_read':False,
'browser_alternative_opened':False,
'profile_mode':'Existing user-authorized persistent Chrome profile; no launch or alternate profile',
'environment':{'cli':cli_version,'node':node_version,'python':platform.python_version(),'platform':platform.system(),'browser_user_agent':browser_agent},
'fixture':{'loopback_only':True,'external_service':False,'synthetic_inputs':True,'third_party_requests_mocked':False},
'source_hashes':source_hashes,
'observations':len(cases),'cases':cases,
'existing_user_tabs_preserved':True,
'artifacts_contain_private_tab_urls':False,
'cleanup_completed':True,
}
(root / 'native-browser-evidence.json').write_text(json.dumps(evidence,indent=2),encoding='utf-8',newline='\n')
print(json.dumps({'observations':len(cases),'cli':cli_version,'native_fixture':True,'full_skill_runtime':False,'existing_tabs_preserved':True,'evidence':str(root / 'native-browser-evidence.json')}))