From 937ac68862d19de8fb3ea450e97e552954ebfdf4 Mon Sep 17 00:00:00 2001 From: Opus Date: Fri, 24 Apr 2026 15:31:12 +0200 Subject: [PATCH] doctrine 193: ux-audit-mobile-banner.js Playwright iPhone12 viewport audit script - detect bottom-right overlaps (bot widget vs injected banners) - 4 pages scanned / 1 overlap found leadforge.html - proofs public URL --- ops-scripts/ux-audit-mobile-banner.js | 135 ++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 ops-scripts/ux-audit-mobile-banner.js diff --git a/ops-scripts/ux-audit-mobile-banner.js b/ops-scripts/ux-audit-mobile-banner.js new file mode 100644 index 000000000..fa6262721 --- /dev/null +++ b/ops-scripts/ux-audit-mobile-banner.js @@ -0,0 +1,135 @@ +// UX Audit Mobile Banner Overlap — doctrine Yacine "banner CTA chevauche Brain+WhatsApp bottom-right sur mobile" +// Crée par Opus session v1.9 — cible pages publiques + mobile viewport +const { chromium, devices } = require('playwright'); +const fs = require('fs'); +const path = require('path'); + +(async () => { + const out = '/var/www/html/proofs/ux-audit-mobile-banner-' + new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); + fs.mkdirSync(out, { recursive: true }); + const logFile = path.join(out, 'audit.log'); + const log = (m) => { console.log(m); fs.appendFileSync(logFile, m + '\n'); }; + fs.writeFileSync(logFile, ''); + + log('=== UX AUDIT MOBILE BANNER OVERLAP ==='); + log('started: ' + new Date().toISOString()); + + const b = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] }); + // iPhone 12 viewport: 390x844 + const c = await b.newContext({ + ...devices['iPhone 12'], + recordVideo: { dir: out, size: { width: 390, height: 844 } } + }); + const p = await c.newPage(); + + const pages = [ + { url: 'https://weval-consulting.com/', name: 'landing-index', slug: 'index' }, + { url: 'https://weval-consulting.com/products/consulting.html', name: 'products-consulting', slug: 'consulting' }, + { url: 'https://weval-consulting.com/products/leadforge.html', name: 'products-leadforge', slug: 'leadforge' }, + { url: 'https://weval-consulting.com/products/academy-elearning-v2.html', name: 'products-academy', slug: 'academy' } + ]; + + const report = { mobile_viewport: '390x844 iPhone12', pages: [], total_overlaps: 0, cta_detected: 0 }; + + for (const target of pages) { + log(`--- scanning ${target.name} (${target.url}) ---`); + const pageReport = { name: target.name, url: target.url, overlaps: [], cta_text: null, bottom_right_elements: 0 }; + try { + await p.goto(target.url, { waitUntil: 'domcontentloaded', timeout: 30000 }); + await p.waitForTimeout(3500); // let widget/brain/whatsapp inject + + // Full page screenshot + await p.screenshot({ path: path.join(out, `${target.slug}-full.png`), fullPage: false }); + + // Zoom bottom-right (viewport 0 - last 250px h, last 200px w) + await p.screenshot({ + path: path.join(out, `${target.slug}-bottomright-zoom.png`), + clip: { x: 390 - 200, y: 844 - 250, width: 200, height: 250 } + }); + + // Detect all fixed/sticky positioned elements + const fixedEls = await p.evaluate(() => { + const all = document.querySelectorAll('*'); + const fixed = []; + for (const el of all) { + const s = getComputedStyle(el); + if ((s.position === 'fixed' || s.position === 'sticky') && s.display !== 'none' && s.visibility !== 'hidden' && parseFloat(s.opacity) > 0.1) { + const r = el.getBoundingClientRect(); + if (r.width > 0 && r.height > 0 && r.width < 500 && r.height < 500) { + fixed.push({ + tag: el.tagName, + id: el.id || '', + cls: (el.className || '').toString().slice(0, 100), + pos: { + top: Math.round(r.top), + left: Math.round(r.left), + right: Math.round(window.innerWidth - r.right), + bottom: Math.round(window.innerHeight - r.bottom), + w: Math.round(r.width), + h: Math.round(r.height) + }, + zIndex: s.zIndex, + text: (el.textContent || '').trim().slice(0, 60) + }); + } + } + } + return fixed; + }); + + log(` fixed/sticky: ${fixedEls.length}`); + + // Find CTA text ("Demander un devis", "Prendre RDV", "Devis") + const ctaEls = fixedEls.filter(e => + /demander.*devis|prendre.*rdv|contact|devis|rdv/i.test(e.text) + ); + if (ctaEls.length > 0) { + pageReport.cta_text = ctaEls[0].text; + report.cta_detected++; + log(` CTA found: "${ctaEls[0].text}" at pos ${JSON.stringify(ctaEls[0].pos)}`); + } + + // Bottom-right zone: right < 80, bottom < 200 + const br = fixedEls.filter(e => e.pos.right < 80 && e.pos.bottom < 200); + pageReport.bottom_right_elements = br.length; + log(` bottom-right elements: ${br.length}`); + for (const e of br) log(` - ${e.tag}#${e.id}.${e.cls.slice(0,40)} pos=${JSON.stringify(e.pos)} text="${e.text}"`); + + // Detect overlaps in bottom-right + for (let i = 0; i < br.length; i++) { + for (let j = i + 1; j < br.length; j++) { + const a = br[i].pos, b = br[j].pos; + const aL = a.left, aR = a.left + a.w, aT = a.top, aB = a.top + a.h; + const bL = b.left, bR = b.left + b.w, bT = b.top, bB = b.top + b.h; + if (aL < bR && aR > bL && aT < bB && aB > bT) { + const overlap = { + zone: 'bottom-right', + elemA: { tag: br[i].tag, id: br[i].id, text: br[i].text, pos: a }, + elemB: { tag: br[j].tag, id: br[j].id, text: br[j].text, pos: b } + }; + pageReport.overlaps.push(overlap); + report.total_overlaps++; + log(` 🔴 OVERLAP detected: [${br[i].id || br[i].tag}] <-> [${br[j].id || br[j].tag}]`); + } + } + } + + pageReport.all_fixed_els = fixedEls; + } catch (e) { + log(` ERROR: ${e.message}`); + pageReport.error = e.message; + } + report.pages.push(pageReport); + } + + // Save JSON report + fs.writeFileSync(path.join(out, 'report.json'), JSON.stringify(report, null, 2)); + log('=== SUMMARY ==='); + log(`total_overlaps: ${report.total_overlaps}`); + log(`cta_detected: ${report.cta_detected}`); + log(`output: ${out}`); + log(`public URL: https://weval-consulting.com/proofs/${path.basename(out)}/`); + + await c.close(); + await b.close(); +})().catch(e => { console.error('FATAL:', e.message); process.exit(1); });