Files
Arena Agent cced3c9cd5 feat: prepare v1.4.0 gallery and LoRA workflow improvements
Protect unsaved edits and partial operation outcomes; improve source/library navigation and mobile density; stabilize virtual masonry near the bottom.

Add single-target LoRA stack append/replace, collapsed CLIP rows with preserved weights, and verified asynchronous delivery. Update docs, version metadata, CI checks, and production assets.
2026-09-27 04:05:47 +00:00

130 lines
8.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""RC2 interaction checks against isolated scripts/preview.py, not a real ComfyUI host.
Usage: python scripts/browser_rc2.py [base_url] [evidence_dir]
"""
import asyncio
import json
import sys
from pathlib import Path
from playwright.async_api import async_playwright, expect
BASE = sys.argv[1] if len(sys.argv) > 1 else 'http://127.0.0.1:8189'
OUT = Path(sys.argv[2] if len(sys.argv) > 2 else Path(__file__).resolve().parents[1] / 'artifacts' / 'rc2-evidence')
OUT.mkdir(parents=True, exist_ok=True)
def overlap(a, b):
return max(0, min(a['x']+a['width'],b['x']+b['width'])-max(a['x'],b['x'])) * max(0,min(a['y']+a['height'],b['y']+b['height'])-max(a['y'],b['y']))
async def main():
report = {'environment':'Isolated preview backend + Chromium; no real ComfyUI/GPU', 'tours':[], 'errors':[]}
async with async_playwright() as p:
browser = await p.chromium.launch()
for width,height in [(1440,960),(768,1024),(390,844),(320,568)]:
for locale in ['en','zh-CN']:
context = await browser.new_context(viewport={'width':width,'height':height}, reduced_motion='reduce' if width==320 else 'no-preference')
await context.add_init_script(f'localStorage.setItem("universal-extractor-locale",{json.dumps(locale)})')
page = await context.new_page()
page.on('pageerror', lambda e:report['errors'].append(str(e)))
await page.goto(BASE+'/gallery/',wait_until='domcontentloaded')
tour = page.locator('.ue-onboarding-card')
await expect(tour).to_be_visible()
await expect(page.locator('html')).to_have_attribute('lang',locale)
print('CASE',width,height,locale,flush=True)
case={'width':width,'height':height,'locale':locale,'steps':[]}
for index in range(11):
await expect(tour).to_have_attribute('data-step',['welcome','topbar','sidebar','gallery','density','selection','detail','library','workbench','settings','done'][index])
if index not in (0,10):
highlight = page.locator('.ue-onboarding-highlight')
await expect(highlight).to_be_visible(timeout=10000)
await page.wait_for_timeout(350)
box=await tour.bounding_box()
assert box and box['x']>=0 and box['y']>=0 and box['x']+box['width']<=width+1 and box['y']+box['height']<=height+1,(case,index,box)
assert await tour.evaluate('(e)=>e.contains(document.activeElement)'), ('focus escaped',width,locale,index)
assert await page.locator('#root').evaluate('(e)=>e.inert')
color=await page.locator('.ue-tour-next').evaluate('(e)=>({fg:getComputedStyle(e).color,bg:getComputedStyle(e).backgroundColor})')
assert color['fg']=='rgb(255, 255, 255)' and color['bg'] in ('rgb(33, 33, 33)','rgb(58, 58, 58)'),color
if index not in (0,10):
target=await highlight.bounding_box()
assert overlap(box,target)<=1,('card overlaps target',case,index,box,target)
case['steps'].append({'index':index,'card':box})
if width in (1440,390) and locale=='zh-CN' and index in (1,4,6):
await page.screenshot(animations='disabled',path=str(OUT/f'tour-{width}-step-{index}.png'))
if index==0:
# Modal keyboard loop in both directions.
await page.locator('.ue-tour-next').focus()
await page.keyboard.press('Tab')
assert await tour.evaluate('(e)=>e.contains(document.activeElement)')
await page.keyboard.press('Shift+Tab')
await page.locator('.ue-tour-next').click()
await expect(tour).to_have_count(0)
assert not await page.locator('#root').evaluate('(e)=>e.inert')
report['tours'].append(case)
# Menu text must remain visible at mobile sizes, not just accessible via title.
await page.locator('[data-tour-id="workspace-switcher"]').click()
menu=page.locator('.ue-workspace-switcher .ue-disclosure-panel')
await expect(menu).to_be_visible()
assert await menu.locator('small').count()==4
for description in await menu.locator('small').all():
await expect(description).to_be_visible()
assert (await description.inner_text()).strip()
if width in (1440,390) and locale=='zh-CN':
await page.screenshot(animations='disabled',path=str(OUT/f'workspace-menu-{width}.png'))
await menu.locator('button').first.click()
await expect(page.locator('.ue-gallery-card').first).to_be_visible()
density=page.locator('[data-tour-id="gallery-density"]')
await density.click()
panel=page.locator('.ue-density-panel')
await expect(panel).to_be_visible()
pb=await panel.bounding_box()
assert pb['x']>=0 and pb['x']+pb['width']<=width+1
if width <= 560:
# P2 uses explicit touch-friendly mobile presets in place of
# the desktop 3–8 column range; report effective, not requested, columns.
presets = panel.locator('.ue-density-presets--mobile button')
await expect(presets).to_have_count(3)
await presets.nth(1).click()
await expect(presets.nth(1)).to_have_attribute('aria-pressed', 'true')
await expect(panel.locator('.ue-density-note')).to_contain_text(
'Currently 2 per row' if locale == 'en' and width == 390 else
'当前实际每行 2 列' if locale == 'zh-CN' and width == 390 else
'Currently 1 per row' if locale == 'en' else '当前实际每行 1 列'
)
else:
await panel.locator('.ue-density-presets button').last.click()
assert await panel.locator('input').input_value()=='6'
await panel.locator('input').focus()
await page.keyboard.press('ArrowRight')
assert await panel.locator('input').input_value()=='7'
if width in (1440,390) and locale=='zh-CN':
await page.screenshot(animations='disabled',path=str(OUT/f'density-{width}.png'))
await page.keyboard.press('Escape')
await expect(panel).to_have_count(0)
await expect(density).to_be_focused()
if width in (1440,320) and locale=='en':
card=page.locator('.ue-gallery-card').first
await card.dblclick()
await expect(page.locator('.ue-lightbox-media')).to_be_visible()
await expect(page.locator('.ue-lightbox-loading')).to_have_count(0,timeout=20000)
assert await page.locator('.ue-modal-backdrop--lightbox').evaluate('(e)=>getComputedStyle(e).transform')=='none'
await expect(page.locator('.ue-lightbox-media img')).to_be_visible()
await expect(page.locator('.ue-modal-backdrop--lightbox')).to_have_css('opacity','1')
await expect(page.locator('.ue-modal-backdrop--lightbox')).to_have_css('background-color','rgba(18, 18, 18, 0.97)')
await page.screenshot(animations='disabled',path=str(OUT/f'lightbox-{width}.png'))
await page.locator('.ue-lightbox-media img').dblclick()
await expect(page.locator('.ue-lightbox-media img')).to_have_css('--ue-zoom','2')
if width==320:
assert await tour.count()==0
assert await page.locator('.ue-lightbox-media img').evaluate('(e)=>getComputedStyle(e).transitionDuration')=='0s'
await page.keyboard.press('ArrowRight')
await expect(page.locator('.ue-lightbox-loading')).to_have_count(0,timeout=20000)
await page.keyboard.press('Escape')
await expect(page.locator('.ue-lightbox-media')).to_have_count(0)
await context.close()
await browser.close()
assert not report['errors'], report['errors']
(OUT/'browser-rc2.json').write_text(json.dumps(report,ensure_ascii=False,indent=2))
print(f'PASS: {len(report["tours"])} full first-run tours, 88 steps; workspace descriptions, density keyboard/presets, focus/inert, both locales, mobile/reduced motion, lightbox. Evidence: {OUT}')
asyncio.run(main())