upgrade: adopt V8.34.1 (multi-image banner carousel + spec-table source modes)
Plain overlay (33 items, same-source lineage, zero patch replay). Theme suites 35/35; full run 453 passed + 10 known Windows env fails (suspect-module rerun confirms identical failure set). Live: container 8.34.1-dev.1; hesupipe home leftover industrial-preset blocks cleared via studio draft+publish flow — home now renders pure trade-pro template (sf-section/Chinese blocks = 0). Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
"""V8.29 isolated regression with per-batch deadline and raw logs."""
|
||||
import argparse,datetime,json,subprocess,sys,time
|
||||
from pathlib import Path
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
def main():
|
||||
ap=argparse.ArgumentParser();ap.add_argument('--timeout',type=int,default=90);ap.add_argument('--group-size',type=int,default=3);ap.add_argument('--start',type=int,default=1);ap.add_argument('--end',type=int,default=999);args=ap.parse_args()
|
||||
all_files=sorted((ROOT/'tests').glob('test_*.py'))
|
||||
groups=[all_files[i:i+args.group_size] for i in range(0,len(all_files),args.group_size)]
|
||||
dest=ROOT/'reports/v834-regression';dest.mkdir(parents=True,exist_ok=True)
|
||||
outcomes=[]
|
||||
for index,files in enumerate(groups,1):
|
||||
if not args.start <= index <= args.end:continue
|
||||
start=time.monotonic();command=[sys.executable,'-m','pytest','-q',*[str(f.relative_to(ROOT)) for f in files]]
|
||||
try:
|
||||
proc=subprocess.run(command,cwd=ROOT,capture_output=True,text=True,timeout=args.timeout)
|
||||
code=proc.returncode;output=proc.stdout+'\n'+proc.stderr;status='passed' if code==0 else 'failed'
|
||||
except subprocess.TimeoutExpired as e:
|
||||
output=(e.stdout or b'').decode(errors='replace') if isinstance(e.stdout,bytes) else (e.stdout or '')
|
||||
output+='\nTIMEOUT exceeded';status='timeout'
|
||||
elapsed=round(time.monotonic()-start,2)
|
||||
(dest/f'batch-{index:02}.log').write_text(output,encoding='utf-8')
|
||||
outcomes.append({'batch':index,'files':[f.name for f in files],'status':status,'seconds':elapsed,'summary':output[-350:]})
|
||||
print(f'[{index:02}/{len(groups)}] {status.upper()} {elapsed:.1f}s: '+', '.join(f.name for f in files),flush=True)
|
||||
result={'version':'8.34.0-dev.1','generated_utc':datetime.datetime.now(datetime.timezone.utc).isoformat(),'groups':outcomes,'all_passed':all(x['status']=='passed' for x in outcomes)}
|
||||
(dest/'result.json').write_text(json.dumps(result,ensure_ascii=False,indent=2),encoding='utf-8')
|
||||
print('RESULT:',result['all_passed'],sum(x['status']=='passed' for x in outcomes),len(groups),flush=True)
|
||||
return 0 if result['all_passed'] else 1
|
||||
if __name__=='__main__':raise SystemExit(main())
|
||||
@@ -0,0 +1,28 @@
|
||||
"""V8.29 isolated regression with per-batch deadline and raw logs."""
|
||||
import argparse,datetime,json,subprocess,sys,time
|
||||
from pathlib import Path
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
def main():
|
||||
ap=argparse.ArgumentParser();ap.add_argument('--timeout',type=int,default=90);ap.add_argument('--group-size',type=int,default=3);ap.add_argument('--start',type=int,default=1);ap.add_argument('--end',type=int,default=999);args=ap.parse_args()
|
||||
all_files=sorted((ROOT/'tests').glob('test_*.py'))
|
||||
groups=[all_files[i:i+args.group_size] for i in range(0,len(all_files),args.group_size)]
|
||||
dest=ROOT/'reports/v8341-regression';dest.mkdir(parents=True,exist_ok=True)
|
||||
outcomes=[]
|
||||
for index,files in enumerate(groups,1):
|
||||
if not args.start <= index <= args.end:continue
|
||||
start=time.monotonic();command=[sys.executable,'-m','pytest','-q',*[str(f.relative_to(ROOT)) for f in files]]
|
||||
try:
|
||||
proc=subprocess.run(command,cwd=ROOT,capture_output=True,text=True,timeout=args.timeout)
|
||||
code=proc.returncode;output=proc.stdout+'\n'+proc.stderr;status='passed' if code==0 else 'failed'
|
||||
except subprocess.TimeoutExpired as e:
|
||||
output=(e.stdout or b'').decode(errors='replace') if isinstance(e.stdout,bytes) else (e.stdout or '')
|
||||
output+='\nTIMEOUT exceeded';status='timeout'
|
||||
elapsed=round(time.monotonic()-start,2)
|
||||
(dest/f'batch-{index:02}.log').write_text(output,encoding='utf-8')
|
||||
outcomes.append({'batch':index,'files':[f.name for f in files],'status':status,'seconds':elapsed,'summary':output[-350:]})
|
||||
print(f'[{index:02}/{len(groups)}] {status.upper()} {elapsed:.1f}s: '+', '.join(f.name for f in files),flush=True)
|
||||
result={'version':'8.34.1-dev.1','generated_utc':datetime.datetime.now(datetime.timezone.utc).isoformat(),'groups':outcomes,'all_passed':all(x['status']=='passed' for x in outcomes)}
|
||||
(dest/'result.json').write_text(json.dumps(result,ensure_ascii=False,indent=2),encoding='utf-8')
|
||||
print('RESULT:',result['all_passed'],sum(x['status']=='passed' for x in outcomes),len(groups),flush=True)
|
||||
return 0 if result['all_passed'] else 1
|
||||
if __name__=='__main__':raise SystemExit(main())
|
||||
@@ -0,0 +1,43 @@
|
||||
"""Test offline actual renderer composed HTML and 390px responsiveness."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
from playwright.sync_api import sync_playwright
|
||||
root=Path(__file__).resolve().parents[1]
|
||||
raw=(root/'examples/V8_34_主题Banner与产品卡片_真实渲染交互预览.html').read_text()
|
||||
out=root/'reports/v834';out.mkdir(parents=True,exist_ok=True)
|
||||
results=[]
|
||||
with sync_playwright() as p:
|
||||
browser=p.chromium.launch(executable_path='/usr/bin/chromium',headless=True,args=['--no-sandbox'])
|
||||
page=browser.new_page(viewport={'width':1480,'height':950},device_scale_factor=1)
|
||||
errors=[];page.on('pageerror',lambda err:errors.append(str(err)))
|
||||
page.set_content(raw,wait_until='load')
|
||||
page.locator('#canvas').wait_for()
|
||||
for theme in ['automotive','industrial']:
|
||||
page.locator('#theme').select_option(theme)
|
||||
for variant in ['split','image-first','centered']:
|
||||
page.locator('#banner').select_option(variant)
|
||||
for cards in ['standard','compact','showcase']:
|
||||
page.locator('#cards').select_option(cards)
|
||||
for width in ['100%','390px']:
|
||||
page.locator(f'[data-width="{width}"]').click()
|
||||
page.wait_for_timeout(80)
|
||||
frame=page.frame_locator('#canvas')
|
||||
hero=frame.locator('.sf-hero-grid')
|
||||
item=frame.locator('.sf-section.sf-collection > .sf-container > .sf-collection')
|
||||
assert hero.count()==1,(theme,variant,cards,width)
|
||||
assert item.count()==1,(theme,variant,cards,width)
|
||||
assert f'sf-hero-layout-{variant}' in hero.get_attribute('class')
|
||||
assert f'sf-card-style-{cards}' in item.get_attribute('class')
|
||||
overflow=frame.locator('body').evaluate('(e)=>e.scrollWidth>document.documentElement.clientWidth+1')
|
||||
results.append({'theme':theme,'banner':variant,'cards':cards,'width':width,'overflow':bool(overflow)})
|
||||
# canonical screenshots
|
||||
page.locator('#theme').select_option('industrial');page.locator('#banner').select_option('centered');page.locator('#cards').select_option('showcase');page.locator('[data-width="100%"]').click()
|
||||
page.wait_for_timeout(120)
|
||||
page.screenshot(path=str(out/'v834-desktop.png'),full_page=True)
|
||||
page.locator('[data-width="390px"]').click();page.wait_for_timeout(120)
|
||||
page.screenshot(path=str(out/'v834-phone.png'),full_page=True)
|
||||
assert not errors,errors
|
||||
browser.close()
|
||||
report={'cases':len(results),'overflows':[x for x in results if x['overflow']],'js_errors':errors,'screenshots':['v834-desktop.png','v834-phone.png']}
|
||||
(out/'browser-check.json').write_text(json.dumps(report,indent=2,ensure_ascii=False))
|
||||
print(json.dumps({'cases':len(results),'overflows':len(report['overflows']),'js_errors':errors},ensure_ascii=False))
|
||||
@@ -0,0 +1,29 @@
|
||||
"""Reproducible offline preview assembled from actual V8.34 server rendering."""
|
||||
import html,json
|
||||
from pathlib import Path
|
||||
from app.blueprints import new_block,render_blocks
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
OUT=ROOT/'examples/V8_34_主题Banner与产品卡片_真实渲染交互预览.html'
|
||||
css=(ROOT/'app/static/blocks.css').read_text()
|
||||
styles={k:((ROOT/f'themes/{k}/blocks.css').read_text() if (ROOT/f'themes/{k}/blocks.css').exists() else '') for k in ('automotive','industrial')}
|
||||
entries={
|
||||
'automotive':[{'route':f'/product/model-{i}/','title':v,'type':'product','cover':'','summary':'出口车型展示 · 可按实际车型数据替换','meta_json':'{}'} for i,v in enumerate(['SUV 车型 A','新能源车型 B','皮卡车型 C','商务车型 D','紧凑车型 E','乘用车型 F'],1)],
|
||||
'industrial':[{'route':f'/product/pipe-{i}/','title':v,'type':'product','cover':'','summary':'管道产品展示 · 可查看技术参数','meta_json':'{}'} for i,v in enumerate(['HDPE 给水管','PVC-U 排水管','双壁波纹管','钢丝网骨架管','土工布','土工膜'],1)]
|
||||
}
|
||||
views={}
|
||||
for theme in entries:
|
||||
views[theme]={}
|
||||
for banner in ('split','image-first','centered'):
|
||||
for cards in ('standard','compact','showcase'):
|
||||
blocks=[new_block('hero',{'title': '中国汽车 · 全球交付' if theme=='automotive' else '专业管道 · 工程应用', 'subtitle': 'GLOBAL AUTOMOTIVE' if theme=='automotive' else 'PIPE SYSTEM SOLUTIONS','description':'这是实际服务端渲染的内容模块,点击选项可比较布局。','image':'','layout_variant':banner,'image_fit':'contain','button':'查看产品','href':'/product/'}),new_block('collection',{'title':'精选车型' if theme=='automotive' else '主要产品','subtitle':'PRODUCT SELECTION','source':'product','limit':6,'columns':3,'card_style':cards,'image_fit':'contain'})]
|
||||
views[theme][banner+'-'+cards]=render_blocks(blocks,{'name':'DEMO'},{'route':'/','type':'home'},entries[theme])
|
||||
# escape script closure
|
||||
payload=json.dumps({'views':views,'styles':styles,'blocks':css},ensure_ascii=False).replace('</','<\\/')
|
||||
base=f'''<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>SiteForge V8.34 · 真正生成的主题布局</title>
|
||||
<style>*{{box-sizing:border-box}}body{{margin:0;background:#edf1f6;color:#24344b;font-family:system-ui,"Microsoft YaHei",sans-serif}}button,select{{font:inherit}}.toolbar{{padding:16px 20px;background:white;border-bottom:1px solid #dfe6ed;display:flex;gap:15px;align-items:center;flex-wrap:wrap}}.toolbar strong{{font-size:17px}}.toolbar small{{display:block;color:#75869a;font-size:11px}}.controls{{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-left:auto}}select{{background:#f7fafc;border:1px solid #c9d6e2;border-radius:8px;padding:9px;color:#2c485e;max-width:185px}}.size button{{padding:9px;border:1px solid #ccd7e2;background:white;border-radius:6px;cursor:pointer}}.size button[aria-pressed=true]{{background:#205a8a;color:#fff}}#canvas{{display:block;width:100%;height:calc(100vh - 100px);min-height:520px;margin:auto;border:0;background:white;transition:width .2s}}@media(max-width:680px){{.toolbar{{padding:12px;gap:10px}}.controls{{margin-left:0}}#canvas{{height:calc(100vh - 155px)}}}}</style></head><body>
|
||||
<div class="toolbar"><div><strong>SiteForge V8.34 · Banner 与产品卡片</strong><small>来自实际服务端区块渲染 · 仅演示布局,不连接业务后台</small></div><div class="controls"><select id="theme" aria-label="主题"><option value="automotive">汽车主题</option><option value="industrial">工业建材主题</option></select><select id="banner" aria-label="Banner 布局"><option value="split">Banner · 文字左</option><option value="image-first">Banner · 图片左</option><option value="centered">Banner · 标题居中</option></select><select id="cards" aria-label="卡片形式"><option value="standard">卡片 · 标准</option><option value="compact">卡片 · 紧凑</option><option value="showcase">卡片 · 图片优先</option></select><div class="size"><button data-width="100%" aria-pressed="true">电脑</button><button data-width="820px" aria-pressed="false">平板</button><button data-width="390px" aria-pressed="false">手机</button></div></div></div>
|
||||
<iframe id="canvas" title="主题页面真实渲染" sandbox="allow-scripts"></iframe><script type="application/json" id="data">{payload}</script><script>
|
||||
const data=JSON.parse(document.getElementById('data').textContent), frame=document.getElementById('canvas');
|
||||
function paint(){{const t=document.getElementById('theme').value,key=document.getElementById('banner').value+'-'+document.getElementById('cards').value;const page='<html><head><meta name="viewport" content="width=device-width, initial-scale=1"><style>'+data.blocks+'</style><style>'+data.styles[t]+'</style><style>body{{margin:0;font-family:system-ui,"Microsoft YaHei",sans-serif;background:white;color:#20354a}}.sf-hero-abstract{{background:linear-gradient(140deg,#eff4f8,#d5e3ed);border-radius:12px}}.sf-item{{height:100%}}</style></head><body><div class="sf-theme-'+t+'"><main class="pro-custom-content">'+data.views[t][key]+'</main></div></body></html>';frame.srcdoc=page;}}
|
||||
['theme','banner','cards'].forEach(id=>document.getElementById(id).addEventListener('change',paint));document.querySelectorAll('[data-width]').forEach(b=>b.addEventListener('click',()=>{{frame.style.width=b.dataset.width;document.querySelectorAll('[data-width]').forEach(x=>x.setAttribute('aria-pressed',String(x===b)));}}));paint();</script></body></html>'''
|
||||
OUT.write_text(base,encoding='utf-8');print('CREATED',OUT,OUT.stat().st_size)
|
||||
@@ -6,7 +6,7 @@ import sys
|
||||
ROOT=Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0,str(ROOT))
|
||||
from app.version import VERSION
|
||||
assert VERSION=='8.33.1-dev.1'
|
||||
assert VERSION=='8.34.1-dev.1'
|
||||
api=(ROOT/'app'/'main.py').read_text(encoding='utf-8')
|
||||
assert 'return builder.build(' not in api, 'A synchronous request-thread build has reappeared'
|
||||
assert "@app.post('/api/sites/{site_id}/builds', status_code=202)" in api
|
||||
|
||||
Reference in New Issue
Block a user