caterium-app/tests/app.spec.mjs
pavlov346346-source 71d9cdc92e
Caterium v17.6.5 stability hardening
* Prepare v17.6.5 stability hardening patch

* Move v17.6.5 stability patch logic to script

* Fix v17.6.5 stability patch workflow

* Harden Caterium v17.6.5 stability

* Remove one-time v17.6.5 patch workflow

* Remove one-time v17.6.5 patch script

* Update static stability checks for v17.6.5

* Fix v17.6.5 E2E version expectation

* Update v17.6.5 E2E version expectation

* Remove one-time v17.6.5 test patch workflow

* Capture failing v17.6.5 E2E diagnostics

* Capture v17.6.5 E2E failures

* Make v17.6.5 stability E2E deterministic

* Make v17.6.5 E2E checks deterministic

* Remove one-time deterministic E2E workflow

* Remove one-time E2E diagnostic workflow

* Remove temporary E2E diagnostics

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-09-08 05:55:48 +03:00

193 lines
14 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import { test, expect } from '@playwright/test';
async function loadModule(page,file,globalName){
await page.goto('/index.html', { waitUntil:'domcontentloaded' });
const content=fs.readFileSync(path.join(process.cwd(),'public','core',file),'utf8');
await page.addScriptTag({content});
expect(await page.evaluate(name=>Boolean(window[name]),globalName)).toBeTruthy();
}
async function injectCore(page,file,globalName){
const content=fs.readFileSync(path.join(process.cwd(),'public','core',file),'utf8');
await page.addScriptTag({content});
if(globalName)expect(await page.evaluate(name=>Boolean(window[name]),globalName)).toBeTruthy();
}
test('legacy localStorage payload cannot execute XSS', async ({ page }) => {
const pageErrors=[]; page.on('pageerror',e=>pageErrors.push(String(e)));
await page.addInitScript(() => {
localStorage.setItem('sunBoxes', JSON.stringify([{id:'1',name:'<img src=x onerror="window.__cateriumXss=1">',price:100,photo:'javascript:alert(1)',ingredients:[['<svg onload="window.__cateriumXss=2">',1,'шт.']]}]));
localStorage.setItem('sunOrders', JSON.stringify([{id:1,event:'<img src=x onerror="window.__cateriumXss=3">',date:'2026-09-07',time:'12:00',address:'<svg onload="window.__cateriumXss=4">',status:'Новый',lines:[{id:'1',qty:1}]}]));
});
await page.goto('/index.html', { waitUntil:'domcontentloaded' }); await page.waitForTimeout(300);
expect(await page.evaluate(()=>window.__cateriumXss)).toBeUndefined();
expect(pageErrors.filter(x=>!x.includes('supabase'))).toEqual([]);
});
test('safe insert helper handles foreign reference node', async ({ page }) => {
await loadModule(page,'sun-safe.js','SunSafe');
const result=await page.evaluate(()=>{const a=document.createElement('div'),b=document.createElement('div'),n=document.createElement('span'),foreign=document.createElement('i');a.appendChild(foreign);document.body.append(a,b);try{window.SunSafe.insertBefore(b,n,foreign);return {ok:true,parent:n.parentNode===b}}catch(e){return {ok:false,error:String(e)}}});
expect(result).toEqual({ok:true,parent:true});
});
test('shared PDF engine produces A4 PDF blob', async ({ page }) => {
await loadModule(page,'pdf-engine.js','SunPdfEngine');
const result=await page.evaluate(async()=>{const fake=new Uint8Array([255,216,255,217]);const blob=window.SunPdfEngine.fromJpegs([{width:1000,height:1414,bytes:fake}]);const head=new TextDecoder().decode(new Uint8Array(await blob.arrayBuffer()).slice(0,8));return {type:blob.type,size:blob.size,head,w:window.SunPdfEngine.PAGE_W,h:window.SunPdfEngine.PAGE_H}});
expect(result.type).toBe('application/pdf'); expect(result.size).toBeGreaterThan(150); expect(result.head.startsWith('%PDF-1.4')).toBeTruthy(); expect(result.w/result.h).toBeCloseTo(1/Math.sqrt(2),3);
});
test('chat photo guard compresses a large camera image', async ({ page }) => {
await loadModule(page,'performance.js','SunAttachmentGuard');
const result=await page.evaluate(async()=>{
const c=document.createElement('canvas');c.width=3000;c.height=2200;const x=c.getContext('2d');
const g=x.createLinearGradient(0,0,c.width,c.height);g.addColorStop(0,'#172a3a');g.addColorStop(.5,'#d79a4a');g.addColorStop(1,'#f1e3ca');x.fillStyle=g;x.fillRect(0,0,c.width,c.height);
for(let i=0;i<1200;i++){x.fillStyle=`rgba(${i%255},${(i*7)%255},${(i*13)%255},.55)`;x.fillRect((i*31)%3000,(i*47)%2200,90,55)}
const blob=await new Promise(r=>c.toBlob(r,'image/jpeg',1));const input=new File([blob],'camera-photo.jpg',{type:'image/jpeg'});const out=await window.SunAttachmentGuard.prepareAttachment(input);const bm=await createImageBitmap(out.file);const data={original:input.size,size:out.file.size,width:bm.width,height:bm.height,type:out.file.type,name:out.file.name};bm.close();return data;
});
expect(Math.max(result.width,result.height)).toBeLessThanOrEqual(2048);expect(result.size).toBeLessThan(result.original);expect(result.size).toBeLessThanOrEqual(15*1024*1024);expect(result.type).toBe('image/jpeg');expect(result.name.endsWith('.jpg')).toBeTruthy();
});
test('v17.6.2 operations UX boots with menu and route features', async ({ page }) => {
await page.goto('/index.html', { waitUntil:'domcontentloaded' });
await page.evaluate(()=>{
if(!document.querySelector('header nav')){
const header=document.createElement('header');
const nav=document.createElement('nav');
header.appendChild(nav);
document.body.appendChild(header);
}
window.SunCloudV2={
getSupportMode:()=>null,
isSupportMode:()=>false,
hasPermission:()=>true,
getSession:()=>({user:{id:'e2e'}})
};
window.editBox=()=>{};
});
const content=fs.readFileSync(path.join(process.cwd(),'public','core','ops-ux-v1762.js'),'utf8');
await page.addScriptTag({content});
await page.waitForFunction(()=>Boolean(window.SunOpsUXV1762),null,{timeout:5000});
const checks=await page.evaluate(()=>window.SunOpsUXV1762.checks());
expect(checks.version).toBe('17.6.2');
expect(checks.menuPage).toBeTruthy();
expect(checks.routeStartKey).toBe('sunRouteBaseV1');
expect(checks.routeOrderPopup).toBeTruthy();
expect(checks.calendarMore).toBeTruthy();
expect(await page.locator('header nav button', {hasText:'Меню'}).count()).toBeGreaterThan(0);
});
test('v17.6.3 developer gate bypasses workspace loading and SaaS click is safe', async ({ page }) => {
await page.goto('/index.html', { waitUntil:'domcontentloaded' });
await page.evaluate(()=>{
document.body.innerHTML='<div id="sunCloudAuthGateV3"><div class="sun-cloud-auth-card"><h2>Загружаю рабочую базу</h2></div></div><button type="button" data-saas-admin>Управление SaaS</button>';
window.__devOpenArgs=[];
window.SunCloudV2={
getSession:()=>({user:{id:'dev',email:'developer@example.com'}}),
getWorkspace:()=>null,
status:()=>({membershipsLoading:true,membershipsLoaded:false}),
signOut:()=>{}
};
window.SunDeveloperV22={
open:arg=>{window.__devOpenArgs.push(arg===null?'null':arg?.constructor?.name||typeof arg)},
isPlatformAdmin:()=>true,
checkPlatformAdmin:async()=>true
};
});
const content=fs.readFileSync(path.join(process.cwd(),'public','core','hotfix-v1763.js'),'utf8');
await page.addScriptTag({content});
await page.waitForFunction(()=>document.querySelector('[data-open-dev-v1763]')&&Boolean(window.SunHotfixV1763),null,{timeout:5000});
expect(await page.locator('#sunCloudAuthGateV3 h2').textContent()).toBe('Аккаунт разработчика');
await page.locator('[data-saas-admin]').click();
await page.locator('[data-open-dev-v1763]').click();
const args=await page.evaluate(()=>window.__devOpenArgs);
expect(args).toEqual(['null','null']);
});
test('v17.6.4 auto-completes and fully pays an order one minute after scheduled time', async ({ page }) => {
await page.addInitScript(()=>{
localStorage.setItem('sunOrders',JSON.stringify([{id:764,event:'Тест авто-завершения',date:'2099-09-07',time:'12:00',status:'Новый',prepayment:100,total:1500,lines:[]}]))
});
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
await injectCore(page,'ux-fixes-v1764.js','SunUXFixV1764');
const result=await page.evaluate(()=>{
window.orderTotalValue=()=>1500;
const run=window.SunUXFixV1764.autoCompleteOrders(new Date('2099-09-07T12:01:01').getTime(),{silent:true});
const order=JSON.parse(localStorage.getItem('sunOrders')||'[]').find(x=>String(x.id)==='764');
return {run,prepayment:order?.prepayment,balance:order?.balance,status:order?.status,auto:Boolean(order?.sunAutoCompletedAt),classDate:window.SunUXFixV1764.classificationDate(order,'2099-09-07')};
});
expect(result.run.changed).toBe(1);
expect(result.prepayment).toBe(1500);
expect(result.balance).toBe(0);
expect(result.status).toBe('Отдан заказчику');
expect(result.auto).toBeTruthy();
expect(result.classDate).toBe('0001-01-01');
});
test('v17.6.4 keeps proposal designs per order and renders a styled menu icon', async ({ page }) => {
await page.addInitScript(()=>{
localStorage.setItem('sunOrders',JSON.stringify([
{id:801,event:'Editorial',date:'2099-09-08',time:'12:00',clientOfferSnapshot:{version:8,offerTemplateId:'editorial-grid'}},
{id:802,event:'Emerald',date:'2099-09-08',time:'13:00',clientOfferSnapshot:{version:8,offerTemplateId:'emerald-gold'}}
]));
});
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
await page.evaluate(()=>{
let nav=document.querySelector('header nav');
if(!nav){const header=document.createElement('header');nav=document.createElement('nav');header.appendChild(nav);document.body.prepend(header)}
let menu=nav.querySelector('.nav-menu');if(!menu){menu=document.createElement('button');menu.type='button';menu.className='sun-nav-button nav-menu';menu.dataset.navLabel='Меню';menu.textContent='Меню';nav.appendChild(menu)}
});
await injectCore(page,'ux-fixes-v1764.js','SunUXFixV1764');
await page.waitForFunction(()=>Boolean(document.querySelector('header nav .nav-menu .sun-v1764-menu-icon')),null,{timeout:5000});
const result=await page.evaluate(()=>{
window.SunUXFixV1764.persistOfferTemplate('801','midnight-glass');
const list=JSON.parse(localStorage.getItem('sunOrders')||'[]');
const one=list.find(x=>String(x.id)==='801'),two=list.find(x=>String(x.id)==='802');
return {one:one?.clientOfferTemplateId,snapOne:one?.clientOfferSnapshot?.offerTemplateId,two:two?.clientOfferSnapshot?.offerTemplateId,icon:Boolean(document.querySelector('header nav .nav-menu .sun-v1764-menu-icon'))};
});
expect(result.one).toBe('midnight-glass');
expect(result.snapOne).toBe('midnight-glass');
expect(result.two).toBe('emerald-gold');
expect(result.icon).toBeTruthy();
});
test('Developer Console memory refresh is bounded and does not react to its own DOM update', async ({ page }) => {
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
const performance=fs.readFileSync(path.join(process.cwd(),'public','core','performance.js'),'utf8');
await page.evaluate(()=>{
document.body.innerHTML='<section id="sun-developer-console-v22" class="on"><div id="sunDevBody"></div><span id="sunDevReleaseVersion"></span></section>';
window.__rpcCount=0;
window.SunCloudV2={getClient:()=>({rpc:async()=>{window.__rpcCount++;return {data:{server_size:'35 MB',database_size:'21 MB',storage_size:'14 MB',storage_objects:12}}}})};
});
await page.addScriptTag({content:performance});
await page.waitForFunction(()=>Boolean(window.SunPerformance),null,{timeout:5000});
await page.evaluate(async()=>{await window.SunPerformance.refreshDeveloperMemory(true);await new Promise(r=>setTimeout(r,250));});
const result=await page.evaluate(()=>({count:window.__rpcCount,box:document.querySelectorAll('#sunDevMemoryV1761').length,version:document.getElementById('sunDevReleaseVersion')?.textContent}));
expect(result.count).toBeLessThanOrEqual(2);
expect(result.box).toBe(1);
expect(result.version).toBe('17.6.5');
});
test('mobile body does not overflow viewport', async ({ page }, testInfo) => {
test.skip(testInfo.project.name!=='mobile-390'); await page.goto('/index.html', { waitUntil:'domcontentloaded' }); await page.waitForTimeout(500);
const dims=await page.evaluate(()=>({innerWidth,scrollWidth:document.documentElement.scrollWidth,bodyWidth:document.body.scrollWidth}));
expect(dims.scrollWidth).toBeLessThanOrEqual(dims.innerWidth+2); expect(dims.bodyWidth).toBeLessThanOrEqual(dims.innerWidth+2);
});
test('v17.6.5 stays free of timer page errors during idle', async ({ page }, testInfo) => {
test.skip(testInfo.project.name!=='desktop');
const errors=[];page.on('pageerror',e=>errors.push(String(e)));
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
await page.waitForTimeout(5500);
expect(errors).toEqual([]);
const runtimeSource=fs.readFileSync(path.join(process.cwd(),'public','app-runtime.js'),'utf8');
expect(runtimeSource).toContain("const VERSION = '17.6.5'");
});
test('v17.6.5 support refresh uses lightweight cloud API', async ({ page }) => {
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
await page.evaluate(()=>{try{window.SunOpsUXV1762?.disconnect?.()}catch(_){}document.getElementById('sunOpsUXV1762Script')?.remove();window.SunOpsUXV1762=undefined;window.__lightRefresh=0;window.__fullEnter=0;let nav=document.querySelector('header nav');if(!nav){const header=document.createElement('header');nav=document.createElement('nav');header.appendChild(nav);document.body.prepend(header)}window.SunCloudV2={getSupportMode:()=>({workspaceId:'support-test',name:'Тест'}),isSupportMode:()=>true,hasPermission:()=>false,getSession:()=>({user:{id:'dev'}}),refreshSupportWorkspace:async()=>{window.__lightRefresh++;return true},enterSupportWorkspace:async()=>{window.__fullEnter++;return true}};window.editBox=window.editBox||(()=>{});});
await injectCore(page,'ops-ux-v1762.js');
await page.waitForFunction(()=>Boolean(window.SunOpsUXV1762),null,{timeout:5000});
await page.evaluate(()=>window.SunOpsUXV1762.refreshSupport(true));
const counts=await page.evaluate(()=>({light:window.__lightRefresh,full:window.__fullEnter}));
expect(counts.light).toBe(1);expect(counts.full).toBe(0);
});
test('v17.6.5 developer hotfix does not poll admin every second', async ({ page }) => {
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
await page.evaluate(()=>{window.SunHotfixV1763=undefined;window.__devChecks=0;window.SunCloudV2={getSession:()=>({user:{id:'dev'}}),getWorkspace:()=>null};window.SunDeveloperV22={open:()=>{},isPlatformAdmin:()=>false,checkPlatformAdmin:async()=>{window.__devChecks++;return false}};});
await injectCore(page,'hotfix-v1763.js','SunHotfixV1763');
await page.waitForTimeout(2600);
expect(await page.evaluate(()=>window.__devChecks)).toBeLessThanOrEqual(2);
});