"""Native website-style Markdown/code proof using actual QMP input and pixels.
The guest exports fixed read-only DOM diagnostics. This harness never injects
source, changes guest DOM through a debugger, or substitutes a host screenshot.
"""
import hashlib
import json
from pathlib import Path
import re
import time
from urllib.parse import urlsplit
from PIL import Image, ImageChops
from NativeCSharpEvidence import normalized_color
ROOT = Path(__file__).resolve().parents[1]
DOCUMENT = re.compile(r'XFE_WEBSITE_DOCUMENT_PROOF_BEGIN\r?\n(.*?)\r?\nXFE_WEBSITE_DOCUMENT_PROOF_END', re.S)
WINDOW = re.compile(r'^XFE_WINDOW_STATE\|(\d+)\|Browser\|(-?\d+)\|(-?\d+)\|(\d+)\|(\d+)\|([01])\|([01])\|([01])$', re.M)
RENDER_FRAMES = re.compile(r'XFE_RENDER_STATS\|\d+\|Frames\|(\d+)\|')
def wait_document(text, start, predicate=lambda proof: True, timeout=90):
deadline = time.monotonic() + timeout
failure = 'No complete native website-document readback'
while time.monotonic() < deadline:
for match in reversed(list(DOCUMENT.finditer(text()[start:]))):
try:
proof = json.loads(match.group(1))
if predicate(proof):
verify_base(proof)
return proof
except (AssertionError, ValueError, KeyError, TypeError) as error:
failure = str(error)
assert all(marker not in text() for marker in ('CPU EXCEPTION:', '[FAILFAST]', '*** UNHANDLED EXCEPTION',
'XFE_RUNTIME_OOM|', 'XFE_RUNTIME_STACK_OOM|')), text()[-4000:]
time.sleep(.1)
raise AssertionError('Actual native website document did not stabilize: ' + failure)
def verify_base(proof):
assert proof['Kind'] == 'ActualWebsiteDocumentDomReadback' and proof['ReadOnly'] is True
assert proof['NoHostScreenshotSubstitution'] is True
assert urlsplit(proof['Address']).netloc == 'xfesystem.local'
assert proof['Article'] and proof['Article']['Bounds']['Width'] > 0
styles = {urlsplit(value).path for value in proof['Styles']}
scripts = {urlsplit(value).path for value in proof['Scripts']}
assert {'/css/docs/code-style.css', '/css/docs/markdown-style.css', '/css/docs/docs-layout.css', '/css/theme-variables.css'}.issubset(styles)
assert {'/js/docs/highlight.min.js', '/js/docs/csharp.js', '/js/docs/doc-utils.js', '/js/docs/code-block-tools.js'}.issubset(scripts)
assert proof['WebsiteHighlighterAvailable'] is True and proof['WebsiteCodeToolsAvailable'] is True
def verify_code(proof, language, source):
verify_base(proof)
code = next(block for block in proof['CodeBlocks'] if 'language-' + language in block['CodeClasses'].split())
assert code['CodeText'] == source, ('Actual native source preview differs from the editor model', source, code['CodeText'])
assert code['Tokens'] and any('hljs-keyword' in span['Classes'].split() for span in code['Tokens'])
assert len(code['Tokens']) >= 3
assert {button['Action'] for button in code['Buttons']} == {'lines', 'download', 'expand', 'copy'}
assert code['LineNumberCount'] == max(1, len(source.rstrip('\n').split('\n')))
assert code['CodeBounds']['Width'] > 0 and code['CodeBounds']['Height'] > 0
return code
def browser_geometry(text):
matches = list(WINDOW.finditer(text()))
assert matches, 'No native browser window geometry'
parts = matches[-1].groups()
window = {'Id': int(parts[0]), 'X': int(parts[1]), 'Y': int(parts[2]), 'Width': int(parts[3]), 'Height': int(parts[4])}
viewport = [window['X'] + 2, window['Y'] + 67, window['Width'] - 4, window['Height'] - 96]
return window, viewport
def paint_evidence(frame, proof, viewport, scale, require_heading=False):
vx, vy, width, height = viewport
visible = [0, 0, width, height]
counts = []
candidates = []
if proof.get('Heading'):
candidates.append(('Heading', proof['Heading']['Text'], proof['Heading']['Bounds'], proof['Heading']['Style']['Color']))
for block in proof.get('CodeBlocks', []):
candidates.extend(('CodeToken', span['Text'], span['Bounds'], span['Color']) for span in block['Tokens'] if span['Text'].strip())
for kind, source, bounds, css_color in candidates:
left = max(visible[0], bounds['X']); top = max(visible[1], bounds['Y'])
right = min(width, bounds['X'] + bounds['Width']); bottom = min(height, bounds['Y'] + bounds['Height'])
if right - left <= 1 or bottom - top <= 1:
continue
rgb = tuple(int(normalized_color(css_color)[index:index + 2], 16) for index in (0, 2, 4))
physical = [round((vx + left) * scale), round((vy + top) * scale), round((vx + right) * scale), round((vy + bottom) * scale)]
crop = frame.crop(physical).convert('RGB')
pixels = sum(all(abs(pixel[channel] - rgb[channel]) <= 12 for channel in range(3)) for pixel in crop.getdata())
counts.append({'Kind': kind, 'Text': source, 'ExpectedCssColor': normalized_color(css_color), 'PhysicalBounds': physical, 'MatchingPixels': pixels})
heading = any(item['Kind'] == 'Heading' and item['MatchingPixels'] >= 4 * scale * scale for item in counts)
token_count = sum(item['Kind'] == 'CodeToken' and item['MatchingPixels'] >= 2 * scale * scale for item in counts)
return {'Passed': (heading if require_heading else token_count >= 2), 'HeadingPainted': heading,
'VisibleTokenSpansWithActualInk': token_count, 'Samples': counts,
'Meaning': 'Observed framebuffer glyph ink in actual laid-out DOM rectangles; offscreen tokens do not count.'}
def required_token_evidence(measured, texts, scale):
"""Short non-overlapping anchors cannot pass from a stale shifted code row."""
threshold = 2 * scale * scale
samples = [next((item for item in measured['Samples'] if item['Kind'] == 'CodeToken' and item['Text'] == token),
{'Kind': 'CodeToken', 'Text': token, 'MatchingPixels': 0}) for token in texts]
return {'Passed': all(item['MatchingPixels'] >= threshold for item in samples),
'MinimumMatchingPixelsPerToken': threshold, 'Samples': samples}
def wait_document_presented_frame(text, start, proof, before_frames, evidence, timeout=60):
# The guest emits this readback before Render(), so observing DOM state is
# not presentation evidence. Bind the ack to the complete matching END.
document_end = None
for match in reversed(list(DOCUMENT.finditer(text()[start:]))):
try:
if json.loads(match.group(1)) == proof:
document_end = start + match.end()
break
except (ValueError, TypeError):
continue
assert document_end is not None, 'The changed document has no complete matching serial END'
evidence.update({'DocumentEndOffset': document_end, 'BeforeFrames': before_frames,
'DeadlineSeconds': timeout, 'Passed': False})
deadline = time.monotonic() + timeout
while True:
current = text()
reports = list(RENDER_FRAMES.finditer(current[document_end:]))
evidence['ObservedFramesAfterDocumentEnd'] = [int(report.group(1)) for report in reports]
if reports and int(reports[-1].group(1)) > before_frames:
evidence.update({'AfterFrames': int(reports[-1].group(1)), 'Passed': True})
return deadline
assert all(marker not in current for marker in ('CPU EXCEPTION:', '[FAILFAST]', '*** UNHANDLED EXCEPTION',
'XFE_RUNTIME_OOM|', 'XFE_RUNTIME_STACK_OOM|')), current[-4000:]
assert time.monotonic() < deadline, ('No actual completed native frame after changed document END', evidence)
time.sleep(.1)
def capture_document(screenshot, name, text, proof, scale, require_heading=False, timeout=60,
required_tokens=(), different_from=None, attempt_log=None):
_, viewport = browser_geometry(text)
deadline = time.monotonic() + timeout
attempts = 0
captures = []
while True:
attempts += 1
frames = RENDER_FRAMES.findall(text())
before_frames = int(frames[-1]) if frames else None
path = Path(screenshot(name + '-' + str(attempts))).resolve()
with Image.open(path) as frame:
measured = paint_evidence(frame, proof, viewport, scale, require_heading)
anchors = required_token_evidence(measured, required_tokens, scale)
difference_bounds = None
if different_from is not None:
x, y, width, height = [round(value * scale) for value in viewport]
with Image.open(different_from) as before:
difference_bounds = ImageChops.difference(before.crop((x,y,x+width,y+height)).convert('RGB'),
frame.crop((x,y,x+width,y+height)).convert('RGB')).getbbox()
frames = RENDER_FRAMES.findall(text())
capture = {'Path': str(path), 'Sha256': hashlib.sha256(path.read_bytes()).hexdigest(),
'FrameCountBeforeCapture': before_frames, 'FrameCountAfterCapture': int(frames[-1]) if frames else None,
'PixelEvidencePassed': measured['Passed'], 'RequiredTokenEvidence': anchors,
'ViewportDifferenceBounds': difference_bounds, 'DifferenceRequired': different_from is not None}
capture['Passed'] = measured['Passed'] and anchors['Passed'] and (different_from is None or difference_bounds is not None)
captures.append(capture)
if attempt_log is not None:
attempt_log.append(capture)
if capture['Passed'] or time.monotonic() >= deadline:
break
time.sleep(.2)
assert capture['Passed'], ('Actual website DOM preview has no corresponding completed native framebuffer', capture, measured)
return {'Path': str(path), 'Sha256': capture['Sha256'], 'BrowserViewport': viewport,
'PixelEvidence': measured, 'RequiredTokenEvidence': anchors, 'Attempts': attempts, 'CaptureAttempts': captures,
'ViewportDifferenceBounds': difference_bounds}
def light_article_background(frame, proof, viewport, scale):
"""Sample only real empty article-top pixels, away from text/corners/pointer.
The threshold permits the website's subtle purple gradient; it does not
require flat pure white or replace the separate pixel-difference report.
"""
article=proof['Article']['Bounds'];vx,vy,width,height=viewport
assert proof['Theme']=='light'
points=[]
for fraction in (None,.25,.5,.75,False):
x=article['X']+(20 if fraction is None else article['Width']-20 if fraction is False else article['Width']*fraction)
y=article['Y']+(12 if fraction in (None,False) else 15)
assert 0 <= x < width and 0 <= y < height, ('Light article sample outside real viewport',x,y,viewport)
px,py=round((vx+x)*scale),round((vy+y)*scale)
crop=frame.crop((px-1,py-1,px+2,py+2)).convert('RGB')
minimum=[min(pixel[channel] for pixel in crop.getdata()) for channel in range(3)]
mean=[sum(pixel[channel] for pixel in crop.getdata())/9 for channel in range(3)]
points.append({'CssPosition':[x,y],'NativePosition':[px,py],'PatchSizePhysical':[3,3],
'MinimumRgb':minimum,'MeanRgb':mean,
'Passed':min(minimum)>=220 and minimum[2]>=230})
return {'Passed':all(point['Passed'] for point in points),'Theme':'light','Samples':points,
'Threshold':'Each empty-top patch RGB minimum >= 220 and blue >= 230; non-flat light gradient is allowed',
'OriginalScreenshotOnly':True,'Meaning':'Regression guard against opaque black gradient fallback; not a claim of exact website pixel parity.'}
def run_fixture_checks(qmp, text, screenshot, scale, pointer):
"""Continue from an existing Browser window; navigate the real fixture URL."""
checks = []
screenshots = []
attempt_log = []
evidence = {'Kind': 'NativeWebsiteMarkdownFixture', 'Passed': False, 'Checks': checks,
'Screenshots': screenshots, 'HostReferenceCountsAsNativeProof': False,
'ExactWebsitePixelParityAccepted': False, 'FramebufferCaptureAttempts': attempt_log}
output = None
def key(code, down):
qmp('input-send-event', {'events': [{'type': 'key', 'data': {'down': down, 'key': {'type': 'qcode', 'data': code}}}]});time.sleep(.03)
def tap(code):
key(code, True);key(code, False)
def chord(modifier, code):
key(modifier, True);tap(code);key(modifier, False)
def move(x, y):
target=[round(x*scale), round(y*scale)]
while pointer != target:
dx=max(-80,min(80,target[0]-pointer[0]));dy=max(-80,min(80,target[1]-pointer[1]))
qmp('input-send-event',{'events':[{'type':'rel','data':{'axis':'x','value':dx}},{'type':'rel','data':{'axis':'y','value':dy}}]})
pointer[0]+=dx;pointer[1]+=dy;time.sleep(.02)
def click(x, y):
move(x,y)
for down in (True,False):
qmp('input-send-event',{'events':[{'type':'btn','data':{'down':down,'button':'left'}}]});time.sleep(.1)
def capture(name, proof, require_heading=True, **options):
nonlocal output
item=capture_document(screenshot,name,text,proof,scale,require_heading=require_heading,attempt_log=attempt_log,**options)
screenshots.append(item);output=Path(item['Path']).parent
return item
try:
start=len(text());chord('ctrl','l')
for character in 'about:Documents':
if character==':':chord('shift','semicolon')
elif character.isupper():chord('shift',character.lower())
else:tap(character)
tap('ret')
proof=wait_document(text,start,lambda item:(item.get('Heading') or {}).get('Text')=='XFESystem 文档预览')
assert proof['Theme']=='light', 'Controlled screenshot fixture must declare its actual light theme'
fixture_path=ROOT/'Source/XFESystem.Documents/Assets/Website/Fixtures/WebsiteParity.md'
fixture=fixture_path.read_text('utf-8-sig').replace('\r\n','\n')
expected_code=re.search(r'```csharp\n(.*?)\n```',fixture,re.S).group(1)+'\n'
code=verify_code(proof,'csharp',expected_code)
assert proof['Table'] and 'Language' in proof['Table']['Text']
assert proof['Blockquote'] and 'Markdown' in proof['Blockquote']['Text']
assert any(span['Text']=='public' and normalized_color(span['Color'])=='569cd6' for span in code['Tokens'])
assert any('hljs-type' in span['Classes'].split() and normalized_color(span['Color'])=='4ec9b0' for span in code['Tokens'])
evidence['FixtureSha256']=hashlib.sha256(fixture_path.read_bytes()).hexdigest()
evidence['FixturePath']=str(fixture_path)
evidence['InitialDomReadback']=proof
checks.append('actual native generated Markdown fixture uses the website component and canonical CSS/JS assets with unchanged C# source and token colors')
window,viewport=browser_geometry(text)
frames=RENDER_FRAMES.findall(text());before_frames=int(frames[-1]) if frames else 0
after_frames=before_frames;settle_steps=[]
# Rendering is event-driven. Two separate physical pointer movements in
# the address toolbar each require a subsequent real render-stat ack;
# idle frames are neither assumed nor fabricated.
for offset in (20,22):
serial_start=len(text());prior_frames=after_frames
point=(window['X']+window['Width']-offset,window['Y']+50)
if pointer==[round(point[0]*scale),round(point[1]*scale)]:point=(point[0]-1,point[1])
move(*point)
settle_deadline=time.monotonic()+12
while time.monotonic()<settle_deadline:
reports=list(RENDER_FRAMES.finditer(text()[serial_start:]))
if reports and int(reports[-1].group(1))>=prior_frames+1:
after_frames=int(reports[-1].group(1));break
time.sleep(.1)
assert after_frames>=prior_frames+1, ('No actual native frame after a real out-of-article pointer movement',prior_frames,after_frames,point)
settle_steps.append({'PointerCssPosition':list(point),'BeforeFrames':prior_frames,'AfterFrames':after_frames})
assert after_frames>=before_frames+2
evidence['InitialRenderSettled']={'BeforeFrames':before_frames,'AfterFrames':after_frames,'AtLeastTwoFrames':True,
'AcknowledgedPointerMovements':settle_steps,'PointerCssPosition':list(point),'PointerOutsideArticle':True}
first=capture('Browser-Website-Markdown-Ready',proof)
evidence['InitialFramebuffer']=first
with Image.open(first['Path']) as frame:
background=light_article_background(frame,proof,viewport,scale)
evidence['LightArticleBackground']=background
assert background['Passed'], ('Native light article background is dark despite readable DOM colors',background)
checks.append('five empty article-top patches preserve the actual light gradient brightness after two native render frames with pointer outside content')
button=next(button for button in code['Buttons'] if button['Action']=='lines')
rectangle=button['Bounds']
# A narrower original browser window can put the code below the fold.
# Scroll through the real mouse path, then re-read actual viewport rects.
if not (0 <= rectangle['Y'] < viewport[3]-24) or first['PixelEvidence']['VisibleTokenSpansWithActualInk'] < 2:
move(viewport[0]+viewport[2]/2,viewport[1]+viewport[3]/2)
for _ in range(max(1,min(12,int(max(0,rectangle['Y']-80)/60)))):
qmp('input-send-event',{'events':[{'type':'btn','data':{'down':True,'button':'wheel-down'}}]});time.sleep(.05)
qmp('input-send-event',{'events':[{'type':'btn','data':{'down':False,'button':'wheel-down'}}]});time.sleep(.1)
scroll_start=len(text());move(window['X']+window['Width']-20,window['Y']+50)
proof=wait_document(text,scroll_start,lambda item:any(0 <= next((button['Bounds']['Y'] for button in block['Buttons'] if button['Action']=='lines'),-1) < viewport[3]-24 for block in item.get('CodeBlocks',[])),timeout=20)
code=verify_code(proof,'csharp',expected_code)
button=next(button for button in code['Buttons'] if button['Action']=='lines');rectangle=button['Bounds']
code_frame=capture('Browser-Website-Markdown-Code',proof,require_heading=False,required_tokens=('using','System'))
evidence['CodeFramebuffer']=code_frame
checks.append('actual native Markdown heading and visible website code-token rectangles contain matching framebuffer glyph colors')
assert 0 <= rectangle['X'] < viewport[2] and 0 <= rectangle['Y'] < viewport[3], ('Real line-number button is outside the initial viewport',rectangle,viewport)
assert 'code-lines-visible' in code['Classes'].split() and button['Pressed']=='true'
frames=RENDER_FRAMES.findall(text());before_toggle_frames=int(frames[-1]) if frames else 0
start=len(text());click(viewport[0]+rectangle['X']+rectangle['Width']/2,viewport[1]+rectangle['Y']+rectangle['Height']/2)
changed=wait_document(text,start,lambda item:any('code-lines-visible' not in block['Classes'].split() for block in item.get('CodeBlocks',[])))
changed_code=verify_code(changed,'csharp',expected_code)
assert next(button for button in changed_code['Buttons'] if button['Action']=='lines')['Pressed']=='false'
assert changed_code['CodeBounds']['X'] < code['CodeBounds']['X'], ('Actual line-number click changed state but not the web layout',code['CodeBounds'],changed_code['CodeBounds'])
evidence['LinesHiddenDomReadback']=changed
presented={};evidence['LinesHiddenPresentation']=presented
deadline=wait_document_presented_frame(text,start,changed,before_toggle_frames,presented)
move(window['X']+window['Width']-20,window['Y']+50)
second=capture('Browser-Website-Markdown-LinesHidden',changed,require_heading=False,
required_tokens=('using','System'),different_from=code_frame['Path'],
timeout=max(.001,deadline-time.monotonic()))
with Image.open(code_frame['Path']) as before, Image.open(second['Path']) as after:
bounds=code_frame['BrowserViewport'];x,y,w,h=[round(value*scale) for value in bounds]
difference=ImageChops.difference(before.crop((x,y,x+w,y+h)).convert('RGB'),after.crop((x,y,x+w,y+h)).convert('RGB'))
assert difference.getbbox() is not None, 'Real line-number click produced no native framebuffer change'
checks.append('real mouse hit invokes the original website line-number button event, changes aria/class, relayouts code and repaints native pixels')
evidence['ObservedFeatureGaps']=[]
if proof.get('KatexCount',0)==0:evidence['ObservedFeatureGaps'].append('The fixture math did not produce KaTeX DOM')
if proof.get('MermaidSvgCount',0)==0:evidence['ObservedFeatureGaps'].append('The fixture diagram did not produce a Mermaid SVG')
evidence['MathDomCount']=proof.get('KatexCount',0);evidence['MermaidSvgCount']=proof.get('MermaidSvgCount',0)
evidence['Boundary']='Core Markdown/code/toolbar behavior checked. Font/raster differences and optional math/diagram gaps are reported separately; passing is not a claim of complete website pixel parity.'
evidence['Passed']=True
return evidence
except Exception as error:
evidence['Failure']=str(error)
raise
finally:
if output is not None:
path=output/'WebsiteDocumentEvidence.json'
path.write_text(json.dumps(evidence,ensure_ascii=False,indent=2)+'\n','utf-8')
evidence['EvidencePath']=str(path);evidence['EvidenceSha256']=hashlib.sha256(path.read_bytes()).hexdigest()
"""Native website-style Markdown/code proof using actual QMP input and pixels.
The guest exports fixed read-only DOM diagnostics. This harness never injects
source, changes guest DOM through a debugger, or substitutes a host screenshot.
"""
import hashlib
import json
from pathlib import Path
import re
import time
from urllib.parse import urlsplit
from PIL import Image, ImageChops
from NativeCSharpEvidence import normalized_color
ROOT = Path(__file__).resolve().parents[1]
DOCUMENT = re.compile(r'XFE_WEBSITE_DOCUMENT_PROOF_BEGIN\r?\n(.*?)\r?\nXFE_WEBSITE_DOCUMENT_PROOF_END', re.S)
WINDOW = re.compile(r'^XFE_WINDOW_STATE\|(\d+)\|Browser\|(-?\d+)\|(-?\d+)\|(\d+)\|(\d+)\|([01])\|([01])\|([01])$', re.M)
RENDER_FRAMES = re.compile(r'XFE_RENDER_STATS\|\d+\|Frames\|(\d+)\|')
def wait_document(text, start, predicate=lambda proof: True, timeout=90):
deadline = time.monotonic() + timeout
failure = 'No complete native website-document readback'
while time.monotonic() < deadline:
for match in reversed(list(DOCUMENT.finditer(text()[start:]))):
try:
proof = json.loads(match.group(1))
if predicate(proof):
verify_base(proof)
return proof
except (AssertionError, ValueError, KeyError, TypeError) as error:
failure = str(error)
assert all(marker not in text() for marker in ('CPU EXCEPTION:', '[FAILFAST]', '*** UNHANDLED EXCEPTION',
'XFE_RUNTIME_OOM|', 'XFE_RUNTIME_STACK_OOM|')), text()[-4000:]
time.sleep(.1)
raise AssertionError('Actual native website document did not stabilize: ' + failure)
def verify_base(proof):
assert proof['Kind'] == 'ActualWebsiteDocumentDomReadback' and proof['ReadOnly'] is True
assert proof['NoHostScreenshotSubstitution'] is True
assert urlsplit(proof['Address']).netloc == 'xfesystem.local'
assert proof['Article'] and proof['Article']['Bounds']['Width'] > 0
styles = {urlsplit(value).path for value in proof['Styles']}
scripts = {urlsplit(value).path for value in proof['Scripts']}
assert {'/css/docs/code-style.css', '/css/docs/markdown-style.css', '/css/docs/docs-layout.css', '/css/theme-variables.css'}.issubset(styles)
assert {'/js/docs/highlight.min.js', '/js/docs/csharp.js', '/js/docs/doc-utils.js', '/js/docs/code-block-tools.js'}.issubset(scripts)
assert proof['WebsiteHighlighterAvailable'] is True and proof['WebsiteCodeToolsAvailable'] is True
def verify_code(proof, language, source):
verify_base(proof)
code = next(block for block in proof['CodeBlocks'] if 'language-' + language in block['CodeClasses'].split())
assert code['CodeText'] == source, ('Actual native source preview differs from the editor model', source, code['CodeText'])
assert code['Tokens'] and any('hljs-keyword' in span['Classes'].split() for span in code['Tokens'])
assert len(code['Tokens']) >= 3
assert {button['Action'] for button in code['Buttons']} == {'lines', 'download', 'expand', 'copy'}
assert code['LineNumberCount'] == max(1, len(source.rstrip('\n').split('\n')))
assert code['CodeBounds']['Width'] > 0 and code['CodeBounds']['Height'] > 0
return code
def browser_geometry(text):
matches = list(WINDOW.finditer(text()))
assert matches, 'No native browser window geometry'
parts = matches[-1].groups()
window = {'Id': int(parts[0]), 'X': int(parts[1]), 'Y': int(parts[2]), 'Width': int(parts[3]), 'Height': int(parts[4])}
viewport = [window['X'] + 2, window['Y'] + 67, window['Width'] - 4, window['Height'] - 96]
return window, viewport
def paint_evidence(frame, proof, viewport, scale, require_heading=False):
vx, vy, width, height = viewport
visible = [0, 0, width, height]
counts = []
candidates = []
if proof.get('Heading'):
candidates.append(('Heading', proof['Heading']['Text'], proof['Heading']['Bounds'], proof['Heading']['Style']['Color']))
for block in proof.get('CodeBlocks', []):
candidates.extend(('CodeToken', span['Text'], span['Bounds'], span['Color']) for span in block['Tokens'] if span['Text'].strip())
for kind, source, bounds, css_color in candidates:
left = max(visible[0], bounds['X']); top = max(visible[1], bounds['Y'])
right = min(width, bounds['X'] + bounds['Width']); bottom = min(height, bounds['Y'] + bounds['Height'])
if right - left <= 1 or bottom - top <= 1:
continue
rgb = tuple(int(normalized_color(css_color)[index:index + 2], 16) for index in (0, 2, 4))
physical = [round((vx + left) * scale), round((vy + top) * scale), round((vx + right) * scale), round((vy + bottom) * scale)]
crop = frame.crop(physical).convert('RGB')
pixels = sum(all(abs(pixel[channel] - rgb[channel]) <= 12 for channel in range(3)) for pixel in crop.getdata())
counts.append({'Kind': kind, 'Text': source, 'ExpectedCssColor': normalized_color(css_color), 'PhysicalBounds': physical, 'MatchingPixels': pixels})
heading = any(item['Kind'] == 'Heading' and item['MatchingPixels'] >= 4 * scale * scale for item in counts)
token_count = sum(item['Kind'] == 'CodeToken' and item['MatchingPixels'] >= 2 * scale * scale for item in counts)
return {'Passed': (heading if require_heading else token_count >= 2), 'HeadingPainted': heading,
'VisibleTokenSpansWithActualInk': token_count, 'Samples': counts,
'Meaning': 'Observed framebuffer glyph ink in actual laid-out DOM rectangles; offscreen tokens do not count.'}
def required_token_evidence(measured, texts, scale):
"""Short non-overlapping anchors cannot pass from a stale shifted code row."""
threshold = 2 * scale * scale
samples = [next((item for item in measured['Samples'] if item['Kind'] == 'CodeToken' and item['Text'] == token),
{'Kind': 'CodeToken', 'Text': token, 'MatchingPixels': 0}) for token in texts]
return {'Passed': all(item['MatchingPixels'] >= threshold for item in samples),
'MinimumMatchingPixelsPerToken': threshold, 'Samples': samples}
def wait_document_presented_frame(text, start, proof, before_frames, evidence, timeout=60):
# The guest emits this readback before Render(), so observing DOM state is
# not presentation evidence. Bind the ack to the complete matching END.
document_end = None
for match in reversed(list(DOCUMENT.finditer(text()[start:]))):
try:
if json.loads(match.group(1)) == proof:
document_end = start + match.end()
break
except (ValueError, TypeError):
continue
assert document_end is not None, 'The changed document has no complete matching serial END'
evidence.update({'DocumentEndOffset': document_end, 'BeforeFrames': before_frames,
'DeadlineSeconds': timeout, 'Passed': False})
deadline = time.monotonic() + timeout
while True:
current = text()
reports = list(RENDER_FRAMES.finditer(current[document_end:]))
evidence['ObservedFramesAfterDocumentEnd'] = [int(report.group(1)) for report in reports]
if reports and int(reports[-1].group(1)) > before_frames:
evidence.update({'AfterFrames': int(reports[-1].group(1)), 'Passed': True})
return deadline
assert all(marker not in current for marker in ('CPU EXCEPTION:', '[FAILFAST]', '*** UNHANDLED EXCEPTION',
'XFE_RUNTIME_OOM|', 'XFE_RUNTIME_STACK_OOM|')), current[-4000:]
assert time.monotonic() < deadline, ('No actual completed native frame after changed document END', evidence)
time.sleep(.1)
def capture_document(screenshot, name, text, proof, scale, require_heading=False, timeout=60,
required_tokens=(), different_from=None, attempt_log=None):
_, viewport = browser_geometry(text)
deadline = time.monotonic() + timeout
attempts = 0
captures = []
while True:
attempts += 1
frames = RENDER_FRAMES.findall(text())
before_frames = int(frames[-1]) if frames else None
path = Path(screenshot(name + '-' + str(attempts))).resolve()
with Image.open(path) as frame:
measured = paint_evidence(frame, proof, viewport, scale, require_heading)
anchors = required_token_evidence(measured, required_tokens, scale)
difference_bounds = None
if different_from is not None:
x, y, width, height = [round(value * scale) for value in viewport]
with Image.open(different_from) as before:
difference_bounds = ImageChops.difference(before.crop((x,y,x+width,y+height)).convert('RGB'),
frame.crop((x,y,x+width,y+height)).convert('RGB')).getbbox()
frames = RENDER_FRAMES.findall(text())
capture = {'Path': str(path), 'Sha256': hashlib.sha256(path.read_bytes()).hexdigest(),
'FrameCountBeforeCapture': before_frames, 'FrameCountAfterCapture': int(frames[-1]) if frames else None,
'PixelEvidencePassed': measured['Passed'], 'RequiredTokenEvidence': anchors,
'ViewportDifferenceBounds': difference_bounds, 'DifferenceRequired': different_from is not None}
capture['Passed'] = measured['Passed'] and anchors['Passed'] and (different_from is None or difference_bounds is not None)
captures.append(capture)
if attempt_log is not None:
attempt_log.append(capture)
if capture['Passed'] or time.monotonic() >= deadline:
break
time.sleep(.2)
assert capture['Passed'], ('Actual website DOM preview has no corresponding completed native framebuffer', capture, measured)
return {'Path': str(path), 'Sha256': capture['Sha256'], 'BrowserViewport': viewport,
'PixelEvidence': measured, 'RequiredTokenEvidence': anchors, 'Attempts': attempts, 'CaptureAttempts': captures,
'ViewportDifferenceBounds': difference_bounds}
def light_article_background(frame, proof, viewport, scale):
"""Sample only real empty article-top pixels, away from text/corners/pointer.
The threshold permits the website's subtle purple gradient; it does not
require flat pure white or replace the separate pixel-difference report.
"""
article=proof['Article']['Bounds'];vx,vy,width,height=viewport
assert proof['Theme']=='light'
points=[]
for fraction in (None,.25,.5,.75,False):
x=article['X']+(20 if fraction is None else article['Width']-20 if fraction is False else article['Width']*fraction)
y=article['Y']+(12 if fraction in (None,False) else 15)
assert 0 <= x < width and 0 <= y < height, ('Light article sample outside real viewport',x,y,viewport)
px,py=round((vx+x)*scale),round((vy+y)*scale)
crop=frame.crop((px-1,py-1,px+2,py+2)).convert('RGB')
minimum=[min(pixel[channel] for pixel in crop.getdata()) for channel in range(3)]
mean=[sum(pixel[channel] for pixel in crop.getdata())/9 for channel in range(3)]
points.append({'CssPosition':[x,y],'NativePosition':[px,py],'PatchSizePhysical':[3,3],
'MinimumRgb':minimum,'MeanRgb':mean,
'Passed':min(minimum)>=220 and minimum[2]>=230})
return {'Passed':all(point['Passed'] for point in points),'Theme':'light','Samples':points,
'Threshold':'Each empty-top patch RGB minimum >= 220 and blue >= 230; non-flat light gradient is allowed',
'OriginalScreenshotOnly':True,'Meaning':'Regression guard against opaque black gradient fallback; not a claim of exact website pixel parity.'}
def run_fixture_checks(qmp, text, screenshot, scale, pointer):
"""Continue from an existing Browser window; navigate the real fixture URL."""
checks = []
screenshots = []
attempt_log = []
evidence = {'Kind': 'NativeWebsiteMarkdownFixture', 'Passed': False, 'Checks': checks,
'Screenshots': screenshots, 'HostReferenceCountsAsNativeProof': False,
'ExactWebsitePixelParityAccepted': False, 'FramebufferCaptureAttempts': attempt_log}
output = None
def key(code, down):
qmp('input-send-event', {'events': [{'type': 'key', 'data': {'down': down, 'key': {'type': 'qcode', 'data': code}}}]});time.sleep(.03)
def tap(code):
key(code, True);key(code, False)
def chord(modifier, code):
key(modifier, True);tap(code);key(modifier, False)
def move(x, y):
target=[round(x*scale), round(y*scale)]
while pointer != target:
dx=max(-80,min(80,target[0]-pointer[0]));dy=max(-80,min(80,target[1]-pointer[1]))
qmp('input-send-event',{'events':[{'type':'rel','data':{'axis':'x','value':dx}},{'type':'rel','data':{'axis':'y','value':dy}}]})
pointer[0]+=dx;pointer[1]+=dy;time.sleep(.02)
def click(x, y):
move(x,y)
for down in (True,False):
qmp('input-send-event',{'events':[{'type':'btn','data':{'down':down,'button':'left'}}]});time.sleep(.1)
def capture(name, proof, require_heading=True, **options):
nonlocal output
item=capture_document(screenshot,name,text,proof,scale,require_heading=require_heading,attempt_log=attempt_log,**options)
screenshots.append(item);output=Path(item['Path']).parent
return item
try:
start=len(text());chord('ctrl','l')
for character in 'about:Documents':
if character==':':chord('shift','semicolon')
elif character.isupper():chord('shift',character.lower())
else:tap(character)
tap('ret')
proof=wait_document(text,start,lambda item:(item.get('Heading') or {}).get('Text')=='XFESystem 文档预览')
assert proof['Theme']=='light', 'Controlled screenshot fixture must declare its actual light theme'
fixture_path=ROOT/'Source/XFESystem.Documents/Assets/Website/Fixtures/WebsiteParity.md'
fixture=fixture_path.read_text('utf-8-sig').replace('\r\n','\n')
expected_code=re.search(r'```csharp\n(.*?)\n```',fixture,re.S).group(1)+'\n'
code=verify_code(proof,'csharp',expected_code)
assert proof['Table'] and 'Language' in proof['Table']['Text']
assert proof['Blockquote'] and 'Markdown' in proof['Blockquote']['Text']
assert any(span['Text']=='public' and normalized_color(span['Color'])=='569cd6' for span in code['Tokens'])
assert any('hljs-type' in span['Classes'].split() and normalized_color(span['Color'])=='4ec9b0' for span in code['Tokens'])
evidence['FixtureSha256']=hashlib.sha256(fixture_path.read_bytes()).hexdigest()
evidence['FixturePath']=str(fixture_path)
evidence['InitialDomReadback']=proof
checks.append('actual native generated Markdown fixture uses the website component and canonical CSS/JS assets with unchanged C# source and token colors')
window,viewport=browser_geometry(text)
frames=RENDER_FRAMES.findall(text());before_frames=int(frames[-1]) if frames else 0
after_frames=before_frames;settle_steps=[]
# Rendering is event-driven. Two separate physical pointer movements in
# the address toolbar each require a subsequent real render-stat ack;
# idle frames are neither assumed nor fabricated.
for offset in (20,22):
serial_start=len(text());prior_frames=after_frames
point=(window['X']+window['Width']-offset,window['Y']+50)
if pointer==[round(point[0]*scale),round(point[1]*scale)]:point=(point[0]-1,point[1])
move(*point)
settle_deadline=time.monotonic()+12
while time.monotonic()<settle_deadline:
reports=list(RENDER_FRAMES.finditer(text()[serial_start:]))
if reports and int(reports[-1].group(1))>=prior_frames+1:
after_frames=int(reports[-1].group(1));break
time.sleep(.1)
assert after_frames>=prior_frames+1, ('No actual native frame after a real out-of-article pointer movement',prior_frames,after_frames,point)
settle_steps.append({'PointerCssPosition':list(point),'BeforeFrames':prior_frames,'AfterFrames':after_frames})
assert after_frames>=before_frames+2
evidence['InitialRenderSettled']={'BeforeFrames':before_frames,'AfterFrames':after_frames,'AtLeastTwoFrames':True,
'AcknowledgedPointerMovements':settle_steps,'PointerCssPosition':list(point),'PointerOutsideArticle':True}
first=capture('Browser-Website-Markdown-Ready',proof)
evidence['InitialFramebuffer']=first
with Image.open(first['Path']) as frame:
background=light_article_background(frame,proof,viewport,scale)
evidence['LightArticleBackground']=background
assert background['Passed'], ('Native light article background is dark despite readable DOM colors',background)
checks.append('five empty article-top patches preserve the actual light gradient brightness after two native render frames with pointer outside content')
button=next(button for button in code['Buttons'] if button['Action']=='lines')
rectangle=button['Bounds']
# A narrower original browser window can put the code below the fold.
# Scroll through the real mouse path, then re-read actual viewport rects.
if not (0 <= rectangle['Y'] < viewport[3]-24) or first['PixelEvidence']['VisibleTokenSpansWithActualInk'] < 2:
move(viewport[0]+viewport[2]/2,viewport[1]+viewport[3]/2)
for _ in range(max(1,min(12,int(max(0,rectangle['Y']-80)/60)))):
qmp('input-send-event',{'events':[{'type':'btn','data':{'down':True,'button':'wheel-down'}}]});time.sleep(.05)
qmp('input-send-event',{'events':[{'type':'btn','data':{'down':False,'button':'wheel-down'}}]});time.sleep(.1)
scroll_start=len(text());move(window['X']+window['Width']-20,window['Y']+50)
proof=wait_document(text,scroll_start,lambda item:any(0 <= next((button['Bounds']['Y'] for button in block['Buttons'] if button['Action']=='lines'),-1) < viewport[3]-24 for block in item.get('CodeBlocks',[])),timeout=20)
code=verify_code(proof,'csharp',expected_code)
button=next(button for button in code['Buttons'] if button['Action']=='lines');rectangle=button['Bounds']
code_frame=capture('Browser-Website-Markdown-Code',proof,require_heading=False,required_tokens=('using','System'))
evidence['CodeFramebuffer']=code_frame
checks.append('actual native Markdown heading and visible website code-token rectangles contain matching framebuffer glyph colors')
assert 0 <= rectangle['X'] < viewport[2] and 0 <= rectangle['Y'] < viewport[3], ('Real line-number button is outside the initial viewport',rectangle,viewport)
assert 'code-lines-visible' in code['Classes'].split() and button['Pressed']=='true'
frames=RENDER_FRAMES.findall(text());before_toggle_frames=int(frames[-1]) if frames else 0
start=len(text());click(viewport[0]+rectangle['X']+rectangle['Width']/2,viewport[1]+rectangle['Y']+rectangle['Height']/2)
changed=wait_document(text,start,lambda item:any('code-lines-visible' not in block['Classes'].split() for block in item.get('CodeBlocks',[])))
changed_code=verify_code(changed,'csharp',expected_code)
assert next(button for button in changed_code['Buttons'] if button['Action']=='lines')['Pressed']=='false'
assert changed_code['CodeBounds']['X'] < code['CodeBounds']['X'], ('Actual line-number click changed state but not the web layout',code['CodeBounds'],changed_code['CodeBounds'])
evidence['LinesHiddenDomReadback']=changed
presented={};evidence['LinesHiddenPresentation']=presented
deadline=wait_document_presented_frame(text,start,changed,before_toggle_frames,presented)
move(window['X']+window['Width']-20,window['Y']+50)
second=capture('Browser-Website-Markdown-LinesHidden',changed,require_heading=False,
required_tokens=('using','System'),different_from=code_frame['Path'],
timeout=max(.001,deadline-time.monotonic()))
with Image.open(code_frame['Path']) as before, Image.open(second['Path']) as after:
bounds=code_frame['BrowserViewport'];x,y,w,h=[round(value*scale) for value in bounds]
difference=ImageChops.difference(before.crop((x,y,x+w,y+h)).convert('RGB'),after.crop((x,y,x+w,y+h)).convert('RGB'))
assert difference.getbbox() is not None, 'Real line-number click produced no native framebuffer change'
checks.append('real mouse hit invokes the original website line-number button event, changes aria/class, relayouts code and repaints native pixels')
evidence['ObservedFeatureGaps']=[]
if proof.get('KatexCount',0)==0:evidence['ObservedFeatureGaps'].append('The fixture math did not produce KaTeX DOM')
if proof.get('MermaidSvgCount',0)==0:evidence['ObservedFeatureGaps'].append('The fixture diagram did not produce a Mermaid SVG')
evidence['MathDomCount']=proof.get('KatexCount',0);evidence['MermaidSvgCount']=proof.get('MermaidSvgCount',0)
evidence['Boundary']='Core Markdown/code/toolbar behavior checked. Font/raster differences and optional math/diagram gaps are reported separately; passing is not a claim of complete website pixel parity.'
evidence['Passed']=True
return evidence
except Exception as error:
evidence['Failure']=str(error)
raise
finally:
if output is not None:
path=output/'WebsiteDocumentEvidence.json'
path.write_text(json.dumps(evidence,ensure_ascii=False,indent=2)+'\n','utf-8')
evidence['EvidencePath']=str(path);evidence['EvidenceSha256']=hashlib.sha256(path.read_bytes()).hexdigest()