const {chromium} = require('playwright'); const assert = require('node:assert/strict'); const key = family => `image-model-collection:settings:v1:${family}`; (async () => { const browser = await chromium.launch({headless:true, ...(process.env.TEST_CHROME ? {executablePath:process.env.TEST_CHROME} : {})}); try { const page = await browser.newPage({viewport:{width:1360,height:1000}}); const errors = []; page.on('pageerror', error => errors.push(error.message)); const number = label => page.getByRole('spinbutton', {name:`number input for ${label}`, exact:true}); const stored = family => page.evaluate(k => JSON.parse(localStorage.getItem(k)), key(family)); const saved = (family, field, value) => page.waitForFunction(({key,field,value}) => { const data = JSON.parse(localStorage.getItem(key)); return data?.values?.[field] === value; }, {key:key(family),field,value}); async function setNumber(label, value) { await number(label).fill(String(value)); await number(label).press('Tab'); } async function choose(label, value) { await page.getByRole('combobox',{name:label,exact:true}).fill(value); await page.getByRole('option').filter({hasText:value}).first().click(); } async function settings() { const area = page.locator('.bs-settings').filter({visible:true}); if (!await area.evaluate(el => el.open)) await area.locator('summary').click(); return area; } await page.goto('http://127.0.0.1:7868'); await saved('anima','cfg',5); assert.equal(await page.locator('.bs-settings').first().evaluate(el => el.open),false); let area = await settings(); assert.equal(await area.locator('.bs-enabled').isChecked(),true); assert.equal(await area.locator('.bs-prompts').isChecked(),true); await page.getByRole('textbox',{name:'Prompt',exact:true}).fill('saved by default'); await page.getByRole('textbox',{name:'Negative prompt',exact:true}).fill('negative saved by default'); await saved('anima','prompt','saved by default'); await saved('anima','negative_prompt','negative saved by default'); await page.reload(); await page.locator('.bs-settings:visible .bs-enabled:not(:disabled)').waitFor({state:'attached'}); assert.equal(await page.getByRole('textbox',{name:'Prompt',exact:true}).inputValue(),'saved by default'); assert.equal(await page.getByRole('textbox',{name:'Negative prompt',exact:true}).inputValue(),'negative saved by default'); area = await settings(); await area.locator('.bs-prompts').uncheck(); console.log('PASS prompts saved by default and both prompts restored'); await page.getByRole('textbox',{name:'Prompt',exact:true}).fill('private unsaved prompt'); await page.getByRole('radio',{name:'非Turbo+Turbo LoRA',exact:true}).check(); await saved('anima','cfg',1); await setNumber('CFG',3.2); await setNumber('Steps',23); await setNumber('Width',1152); await choose('Anima Checkpoint','anima-checkpoint-2'); await page.getByRole('button',{name:'LoRA設定・追加モデル(最大4つ)'}).click(); await choose('LoRA 3 File','existing.safetensors'); await page.getByRole('checkbox',{name:'LoRA 3 Enable',exact:true}).check(); await saved('anima','lora3_enabled',true); await saved('anima','cfg',3.2); assert.equal(Object.hasOwn((await stored('anima')).values,'prompt'),false); await page.reload(); await page.locator('.bs-settings:visible .bs-enabled:not(:disabled)').waitFor({state:'attached'}); await saved('anima','cfg',3.2); assert.equal(await number('CFG').inputValue(),'3.2'); assert.equal(await number('Steps').inputValue(),'23'); assert.equal(await number('Width').inputValue(),'1152'); assert.equal(await page.getByRole('radio',{name:'非Turbo+Turbo LoRA',exact:true}).isChecked(),true); assert.equal(await page.getByRole('textbox',{name:'Prompt',exact:true}).inputValue(),'masterpiece, best quality,'); await page.getByRole('button',{name:'LoRA設定・追加モデル(最大4つ)'}).click(); assert.equal(await page.getByRole('checkbox',{name:'LoRA 3 Enable',exact:true}).isChecked(),true); console.log('PASS collapsed auto-restore, custom sampling/dimensions preserved, LoRA enabled, prompts excluded'); area = await settings(); await area.locator('.bs-prompts').check(); await page.getByRole('textbox',{name:'Prompt',exact:true}).fill(' saved as plain text'); await saved('anima','prompt',' saved as plain text'); await page.reload(); await page.waitForFunction(() => document.querySelector('#anima-positive-prompt textarea')?.value.includes('saved as plain text')); assert.equal(await page.locator('#anima-positive-prompt img').count(),0); area = await settings(); await area.locator('.bs-prompts').uncheck(); assert.equal(Object.hasOwn((await stored('anima')).values,'prompt'),false); await page.reload(); await page.locator('.bs-settings:visible .bs-enabled:not(:disabled)').waitFor({state:'attached'}); area = await settings(); assert.equal(await area.locator('.bs-prompts').isChecked(),false); console.log('PASS prompt opt-in, plain-text restoration, opt-out removes stored prompt'); await page.getByRole('tab',{name:'Illustrious',exact:true}).click(); await saved('illustrious','cfg',5); await choose('VAE','SDXL VAE'); await page.getByRole('checkbox',{name:'test-embedding',exact:true}).check(); await setNumber('CFG',4.1); await saved('illustrious','vae','sdxl'); await saved('illustrious','cfg',4.1); assert.equal((await stored('anima')).values.cfg,3.2); await page.reload(); await page.getByRole('tab',{name:'Illustrious',exact:true}).click(); await page.waitForFunction(() => [...document.querySelectorAll('input[aria-label="number input for CFG"]')].some(el => el.value==='4.1')); assert.equal(await number('CFG').inputValue(),'4.1'); assert.equal(await page.getByRole('combobox',{name:'VAE',exact:true}).inputValue(),'SDXL VAE'); assert.equal(await page.getByRole('checkbox',{name:'test-embedding',exact:true}).isChecked(),true); console.log('PASS independent family settings and Illustrious VAE'); area = await settings(); await area.locator('.bs-reset').click(); await saved('illustrious','cfg',5); assert.equal(await number('CFG').inputValue(),'5'); await area.locator('.bs-delete').click(); let record = await stored('illustrious'); assert.deepEqual(record,{version:1,family:'illustrious',enabled:false}); await setNumber('CFG',6); await page.waitForTimeout(900); assert.equal((await stored('illustrious')).values,undefined); await page.reload(); await page.getByRole('tab',{name:'Illustrious',exact:true}).click(); area = await settings(); await page.waitForFunction(() => [...document.querySelectorAll('.bs-enabled')].some(el => !el.disabled&&!el.checked)); assert.equal(await area.locator('.bs-enabled').isChecked(),false); assert.equal(await number('CFG').inputValue(),'5'); console.log('PASS reset defaults, deletion, disabled saving stays disabled on reload'); // Inject before app initialization; pagehide normally saves the current UI. await page.addInitScript(k => localStorage.setItem(k, JSON.stringify({version:1,family:'anima',enabled:true,prompts:false,values:{model:'../evil',cfg:999,width:99999,lora3_file:'../evil.safetensors',lora3_enabled:true,prompt:'must not restore'}})), key('anima')); await page.reload(); await page.locator('.bs-settings:visible .bs-enabled:not(:disabled)').waitFor({state:'attached'}); await saved('anima','cfg',5); record = await stored('anima'); assert.equal(record.values.model,'anima-checkpoint'); assert.equal(record.values.lora3_file,'---'); assert.equal(record.values.lora3_enabled,false); assert.equal(Object.hasOwn(record.values,'prompt'),false); assert.equal(await page.getByText('Received LoRA slots:',{exact:false}).count(),0); console.log('PASS modified browser data validated and restoration never generates'); const mobile = await browser.newPage({viewport:{width:390,height:844},isMobile:true,hasTouch:true}); mobile.on('pageerror', error => errors.push(error.message)); await mobile.goto('http://127.0.0.1:7868'); await mobile.locator('.bs-settings summary').click(); await mobile.locator('.bs-enabled:not(:disabled)').waitFor(); assert.equal(await mobile.evaluate(() => document.documentElement.scrollWidth<=innerWidth),true); assert.equal(await mobile.locator('.bs-prompts').isChecked(),true); await mobile.locator('.bs-prompts').check(); await mobile.getByRole('textbox',{name:'Prompt',exact:true}).fill('mobile prompt'); await mobile.waitForFunction(k => JSON.parse(localStorage.getItem(k))?.values?.prompt==='mobile prompt',key('anima')); await mobile.reload(); await mobile.waitForFunction(() => document.querySelector('#anima-positive-prompt textarea')?.value==='mobile prompt'); console.log('PASS mobile layout and restoration'); const blocked = await browser.newPage(); blocked.on('pageerror', error => errors.push(error.message)); await blocked.addInitScript(() => { const get = Storage.prototype.getItem, set = Storage.prototype.setItem; Storage.prototype.getItem = function(key) { if (key.startsWith('image-model-collection:settings:')) throw new Error('storage denied'); return get.call(this,key); }; Storage.prototype.setItem = function(key,value) { if (key.startsWith('image-model-collection:settings:')) throw new Error('storage denied'); return set.call(this,key,value); }; }); await blocked.goto('http://127.0.0.1:7868'); await blocked.locator('.bs-settings summary').click(); await blocked.getByText('ブラウザの保存領域を利用できません。現在の設定で生成はできます。',{exact:true}).waitFor(); assert.equal(await blocked.getByRole('button',{name:'Generate with Anima',exact:true}).isEnabled(),true); console.log('PASS unavailable browser storage does not prevent generation'); assert.deepEqual(errors,[]); } finally { await browser.close(); } })().catch(error => {console.error(error);process.exitCode=1;});