Setting the file. One moment.
Check Mirror · Wix Harmony Editor · wix/skills · Skills Docs
ContentsBack to the top of the page scripts/copy/ check-mirror.mjs
JavaScript · 89 lines · 7 KB
14 // `diffOutsideMediaPct` ignores those regions and is the figure the 0.1% gate applies to.
15 import { chromium } from 'playwright' ;
16 import fs from 'node:fs' ;
17
18 const args = process.argv. slice ( 2 );
19 const opt = ( n , d ) => { const i = args. indexOf ( `--${ n }` ); return i >= 0 ? args[i + 1 ] : d; };
20 const [ outDir , shots ] = args;
21 const port = Number ( opt ( 'port' , 8732 ));
22 const widths = opt ( 'widths' , '390,1024,1280,1920' ). split ( ',' ). map (Number);
23 const index = JSON . parse (fs. readFileSync ( `${ outDir }/index.json` , 'utf8' ));
24 const pick = opt ( 'pages' , null );
25 const pages = pick ? pick. split ( ',' ). map (( i ) => index.pages[ Number (i)]) : index.pages;
26 fs. mkdirSync (shots, { recursive: true });
27 const local = ( u ) => { const x = new URL (u); return `http://127.0.0.1:${ port }${ x . pathname }${ x . search }` ; };
28 const freeze = '*,*::before,*::after{animation:none!important;transition:none!important;caret-color:transparent!important}' ;
29 const slug = ( u ) => { const x = new URL (u); return `${ x . pathname }${ x . search }` . replace ( / [ ^ \w] + / g , '-' ). replace ( / ^ - | - $ / g , '' ) || 'home' ; }; // path AND query, so path-only pages do not share a name
30
31 const served = await fetch ( `http://127.0.0.1:${ port }/__mirror-site` ). then (( r ) => r.ok ? r. text () : '' , () => '' );
32 if (served !== index.site) { console. error ( `port ${ port } serves ${ served ? `"${ served }"` : 'nothing'}, not "${ index . site }". Start serve-mirror.mjs for this mirror on a free --port and pass the same --port here.` ); process. exit ( 2 ); }
33 const browser = await chromium. launch ();
34 async function shot ( url , width , file , offline ) {
35 const ctx = await browser. newContext ({ viewport: { width, height: 900 } });
36 const leaks = [], misses = [];
37 if (offline) await ctx. route ( '**/*' , ( r ) => { const u = r. request (). url (); if (u. startsWith ( `http://127.0.0.1:${ port }/` )) return r. continue (); leaks. push (u); return r. abort (); });
38 const page = await ctx. newPage ();
39 page. on ( 'response' , ( r ) => { if (offline && r. status () === 404 ) misses. push (r. url ()); });
40 await page. goto (url, { waitUntil: 'networkidle' , timeout: 90000 }). catch (() => {});
41 await page. addStyleTag ({ content: freeze });
42 const h = await page. evaluate (() => document.documentElement.scrollHeight);
43 for ( let y = 0 ; y <= h; y += 600 ) { await page. evaluate (( y ) => scrollTo ( 0 , y), y); await page. waitForTimeout ( 80 ); }
44 await page. evaluate (() => scrollTo ( 0 , 0 ));
45 await page. waitForLoadState ( 'networkidle' ). catch (() => {});
46 await page. waitForTimeout ( 1200 );
47 await page. screenshot ({ path: file, fullPage: true });
48 const media = await page. evaluate (() => [ ... document. querySelectorAll ( 'video, iframe, canvas, [class*="vidyard" i]' )]. map (( e ) => { const r = e. getBoundingClientRect (); return [Math. round (r.left + scrollX), Math. round (r.top + scrollY), Math. round (r.width), Math. round (r.height)]; }). filter (( r ) => r[ 2 ] > 40 && r[ 3 ] > 40 ));
49 const height = await page. evaluate (() => document.documentElement.scrollHeight);
50 await ctx. close ();
51 return { height, media, leaks: [ ...new Set (leaks. map (( u ) => new URL (u).host))], misses };
52 }
53 // pixel comparison in a blank page: draw both PNGs, count channel differences, paint the diff
54 const cmpPage = await browser. newPage ();
55 async function compare ( a , b , out , media = []) {
56 const r = await cmpPage. evaluate ( async ([ a , b , media ]) => {
57 const load = ( d ) => new Promise (( ok ) => { const i = new Image (); i. onload = () => ok (i); i.src = d; });
58 const [ ia , ib ] = await Promise . all ([ load (a), load (b)]);
59 const w = Math. max (ia.width, ib.width), h = Math. max (ia.height, ib.height);
60 const px = ( img ) => { const c = new OffscreenCanvas (w, h); const x = c. getContext ( '2d' ); x.fillStyle = '#fff' ; x. fillRect ( 0 , 0 , w, h); x. drawImage (img, 0 , 0 ); return x. getImageData ( 0 , 0 , w, h); };
61 const da = px (ia), db = px (ib); const c = new OffscreenCanvas (w, h); const x = c. getContext ( '2d' ); const out = x. createImageData (w, h);
62 let diff = 0 , outside = 0 ;
63 const masked = new Uint8Array (w * h);
64 for ( const [ mx , my , mw , mh ] of media) for ( let yy = Math. max ( 0 , my); yy < Math. min (h, my + mh); yy ++ ) masked. fill ( 1 , yy * w + Math. max ( 0 , mx), yy * w + Math. min (w, mx + mw));
65 for ( let i = 0 ; i < da.data. length ; i += 4 ) {
66 const d = Math. max (Math. abs (da.data[i] - db.data[i]), Math. abs (da.data[i + 1 ] - db.data[i + 1 ]), Math. abs (da.data[i + 2 ] - db.data[i + 2 ])) > 40 ;
67 if (d) { diff ++ ; if ( ! masked[i / 4 ]) outside ++ ; }
68 const g = (da.data[i] + da.data[i + 1 ] + da.data[i + 2 ]) / 3 ;
69 out.data[i] = d ? 255 : g * 0.3 + 178 ; out.data[i + 1 ] = d ? 0 : g * 0.3 + 178 ; out.data[i + 2 ] = d ? 0 : g * 0.3 + 178 ; out.data[i + 3 ] = 255 ;
70 }
71 x. putImageData (out, 0 , 0 );
72 const blob = await c. convertToBlob (); const buf = new Uint8Array ( await blob. arrayBuffer ());
73 let s = '' ; for ( let i = 0 ; i < buf. length ; i += 0x8000 ) s += String. fromCharCode ( ... buf. subarray (i, i + 0x8000 ));
74 return { pct: Math. round ((diff / (w * h)) * 10000 ) / 100 , outside: Math. round ((outside / (w * h)) * 10000 ) / 100 , png: btoa (s) };
75 }, [ ... [a, b]. map (( f ) => `data:image/png;base64,${ fs . readFileSync ( f ). toString ( 'base64' ) }` ), media]);
76 fs. writeFileSync (out, Buffer. from (r.png, 'base64' ));
77 return { pct: r.pct, outside: r.outside };
78 }
79 const rows = [];
80 for ( const url of pages) for ( const w of widths) {
81 const s = slug (url);
82 const live = await shot (url, w, `${ shots }/live-${ s }-${ w }.png` , false );
83 const loc = await shot ( local (url), w, `${ shots }/local-${ s }-${ w }.png` , true );
84 const cmp = await compare ( `${ shots }/live-${ s }-${ w }.png` , `${ shots }/local-${ s }-${ w }.png` , `${ shots }/diff-${ s }-${ w }.png` , live.media);
85 rows. push ({ page: s, width: w, liveH: live.height, localH: loc.height, diffPct: cmp.pct, diffOutsideMediaPct: cmp.outside, mediaRegions: live.media. length , leaks: loc.leaks, misses: loc.misses. length });
86 console. log ( JSON . stringify (rows. at ( - 1 )));
87 }
88 await browser. close ();
89 fs. writeFileSync ( `${ shots }/report.json` , JSON . stringify (rows, null , 1 ));