caterium-app/tests/production-ui-smoke.mjs

116 lines
7.9 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import {chromium,expect} from '@playwright/test';
// Fetch real production assets, but use a synthetic SDK session and workspace.
// All backend traffic is blocked: this verifies published UI, not server login,
// and never reads or modifies a customer account or database.
const base=new URL(process.env.TIMEWEB_BASE_URL||'https://app.caterium.ru');
assert.equal(base.protocol,'https:');
const output='production-ui-results';
await fs.mkdir(output,{recursive:true});
const browser=await chromium.launch();
const results=[];
try{
for(const width of [1440,390]){
const context=await browser.newContext({viewport:{width,height:900},serviceWorkers:'block'});
try{
await context.route('**/*',route=>{
const request=route.request(),url=new URL(request.url());
if(request.method()==='GET'&&url.origin===base.origin&&!url.pathname.startsWith('/api/'))return route.continue();
return route.abort();
});
await context.addInitScript(()=>{
const workspaceId='ui-smoke-workspace';
localStorage.setItem('sunBoxes',JSON.stringify([{id:'ui-smoke-box',name:'Праздничный бокс',category:0,price:1000,ingredients:[],weight:'600 г',pieces:12}]));
localStorage.setItem('sunOrders',JSON.stringify(Array.from({length:24},(_,i)=>({id:i+1,contact:`Клиент ${String(i+1).padStart(2,'0')}`,phone:`+7 900 000 ${String(i).padStart(4,'0')}`,event:'Фуршет',address:'Адрес клиента',date:'2099-12-20',time:'12:00',status:'Новый',lines:[{id:'ui-smoke-box',qty:1,price:1000}],total:1000}))));
const user={id:'ui-smoke-user',email:'ui-smoke@example.invalid',email_confirmed_at:'2026-01-01T00:00:00Z'};
localStorage.setItem('sunCloudV2Config',JSON.stringify({workspaceId,localWorkspaceId:workspaceId,tenantStorageReady:true,autoSync:false}));
window.supabase={createClient:()=>({
auth:{onAuthStateChange:()=>({data:{subscription:{unsubscribe(){}}}}),getSession:async()=>({data:{session:{user}},error:null}),getUser:async()=>({data:{user},error:null})},
rpc:async name=>{
if(name==='sun_my_workspaces')return new Promise(resolve=>{window.finishUiSmokeWorkspace=()=>resolve({data:[{id:workspaceId,name:'UI verification',role:'admin',is_active:true,permissions:{}}],error:null});});
if(name==='sun_subscription_snapshot')return {data:{plan_id:'full',plan_name:'Full',status:'active',access_mode:'full',features:Object.fromEntries('orders calendar clients catalog_view catalog_edit production shopping stock routes mailings money stats_basic stats_advanced team suppliers print settings branding client_offers offer_templates backups audit users_manage'.split(' ').map(k=>[k,true]))},error:null};
if(name==='caterium_trial_demo_status')return {data:{canInstall:false,canUpgrade:false},error:null};
if(name==='sun_is_platform_admin')return {data:false,error:null};
return {data:null,error:null};
},
channel:()=>({on(){return this;},subscribe(){return this;}}),removeChannel(){}
})};
});
const page=await context.newPage();
await page.clock.setFixedTime(new Date());
await page.goto(base.href,{waitUntil:'domcontentloaded',timeout:60000});
const gate=page.locator('#sunCloudAuthGateV3');
await expect(gate).toHaveAttribute('data-auth-state','loading',{timeout:20000});
await expect(gate.locator('h2')).toHaveText('Загружаю рабочую базу');
await expect(gate.locator('button:visible,input:visible,a:visible')).toHaveCount(0);
await expect(gate.locator('.sun-cloud-auth-error')).toBeHidden();
await expect(page.locator('body > header')).toBeHidden();
await page.screenshot({path:`${output}/loading-${width}.png`});
await page.evaluate(()=>window.finishUiSmokeWorkspace());
await expect(gate).toHaveCount(0,{timeout:20000});
await expect(page.locator('body > header')).toBeVisible();
const help=page.locator('#ctHelpNav');
await expect(help).toHaveClass(/sun-nav-button/);
await help.scrollIntoViewIfNeeded();
let icon=null;
if(width>900){
await expect.poll(()=>help.evaluate(el=>getComputedStyle(el,'::before').maskImage||getComputedStyle(el,'::before').webkitMaskImage)).toContain('data:image/svg+xml');
icon=await help.evaluate(el=>{
const peer=document.querySelector('header nav .nav-settings');
const size=node=>{const s=getComputedStyle(node,'::before');return {width:s.width,height:s.height,marginRight:s.marginRight,background:s.backgroundColor};};
const s=getComputedStyle(el,'::before');
return {help:size(el),peer:size(peer),mask:s.maskImage||s.webkitMaskImage};
});
assert.deepEqual(icon.help,icon.peer);
assert.match(decodeURIComponent(icon.mask),/<circle[^>]*r="9"/);
assert.match(decodeURIComponent(icon.mask),/M12 17h\.01/);
await page.locator('body > header').screenshot({path:`${output}/sidebar-${width}.png`});
}
await help.click();
await expect(page.locator('#ctHelpDialog')).toBeVisible();
await expect(page.locator('#ctHelpTitle')).toHaveText('Помощь в Caterium');
await expect.poll(()=>page.locator('#ctHelpResults details').count()).toBeGreaterThan(0);
await page.locator('#ctHelpClose').click();
await expect(page.locator('#ctHelpDialog')).not.toBeVisible();
await page.waitForFunction(()=>window.SunOpsUXV1762&&window.CateriumDataV1773&&window.CateriumPricing);
const nav=page.locator('header nav');
await nav.getByRole('button',{name:'Клиенты',exact:true}).click();
const clients=page.locator('#client-list .client-card');
await expect(clients).toHaveCount(24);
const clientSize=await clients.first().boundingBox();
assert(clientSize.height<=96);
await page.screenshot({path:`${output}/clients-${width}.png`,animations:'disabled'});
await nav.getByRole('button',{name:'Меню',exact:true}).click();
await page.locator('[data-menu-item-v1762="ui-smoke-box"]').click();
await page.locator('#ctPromotionEnabled').check();
await page.locator('#ctDiscountType').selectOption('percent');
await page.locator('#ctDiscountValue').fill('15');
await expect(page.locator('#ctPromotionPreview')).toContainText('850 ₽');
await page.locator('#ctPromotionEditor').screenshot({path:`${output}/promotion-editor-${width}.png`});
await page.locator('#saveBoxButton').click();
const row=page.locator('[data-menu-item-v1762="ui-smoke-box"]');
await expect(row).toContainText('850 ₽');
await expect(row.locator('.ct-promotion-badge')).toHaveText('Акция');
// Saving normally redraws the menu list on a queued task. A viewport
// screenshot does not retain an element handle that may detach meanwhile.
// Keep the price/badge checks above and the expiry/order checks below.
await row.scrollIntoViewIfNeeded();
await page.screenshot({path:`${output}/promotion-card-${width}.png`,animations:'disabled'});
const beforeOrders=await page.evaluate(()=>localStorage.sunOrders);
const promo=await page.evaluate(()=>JSON.parse(localStorage.sunBoxes)[0]);
assert.equal(promo.price,1000);
assert.equal(Date.parse(promo.promotion.endsAt)-Date.parse(promo.promotion.startsAt),7*86400000);
await page.clock.setFixedTime(new Date(promo.promotion.endsAt));
await page.evaluate(()=>window.dispatchEvent(new Event('focus')));
await expect(row).toContainText('1 000 ₽');
await expect(row.locator('.ct-promotion-badge')).toHaveCount(0);
assert.equal(await page.evaluate(()=>localStorage.sunOrders),beforeOrders);
results.push({width,quietLoading:true,automaticOpen:true,helpDialog:true,icon,clientSize,clients:24,percentPromotion:true,automaticExpiry:true,savedOrdersUnchanged:true});
console.log(`PASS published UI ${width}px: quiet loading, automatic open, Help dialog, compact clients and timed discount${icon?', native question-circle icon':''}`);
}finally{await context.close();}
}
await fs.writeFile(`${output}/result.json`,JSON.stringify({base:base.href,checkedAt:new Date().toISOString(),backend:'mocked and network-blocked',results},null,2));
}finally{await browser.close();}