import { chromium } from '../tools/playwright/node_modules/playwright/index.mjs'; import assert from 'node:assert/strict'; const browser=await chromium.launch({headless:true}); try { for(const theme of ['dark','light']) { const page=await browser.newPage({viewport:{width:390,height:844},hasTouch:true,isMobile:true,serviceWorkers:'block'}); page.setDefaultTimeout(8000); await page.addInitScript(theme=>{localStorage.setItem('lazyboy.theme',theme);localStorage.setItem('lazyboy.locale','zh-Hant');localStorage.setItem('lazyboy.agent','beta')},theme); const errors=[];page.on('pageerror',e=>errors.push(e.message)); let opened=[],computers=0,globalActions=[]; page.on('dialog',dialog=>dialog.accept()); const activity={state:'idle',running:false,queued:false,active_task_ids:[],observed_at_ms:Date.now()}; const agents=[{id:'alpha',name:'小麻糬',avatar_color:'#edc585',preview:'今天想一起完成什麼?',last_at:'2026-09-16T08:00:00Z'},{id:'beta',name:'程式小幫手',avatar_color:'#a3cfef',preview:'剛才的修改已經完成。',last_at:'2026-09-16T07:55:00Z'}]; await page.route('**/api/**',route=>{ const path=new URL(route.request().url()).pathname; if(path==='/api/agents')return route.fulfill({json:{agents}}); if(path.endsWith('/activity'))return route.fulfill({json:activity}); if(path.endsWith('/events'))return route.fulfill({contentType:'text/event-stream',body:'data: {"events":[]}\n\n'}); if(path==='/api/computer'){globalActions.push(route.request().postDataJSON().action);return route.fulfill({json:{ready:false,error:'測試電腦回應'}})} if(path.endsWith('/computer')){computers++;return route.fulfill({json:{ready:false}})} const a=agents.find(a=>path===`/api/agents/${a.id}`); if(a){opened.push(a.id);return route.fulfill({json:{...a,activity,transcript:[{role:'assistant',content:`你好,我是${a.name}。`,at:'2026-09-16T08:00:00Z'}]}})} return route.fulfill({json:{}}); }); await page.goto('http://127.0.0.1:5173'); await page.getByRole('button',{name:/小麻糬 今天/}).waitFor(); assert.equal(await page.locator('.chat-panel').isVisible(),false); assert.deepEqual(opened,[],'remembered agent must not open on mobile');assert.equal(computers,0); const list=await page.locator('.sidebar').boundingBox();assert.equal(list.x,0);assert.equal(list.width,390);assert.equal(list.height,844); assert.equal(await page.locator('.mobile-nav-backdrop').count(),0); assert.equal(await page.locator('.brand-icon').count(),0,'phone header has no brand row'); const heading=await page.locator('.mobile-profile').boundingBox(),add=await page.locator('.brand .icon-button').boundingBox(); assert.ok(Math.abs(heading.y+heading.height/2-add.y-add.height/2)<1); await page.getByRole('button',{name:'設定',exact:true}).click(); const dialog=page.getByRole('dialog');await dialog.waitFor(); const settings=await dialog.boundingBox();assert.equal(settings.width,374);assert.equal(settings.height,740);assert.equal(settings.x,8); assert.equal(await page.locator('.lb-settings-nav').count(),0); await page.screenshot({path:`.run/mobile-settings-${theme}.png`}); await dialog.getByRole('button',{name:/電腦.*更新/}).click(); await dialog.getByRole('button',{name:'更新',exact:true}).click(); await dialog.getByText('測試電腦回應').waitFor(); await dialog.getByRole('button',{name:'重啟',exact:true}).click(); await page.waitForFunction(()=>!document.querySelector('.lb-settings-control button:disabled')); assert.deepEqual(globalActions,['update','restart']); await dialog.getByRole('button',{name:'設定',exact:true}).click(); await dialog.getByRole('button',{name:/外觀/}).click(); const themeOption=dialog.getByRole('radio',{name:theme==='dark'?'淺色':'深色',exact:true}); await themeOption.click();assert.equal(await themeOption.getAttribute('aria-checked'),'true'); assert.equal(await page.locator('html').getAttribute('data-theme'),theme==='dark'?'light':'dark'); await dialog.getByRole('radio',{name:theme==='dark'?'深色':'淺色',exact:true}).click(); await page.screenshot({path:`.run/mobile-theme-${theme}.png`}); await dialog.getByRole('button',{name:'設定',exact:true}).click(); await dialog.getByRole('button',{name:/語言/}).click(); await dialog.getByRole('radio',{name:'English',exact:true}).click(); await dialog.getByRole('button',{name:'Settings',exact:true}).click(); await dialog.getByRole('button',{name:/Language/}).click(); await dialog.getByRole('radio',{name:'繁體中文',exact:true}).click(); await dialog.getByRole('button',{name:'設定',exact:true}).click(); await dialog.getByRole('button',{name:/關於/}).click(); await page.locator('.mobile-about-page').waitFor(); await dialog.getByRole('button',{name:'設定',exact:true}).click(); await dialog.getByRole('button',{name:'返回聊天列表',exact:true}).click(); await dialog.waitFor({state:'hidden'}); await page.getByRole('button',{name:'設定',exact:true}).click(); await dialog.waitFor();await page.goBack();await dialog.waitFor({state:'hidden'}); await page.screenshot({path:`.run/mobile-inbox-${theme}.png`}); await page.getByRole('button',{name:/小麻糬 今天/}).click(); await page.locator('.messages').getByText('你好,我是小麻糬。').waitFor(); assert.equal(await page.locator('.sidebar').isVisible(),false); assert.equal(await page.locator('.chat-panel').isVisible(),true);assert.equal(computers,0,'chat selection must not boot a remote desktop'); await page.locator('.composer textarea').fill('保留中的草稿'); await page.screenshot({path:`.run/mobile-chat-${theme}.png`}); await page.getByRole('button',{name:'返回聊天列表'}).click(); await page.locator('.sidebar').waitFor({state:'visible'}); await page.getByRole('button',{name:'搜尋',exact:true}).click(); await page.getByRole('textbox',{name:'搜尋'}).fill('程式'); assert.equal(await page.locator('.bot-row').count(),1); await page.locator('.bot-row').click(); await page.locator('.messages').getByText('你好,我是程式小幫手。').waitFor(); await page.goBack();await page.locator('.sidebar').waitFor({state:'visible'}); await page.getByRole('textbox',{name:'搜尋'}).fill(''); await page.getByRole('button',{name:/小麻糬 今天/}).click(); await page.locator('.chat-panel').waitFor({state:'visible'}); await page.reload();await page.locator('.sidebar').waitFor({state:'visible'}); assert.equal(await page.locator('.chat-panel').isVisible(),false,'reload starts at list'); await page.setViewportSize({width:844,height:390}); assert.equal(await page.locator('.sidebar').isVisible(),true,'phone landscape keeps full-page list'); assert.equal((await page.locator('.sidebar').boundingBox()).width,844); await page.setViewportSize({width:1440,height:900}); await page.locator('.chat-panel').waitFor({state:'visible'}); assert.equal(await page.locator('.sidebar').isVisible(),true,'desktop keeps both panels'); await page.locator('.messages').getByText('你好,我是小麻糬。').waitFor(); assert.ok(computers>0); assert.deepEqual(errors,[]); console.log(`PASS ${theme}: full-page inbox, choose chat, UI/browser back, search, reload, rotation and desktop layout`); await page.close(); } }finally{await browser.close()}