import assert from 'node:assert/strict'; import fs from 'node:fs/promises'; import {createHash} from 'node:crypto'; import {chromium,expect} from '@playwright/test'; import {trainingInit} from './training-catalog-fixture.mjs'; // Read only public application assets. Use synthetic SDK responses; no live // customer credentials, database reads, writes, or session termination. 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 files=['core/account-center-v1780.js','core/company-branding.js','core/sun-safe.js','app-runtime.js','index.html','service-worker.js']; for(const file of files){ const response=await fetch(new URL(file+'?employee_check='+Date.now(),base),{signal:AbortSignal.timeout(20000),headers:{'Cache-Control':'no-cache'}}); assert(response.ok,'Published asset unavailable: '+file); const actual=Buffer.from(await response.arrayBuffer()),expected=await fs.readFile('public/'+file); assert.equal(createHash('sha256').update(actual).digest('hex'),createHash('sha256').update(expected).digest('hex'),'Published revision differs: '+file); } const browser=await chromium.launch(); try{ const context=await browser.newContext({viewport:{width:390,height:844},isMobile:true,hasTouch:true,serviceWorkers:'block'}); await context.route('**/*',route=>{const req=route.request(),url=new URL(req.url());return req.method()==='GET'&&url.origin===base.origin&&!url.pathname.startsWith('/api/')?route.continue():route.abort()}); await context.addInitScript(trainingInit); const page=await context.newPage();await page.goto(base.href,{waitUntil:'domcontentloaded',timeout:60000}); await page.waitForFunction(()=>window.SunCloudV2?.getWorkspace()?.id&&window.CateriumAccountCenterV1780); await expect(page.locator('#sunCloudAuthGateV3')).toHaveCount(0,{timeout:20000}); await expect(page.locator('#cateriumMobileLogout')).toBeVisible();await expect(page.locator('#cateriumMobileLogout')).toHaveText('Выйти'); await expect(page.locator('#cateriumMobileAccountButton')).toBeVisible(); await page.evaluate(()=>{window.exitChecks=0;SunCloudV2.signOut=async()=>{exitChecks++};const c=SunCloudV2.getClient(),rpc=c.rpc;c.rpc=(n,a)=>n==='caterium_account_snapshot'?Promise.resolve({error:{message:'Synthetic offline'}}):rpc(n,a)}); await page.screenshot({path:output+'/employee-mobile-header.png'}); await page.locator('#cateriumMobileAccountButton').click(); await expect(page.locator('#cateriumAccountCenterBody')).toContainText('Выход из аккаунта доступен'); await expect(page.locator('#cacHeaderLogout')).toBeVisible();await page.locator('#cacHeaderLogout').click(); assert.equal(await page.evaluate(()=>exitChecks),1); await page.screenshot({path:output+'/employee-profile-offline.png'}); await page.locator('#cateriumAccountCenter .cac-close').click();await page.locator('#cateriumMobileLogout').click();assert.equal(await page.evaluate(()=>exitChecks),2); assert.equal(await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth+1),false); await fs.writeFile(output+'/employee-session.json',JSON.stringify({checkedAt:new Date().toISOString(),base:base.href,assetHashes:'match',mobileLogout:'pass',profileRpcFailure:'exit remains usable',backend:'synthetic, blocked',liveMembership:'not inspected',sqlMigration:'not deployed by frontend workflow'},null,2)); console.log('PASS published employee session: exact assets, visible mobile logout, profile unavailable fallback; synthetic backend only'); await context.close(); }finally{await browser.close()}