feat(admin): ручные варианты UTM и независимый редактор config (#30)

This commit is contained in:
Panfilov Konstantin committed 2026-10-26 05:42:03 +03:00
1 parent 47405b27d8
commit 941a4f01aa
28 files changed
+108 -174

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
/* preland#29: actual rendered read lifecycle and immutable UTM identifiers. Browser plugin not available. */
const {chromium}=require('playwright-core'),assert=require('node:assert/strict'),fs=require('node:fs');
(async()=>{const browser=await chromium.launch({executablePath:process.env.CHROMIUM_PATH,args:['--no-sandbox']}),page=await browser.newPage({baseURL:process.env.ADMIN_URL||'http://host.docker.internal:18093',viewport:{width:1440,height:1000}}),errors=[];page.on('pageerror',e=>errors.push(e.message));const header=id=>page.locator('[data-overview-target="'+id+'"]'),reload=id=>page.getByRole('button',{name:'Обновить настройки '+id,exact:true});const idle=()=>page.waitForFunction(()=>[...document.querySelectorAll('.matrix-status')].every(n=>!n.textContent.includes('Чтение')));try{await page.goto('/');assert.match(await page.title(),/Прелендинги/);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 idle();
assert.equal(await page.locator('[data-overview-utm] select').count(),0,'UTM numbers must be static');assert.equal(await page.locator('[data-overview-utm] button').count(),0,'UTM variants/groups cannot be added or removed');assert.match(await header('a').locator('.matrix-status').innerText(),/Прочитано с хостинга/);const date=header('a').locator('.matrix-read-at');assert.match(await date.innerText(),/Последнее чтение: .*\d{2}:\d{2}:\d{2} МСК/);const before=await date.innerText(),foreign=await header('b').locator('.matrix-read-at').innerText();let release,started;const gate=new Promise(r=>release=r),seen=new Promise(r=>started=r);const delay=async route=>{const response=await route.fetch();started();await gate;await route.fulfill({response});};await page.route('**/api/config?target=a',delay);await reload('a').click();await seen;assert.equal(await reload('a').isDisabled(),true);assert.match(await header('a').locator('.matrix-status').innerText(),/Чтение с хостинга/);assert.equal(await date.innerText(),before);await page.waitForTimeout(1100);release();await page.waitForFunction(()=>!document.querySelector('[data-overview-target=a] .matrix-status').textContent.includes('Чтение'));await page.unroute('**/api/config?target=a',delay);assert.equal(await reload('a').isEnabled(),true);assert.equal(await header('b').locator('.matrix-read-at').innerText(),foreign);const success=await date.innerText();
assert.equal(await page.locator('[data-overview-utm] select').count(),0,'UTM numbers must be static');assert.equal(await page.locator('[data-overview-utm] .matrix-remove').count(),0,'UTM variants cannot be removed');assert.equal(await page.locator('[data-overview-utm] .matrix-add-variant').count(),32,'manual add on every UTM/domain');assert.match(await header('a').locator('.matrix-status').innerText(),/Прочитано с хостинга/);const date=header('a').locator('.matrix-read-at');assert.match(await date.innerText(),/Последнее чтение: .*\d{2}:\d{2}:\d{2} МСК/);const before=await date.innerText(),foreign=await header('b').locator('.matrix-read-at').innerText();let release,started;const gate=new Promise(r=>release=r),seen=new Promise(r=>started=r);const delay=async route=>{const response=await route.fetch();started();await gate;await route.fulfill({response});};await page.route('**/api/config?target=a',delay);await reload('a').click();await seen;assert.equal(await reload('a').isDisabled(),true);assert.match(await header('a').locator('.matrix-status').innerText(),/Чтение с хостинга/);assert.equal(await date.innerText(),before);await page.waitForTimeout(1100);release();await page.waitForFunction(()=>!document.querySelector('[data-overview-target=a] .matrix-status').textContent.includes('Чтение'));await page.unroute('**/api/config?target=a',delay);assert.equal(await reload('a').isEnabled(),true);assert.equal(await header('b').locator('.matrix-read-at').innerText(),foreign);const success=await date.innerText();
const fail=route=>route.fulfill({status:503,contentType:'application/json',body:JSON.stringify({ok:false,error:'Ошибка чтения fixture #29'})});await page.route('**/api/config?target=a',fail);await reload('a').click();await header('a').getByText(/Ошибка чтения fixture/).waitFor();assert.equal(await date.innerText(),success);assert.equal(await reload('a').isEnabled(),true);await page.unroute('**/api/config?target=a',fail);await page.waitForTimeout(1100);await reload('a').click();await idle();assert.match(await header('a').locator('.matrix-status').innerText(),/Прочитано с хостинга/);assert.notEqual(await date.innerText(),success);
const host=page.getByLabel('Основной сайт a',{exact:true});await host.fill('draft-29.invalid');page.once('dialog',d=>d.dismiss());await reload('a').click();assert.equal(await host.inputValue(),'draft-29.invalid');page.once('dialog',d=>d.accept());await reload('a').click();await idle();assert.notEqual(await host.inputValue(),'draft-29.invalid');assert.deepEqual(errors,[]);await page.locator('#overview').screenshot({path:'/output/read-desktop.png'});await page.setViewportSize({width:390,height:844});assert.equal(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth),true);await page.locator('#overview').scrollIntoViewIfNeeded();await page.screenshot({path:'/output/read-mobile.png'});console.log('PASS static UTM / actual GET loading+disabled/date / error retains date / retry / foreign date / dirty dismiss+confirm / desktop/mobile/no JS errors');}finally{await browser.close();}})().catch(e=>{console.error(e);process.exit(1)});