Setting the file. One moment.
Preview Widths · Wix Harmony Editor · wix/skills · Skills Docs
ContentsBack to the top of the page scripts/copy/preview-widths.mjs
scripts/copy/ preview-widths.mjs
JavaScript · 73 lines · 7 KB
14 // (line-height, height, margin) that does not scale and will break other screen sizes.
15 import { chromium } from 'playwright' ;
16 import { hideScrollbars } from './hide-scrollbars.mjs' ;
17 import fs from 'node:fs' ;
18
19 const args = process.argv. slice ( 2 );
20 const opt = ( n , d ) => { const i = args. indexOf ( `--${ n }` ); return i >= 0 ? args[i + 1 ] : d; };
21 const [ sourceUrl , out ] = args. filter (( a , i ) => ! a. startsWith ( '--' ) && ! (i > 0 && args[i - 1 ]. startsWith ( '--' )));
22 const widths = String ( opt ( 'widths' , '1920,1440,1280,1024' )). split ( ',' ). map (Number);
23 const checkId = opt ( 'scale-check' , null );
24 if ( ! sourceUrl || ! out) { console. error ( 'usage: preview-widths.mjs <sourceUrl> <outDir> --widths a,b,c [--scale-check <sectionId>]' ); process. exit ( 2 ); }
25 fs. mkdirSync (out, { recursive: true });
26
27 const b = await chromium. connectOverCDP ( `http://127.0.0.1:${ process . env . PW_PORT || 9333 }` );
28 const ctx = b. contexts ()[ 0 ];
29 const p = b. contexts (). flatMap (( c ) => c. pages ()). find (( x ) => x. url (). includes ( 'editor.wix.com/edit/' ));
30 if ( ! p) { console. error ( 'No Harmony editor page is open.' ); process. exit ( 3 ); }
31 await hideScrollbars (p); // the Preview frame must not lose 15 px to a classic scrollbar
32 const pubBefore = await p. evaluate ( async () => { const a = await repluggableAppDebug.host. getAPI ({ name: 'RunEditorLLMCodeToolAPI' , public: true , layer: 'DATA_SERVICE' }); const r = await a. execute ({ code: 'return (await editorLLMApis.site.getStatus()).isPublished;' }, { biParams: {} }); return JSON . parse (r.items[ 0 ].content).result; });
33 if ( ! ( await p. getByText ( 'Edit Site' , { exact: true }). first (). isVisible (). catch (() => false ))) { await p. getByText ( 'Preview' , { exact: true }). first (). click (); await p. waitForTimeout ( 7000 ); }
34 const siteFrame = async () => { for ( const f of p. frames ()) { const ok = await f. evaluate (() => !! document. getElementById ( 'SITE_CONTAINER' )). catch (() => false ); if (ok) { const fe = await f. frameElement (). catch (() => null ); if (fe) return { f, box: await fe. boundingBox () }; } } return null ; };
35 const res = [], rects = {};
36 for ( const w of widths) {
37 await p. setViewportSize ({ width: Math. max (w + 84 , 1300 ), height: 1100 });
38 await p. waitForTimeout ( 1500 );
39 await p.mouse. move ( 200 , 20 ); await p. waitForTimeout ( 800 );
40 let tr = null ;
41 for ( let i = 0 ; i < 20 && ! tr; i ++ ) { tr = await p. evaluate (() => { const h = document. querySelector ( '[data-hook="slider-handle"]' ); if ( ! h) return null ; const r = h.parentElement. getBoundingClientRect (); return r.width ? { x: r.x, w: r.width, y: r.y + r.height / 2 } : null ; }); if ( ! tr) await p. waitForTimeout ( 500 ); }
42 if ( ! tr) throw new Error ( 'Preview width slider not found' );
43 const val = () => p. evaluate (() => Number (document. querySelector ( '[data-hook="slider-handle"]' ). getAttribute ( 'aria-valuenow' )));
44 const max = await p. evaluate (() => Number (document. querySelector ( '[data-hook="slider-handle"]' ). getAttribute ( 'aria-valuemax' )));
45 await p.mouse. click (tr.x + Math. max ( 1 , Math. min (tr.w - 1 , ((w - 390 ) / (max - 390 )) * tr.w)), tr.y);
46 await p. waitForTimeout ( 800 );
47 for ( let i = 0 ; i < 400 ; i ++ ) { const v = await val (); if (v === w) break ; await p.keyboard. press (v < w ? 'ArrowRight' : 'ArrowLeft' ); }
48 await p. waitForTimeout ( 3000 );
49 let fr = await siteFrame ();
50 const docH = await fr.f. evaluate (() => document.documentElement.scrollHeight);
51 if (checkId) rects[w] = await fr.f. evaluate (( id ) => { const s = document. getElementById (id); if ( ! s) return null ; const sr = s. getBoundingClientRect (); const o = { _section: [ 0 , 0 , sr.width, sr.height] }; for ( const e of s. querySelectorAll ( '[id^="comp-"]' )) { const q = e. getBoundingClientRect (); if (q.width && e.parentElement. closest ( '[id^="comp-"]' ) === s) o[e.id] = [q.x - sr.x, q.y - sr.y, q.width, q.height]; } return o; }, checkId);
52 await p. setViewportSize ({ width: Math. max (w + 84 , 1300 ), height: Math. min (Math. round (fr.box.y + docH + 20 ), 8000 ) });
53 await p. waitForTimeout ( 2500 );
54 fr = await siteFrame ();
55 await p. screenshot ({ path: `${ out }/h-${ w }.png` , clip: { x: fr.box.x, y: fr.box.y, width: fr.box.width, height: Math. min (docH, 8000 - fr.box.y) } });
56 res. push ({ w, slider: await val (), frameW: Math. round (fr.box.width), docH });
57 }
58 await p. getByText ( 'Edit Site' , { exact: true }). first (). click (). catch (() => {});
59 await p. waitForTimeout ( 2500 );
60 await p. setViewportSize ({ width: 1600 , height: 1000 });
61 const s = await ctx. newPage ();
62 // a local copy is compared OFFLINE: live third-party widgets (order popups, chat) would paint over it
63 if ( / ^ https ? : \/\/ (127 \. 0 \. 0 \. 1 | localhost) [:/] / . test (sourceUrl)) { const origin = new URL (sourceUrl).origin; await s. route ( '**/*' , ( r ) => (r. request (). url (). startsWith (origin) ? r. continue () : r. abort ())); }
64 for ( const w of widths) { await s. setViewportSize ({ width: w, height: 1000 }); await s. goto (sourceUrl, { waitUntil: 'networkidle' , timeout: 90000 }); await s. waitForTimeout ( 1200 ); await s. screenshot ({ path: `${ out }/s-${ w }.png` , fullPage: true }); }
65 await s. close ();
66 let scaleDeviations = null ;
67 if (checkId && widths. length > 1 && rects[widths[ 0 ]] && rects[widths[ 1 ]]) {
68 const [ a , c ] = widths, k = c / a; scaleDeviations = [];
69 for ( const id of Object. keys (rects[a])) { const A = rects[a][id], C = rects[c][id]; if ( ! C ) continue ; const d = [ C [ 0 ] - A [ 0 ] * k, C [ 1 ] - A [ 1 ] * k, C [ 2 ] - A [ 2 ] * k, C [ 3 ] - A [ 3 ] * k]. map (Math.round); if (d. some (( v ) => Math. abs (v) > 3 )) scaleDeviations. push ({ id, at: [a, A . map (Math.round)], [c]: C . map (Math.round), delta: d }); }
70 }
71 const pubAfter = await p. evaluate ( async () => { const a = await repluggableAppDebug.host. getAPI ({ name: 'RunEditorLLMCodeToolAPI' , public: true , layer: 'DATA_SERVICE' }); const r = await a. execute ({ code: 'return (await editorLLMApis.site.getStatus()).isPublished;' }, { biParams: {} }); return JSON . parse (r.items[ 0 ].content).result; });
72 console. log ( JSON . stringify ({ res, scaleDeviations, published: { before: pubBefore, after: pubAfter } }));
73 await b. close ();