Files
preland/admin/tests/ui-utm-presets.cjs
T

17 lines
6.6 KiB
JavaScript

/* Browser plugin not available; local disposable fixture, real HTTP/jobs and rendered UI. */
const {chromium}=require('playwright-core'),assert=require('node:assert/strict'),fs=require('node:fs');
(async()=>{const root=process.env.FIXTURE_ROOT||'/fixture',out=process.env.UI_OUTPUT||'/output',original=new Map(['a','b','c','d'].map(id=>[id,fs.readFileSync(root+'/'+id+'/config.json','utf8')]));const browser=await chromium.launch({args:['--no-sandbox']}),context=await browser.newContext({baseURL:process.env.ADMIN_URL||'http://host.docker.internal:18093',viewport:{width:1440,height:1000}}),page=await context.newPage(),errors=[];page.on('pageerror',e=>errors.push(e.message));page.on('dialog',d=>d.dismiss());page.setDefaultTimeout(12000);let csrf;const created=[];
const login=async()=>{await page.getByLabel('Логин',{exact:true}).fill('admin');await page.getByLabel('Пароль',{exact:true}).fill('test-password');await page.getByRole('button',{name:'Войти',exact:true}).click();await page.locator('#workspace').waitFor();await page.waitForFunction(()=>[...document.querySelectorAll('.matrix-status')].every(e=>!e.textContent.includes('Чтение')));csrf=(await page.request.get('/api/state').then(r=>r.json())).csrf;};
const read=id=>JSON.parse(fs.readFileSync(root+'/'+id+'/config.json','utf8'));const weight=(id,n)=>page.getByLabel('theme вес '+n+' '+id,{exact:true});const name='UI набор '+Date.now();
try{await page.goto('/');await login();await weight('a','1').fill('80');await weight('a','2').fill('20');await page.getByLabel('Основной сайт a',{exact:true}).fill('invalid host /');await page.getByRole('button',{name:'Сохранить UTM-набор a.invalid',exact:true}).click();await page.getByLabel('Название набора',{exact:true}).fill(' ');await page.locator('#preset-save').click();await page.locator('#preset-error').getByText(/Укажите название/).waitFor();await page.getByLabel('Название набора',{exact:true}).fill(name);await page.locator('#preset-save').click();await page.locator('#preset-dialog').waitFor({state:'hidden'});const id=await page.locator('#copy-source').inputValue();created.push(id);assert.equal(fs.readFileSync(root+'/a/config.json','utf8'),original.get('a'),'saving draft does not publish');
await page.getByRole('button',{name:'Сохранить UTM-набор a.invalid',exact:true}).click();await page.getByLabel('Название набора',{exact:true}).fill(name);await page.locator('#preset-save').click();await page.locator('#preset-error').getByText(/уже существует/).waitFor();assert.equal(await page.locator('#preset-name').inputValue(),name);await page.locator('#preset-cancel').click();
await page.getByRole('button',{name:'Отменить настройки a',exact:true}).click();for(const target of ['b','c'])await page.getByLabel('Копировать в '+target+'.invalid',{exact:true}).check();await page.locator('#copy-submit').click();await page.waitForFunction(()=>document.querySelectorAll('#copy-results .status-success').length===2,null,{timeout:60000});assert.equal(read('b').utm.theme.weights.dark,80);assert.equal(read('c').utm.theme.weights['1'],80);assert.equal(read('b').host,JSON.parse(original.get('b')).host);assert.equal(fs.readFileSync(root+'/d/config.json','utf8'),original.get('d'));await page.locator('#copy-panel').screenshot({path:out+'/presets-copy.png'});
await page.reload();await page.locator('#copy-source option[value="'+id+'"]').waitFor({state:'attached'});await page.locator('#copy-source').selectOption(id);assert.equal(await page.getByLabel('Копировать в a.invalid',{exact:true}).count(),1,'origin has no special status');
// A pending list refresh must disable deletion, then recover.
let release,seen;const gate=new Promise(r=>release=r),captured=new Promise(r=>seen=r);await page.route('**/api/utm-presets',async route=>{if(route.request().method()!=='GET')return route.continue();const response=await route.fetch();seen();await gate;await route.fulfill({response});});await page.locator('#preset-reload').click();await captured;assert.equal(await page.locator('#preset-delete').isDisabled(),true,'delete blocked while list GET pending');release();await page.waitForFunction(()=>!document.querySelector('#preset-reload').disabled);await page.unroute('**/api/utm-presets');
// 401 during save must not leave controls disabled after re-login.
await page.route('**/api/utm-presets',async route=>route.request().method()==='POST'?route.fulfill({status:401,contentType:'application/json',body:JSON.stringify({ok:false,error:'Истекла сессия теста'})}):route.continue());await page.getByRole('button',{name:'Сохранить UTM-набор a.invalid',exact:true}).click();await page.locator('#preset-name').fill(name+' session');await page.locator('#preset-save').click();await page.locator('#login-panel').waitFor();await page.unroute('**/api/utm-presets');await login();await page.getByRole('button',{name:'Сохранить UTM-набор a.invalid',exact:true}).click();assert.equal(await page.locator('#preset-name').isEnabled(),true,'new session resets preset form');assert.equal(await page.locator('#preset-save').isEnabled(),true);assert.equal(await page.locator('#preset-cancel').isEnabled(),true);await page.locator('#preset-cancel').click();
await page.locator('#copy-source').selectOption(id);await page.locator('#preset-delete').click();assert.equal(await page.locator('#copy-source option[value="'+id+'"]').count(),1,'cancel deletion leaves preset');const deleted=await page.request.delete('/api/utm-presets',{headers:{'X-CSRF-Token':csrf},data:{id}});assert.equal(deleted.ok(),true);await page.locator('#preset-reload').click();await page.locator('#copy-source option[value="'+id+'"]').waitFor({state:'detached'});assert.equal(read('b').utm.theme.weights.dark,80,'deleting preset does not revert recipient');
await page.setViewportSize({width:390,height:844});await page.locator('#copy-panel').screenshot({path:out+'/presets-mobile.png'});assert.equal(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth),true);assert.deepEqual(errors,[]);console.log('PASS preset draft/name/duplicate/apply2/host preservation/reload/any recipient/delete cancel+API/list race/session recovery/mobile');
}catch(error){console.error('UI evidence',await page.locator('#copy-panel').innerText(),errors);await page.screenshot({path:out+'/presets-failure.png'});throw error;}finally{for(const id of created)if(csrf)await page.request.delete('/api/utm-presets',{headers:{'X-CSRF-Token':csrf},data:{id}}).catch(()=>{});await browser.close();for(const [id,raw]of original)fs.writeFileSync(root+'/'+id+'/config.json',raw);}})().catch(e=>{console.error(e);process.exit(1)});