SPB Git forge

spb/rent-ka

Public
8commits 1branches 0releases
7.4 MBsize
maindefault branch
19 days agolast push
Python 68.8% TypeScript 18.6% CSS 8.7% JavaScript 3.3% HTML 0.6%
15.6 KB · 351 lines javascript
Raw Blame History
1// -----------------------------------------------------------------------------2// Rent-Ka — Agrégateur de logements à louer (province de Québec)3// Auteur : Simon-Pierre Boucher — contact@spboucher.ai4// scripts/test-sync.mjs : validation E2E de la synchronisation liste ↔ carte5//   (critères d'acceptation 1-9 du chantier « sync légendaire »).6// Usage : node scripts/test-sync.mjs   (RENTKA_BASE pour cibler un déploiement)7// Captures : reports/sync-validation/*.png8// -----------------------------------------------------------------------------9import { chromium, devices } from 'playwright';10import { mkdirSync } from 'fs';1112const BASE = process.env.RENTKA_BASE || 'http://localhost:8125';13const OUT = new URL('../reports/sync-validation/', import.meta.url).pathname;14mkdirSync(OUT, { recursive: true });1516let passed = 0, failed = 0;17const results = [];18function check(name, ok, detail = '') {19  results.push({ name, ok, detail });20  if (ok) { passed++; console.log(`  ✓ ${name}${detail ? ` — ${detail}` : ''}`); }21  else { failed++; console.log(`  ✗ ${name}${detail ? ` — ${detail}` : ''}`); }22}2324const browser = await chromium.launch();2526const initScript = () => {27  localStorage.setItem('rentka-consent-v1',28    JSON.stringify({ essential: true, preferences: true, statistics: false, ts: 1 }));29  sessionStorage.setItem('rentka_geo_session', '1');  // pas de prompt géoloc ici30};3132async function newDesktop() {33  const ctx = await browser.newContext({34    viewport: { width: 1440, height: 900 }, locale: 'fr-CA',35  });36  await ctx.addInitScript(initScript);37  return ctx;38}3940const engineState = (page) => page.evaluate(() => {41  const m = globalThis.__kaMap;42  if (!m) return null;43  const st = m.getState();44  return { ...st, nProps: m.getState().activeLayers.length >= 045    ? (m.map.querySourceFeatures ? undefined : undefined) : undefined };46});4748const engineCount = (page) => page.evaluate(() => {49  const m = globalThis.__kaMap;50  // byId est privé : compter via les données GeoJSON poussées51  return m ? m['byId'].size : -1;52});5354const counterTotal = async (page) => {55  const txt = await page.locator('.ms-count b').first().textContent({ timeout: 15000 });56  return Number(txt.replace(/\s| |,/g, ''));57};5859const canvasBox = async (page) => {60  await page.locator('.mapview').scrollIntoViewIfNeeded();61  await page.waitForTimeout(400);62  return page.locator('.ka-map-canvas').boundingBox();63};6465const waitSettled = async (page, ms = 1200) => {66  await page.waitForTimeout(ms);67  await page.waitForFunction(() => !document.querySelector('.ms-progress'), null, { timeout: 20000 })68    .catch(() => {});69};7071// =============================== TESTS =====================================72console.log('— Vue carte desktop —');73{74  const ctx = await newDesktop();75  const page = await ctx.newPage();76  await page.goto(`${BASE}/?view=carte`, { waitUntil: 'domcontentloaded' });77  await page.waitForSelector('.ms-count b', { timeout: 30000 });78  await page.waitForFunction(() => {79    const m = globalThis.__kaMap;80    return m && m['byId'].size > 0;81  }, null, { timeout: 30000 });82  await waitSettled(page, 2500);8384  // C1 — compteur liste = points carte85  const total = await counterTotal(page);86  const nPts = await engineCount(page);87  check('C1 compteur liste = points carte (arrivée)', total === nPts, `${total} = ${nPts}`);88  await page.screenshot({ path: `${OUT}01-carte-desktop.png` });8990  // C2/C5 — déplacement carte → liste mise à jour, jamais de résultat périmé91  const urlBefore = page.url();92  const cb = await canvasBox(page);93  const mx = cb.x + cb.width / 2, my = cb.y + cb.height / 2;94  await page.mouse.move(mx, my);95  await page.mouse.down();96  await page.mouse.move(mx - 260, my - 60, { steps: 12 });97  await page.mouse.up();98  await waitSettled(page, 1800);99  const total2 = await counterTotal(page);100  const nPts2 = await engineCount(page);101  check('C2 déplacement → compteur/liste rafraîchis, égalité conservée',102    total2 === nPts2, `${total2} = ${nPts2}`);103  check('C7 caméra dans l’URL après déplacement',104    page.url() !== urlBefore && /lat=/.test(page.url()));105106  // rafale de mouvements rapides : la dernière réponse gagne toujours107  for (let i = 0; i < 4; i++) {108    await page.mouse.move(mx, my);109    await page.mouse.down();110    await page.mouse.move(mx - 70 * (i + 1), my + 20, { steps: 4 });111    await page.mouse.up();112    await page.waitForTimeout(120);113  }114  await waitSettled(page, 2200);115  const total3 = await counterTotal(page);116  const nPts3 = await engineCount(page);117  check('C5 mouvements rapides successifs → aucun résultat périmé',118    total3 === nPts3, `${total3} = ${nPts3}`);119120  // retour dans une zone dense avant les tests de miroirs121  await page.goto(`${BASE}/?view=carte&lat=46.81&lng=-71.23&zoom=13`,122    { waitUntil: 'domcontentloaded' });123  await page.waitForSelector('.map-card', { timeout: 30000 });124  await waitSettled(page, 2500);125126  // C3 — survol d'une carte de liste → marqueur en survol < 100 ms127  const firstCard = page.locator('.map-card').first();128  const uid = await firstCard.getAttribute('data-uid');129  await firstCard.hover();130  await page.waitForTimeout(90);131  const hovered = await page.evaluate(() => globalThis.__kaMap.getState().hoveredPropertyId);132  check('C3 survol liste → marqueur illuminé (< 100 ms)', hovered === uid, `${hovered}`);133134  // C3 inverse — survol venu de la carte → carte de liste surlignée135  await page.mouse.move(10, 10);   // souris hors de la liste136  await page.evaluate(() => globalThis.__kaMap.setHovered(null, 'map'));137  await page.waitForTimeout(60);138  await page.evaluate((u) => globalThis.__kaMap.setHovered(u, 'map'), uid);139  await page.waitForTimeout(90);140  const hl = await page.locator(`.map-card[data-uid="${uid}"]`).getAttribute('class');141  check('C3 survol carte → annonce surlignée dans la liste', hl.includes('map-card-hover'));142  await page.evaluate(() => globalThis.__kaMap.setHovered(null, 'map'));143144  // C4 — clic marqueur (simulé moteur) → liste défile vers l'annonce + mini-fiche145  const farUid = await page.evaluate(() => {146    const m = globalThis.__kaMap;147    const ids = [...m['byId'].keys()];148    return ids[Math.min(ids.length - 1, 30)];149  });150  await page.evaluate((u) => globalThis.__kaMap.select(u, 'map'), farUid);151  await page.waitForTimeout(1400);152  const selVisible = await page.evaluate((u) => {153    const el = document.querySelector(`.map-card[data-uid="${CSS.escape(u)}"]`);154    if (!el) return 'absente';155    const r = el.getBoundingClientRect();156    const c = el.closest('.ms-list').getBoundingClientRect();157    return r.bottom > c.top && r.top < c.bottom ? 'visible' : 'hors-vue';158  }, farUid);159  check('C4 clic marqueur → défilement de la liste jusqu’à l’annonce',160    selVisible === 'visible', selVisible);161  const preview = await page.locator('.ka-preview').count();162  check('C4 mini-fiche ouverte au clic marqueur', preview === 1);163  await page.screenshot({ path: `${OUT}02-selection-miroir.png` });164165  // C4 inverse — clic annonce → carte recentrée sur le marqueur + sélection166  await page.evaluate(() => globalThis.__kaMap.select(null, 'app'));167  const cardUid = await page.locator('.map-card').nth(2).getAttribute('data-uid');168  await page.locator('.map-card').nth(2).click();169  await page.waitForTimeout(900);170  const selId = await page.evaluate(() => globalThis.__kaMap.getSelectedId());171  check('C4 clic annonce → marqueur sélectionné sur la carte', selId === cardUid);172173  // C2 — filtre resserré depuis l'URL partagée → les deux vues suivent174  await ctx.close();175}176177console.log('— Filtres → carte (compteur partagé) —');178{179  const ctx = await newDesktop();180  const page = await ctx.newPage();181  await page.goto(`${BASE}/?view=carte&price_max=900`, { waitUntil: 'domcontentloaded' });182  await page.waitForSelector('.ms-count b', { timeout: 30000 });183  await waitSettled(page, 2500);184  const total = await counterTotal(page);185  const nPts = await engineCount(page);186  check('C1 filtre loyer ≤ 900 $ : compteur = points', total === nPts, `${total} = ${nPts}`);187188  // tri partagé : passer en « Meilleures affaires » réordonne liste + points189  await page.locator('.ms-sort select').selectOption('deal');190  await waitSettled(page, 1800);191  const firstUid = await page.locator('.map-card').first().getAttribute('data-uid');192  const firstPointUid = await page.evaluate(() => [...globalThis.__kaMap['byId'].keys()][0]);193  check('C1 tri partagé : 1er de la liste = 1er point du tri', firstUid === firstPointUid,194    `${firstUid}`);195  await page.screenshot({ path: `${OUT}03-filtre-tri.png` });196  await ctx.close();197}198199console.log('— Zone dessinée (polygone) —');200{201  const ctx = await newDesktop();202  const page = await ctx.newPage();203  await page.goto(`${BASE}/?view=carte&lat=46.81&lng=-71.23&zoom=13`,204    { waitUntil: 'domcontentloaded' });205  await page.waitForSelector('.ka-draw-btn', { timeout: 30000 });206  await waitSettled(page, 2500);207  const box = await canvasBox(page);208  await page.locator('.ka-draw-btn').click();209  const cx = box.x + box.width / 2, cy = box.y + box.height / 2;210  await page.mouse.click(cx - 160, cy - 80);211  await page.mouse.click(cx + 160, cy - 60);212  await page.mouse.click(cx + 40, cy + 140);213  await page.mouse.click(cx - 160, cy - 80);   // fermeture sur le 1er sommet214  await waitSettled(page, 2000);215  const chip = await page.locator('.ms-pills .pill').count();216  check('Polygone → puce « Zone dessinée » visible', chip >= 1);217  const total = await counterTotal(page);218  const nPts = await engineCount(page);219  check('C1 polygone : compteur = points', total === nPts && total > 0, `${total} = ${nPts}`);220  check('C7 zone dans l’URL', /zone=/.test(page.url()));221  await page.screenshot({ path: `${OUT}04-polygone.png` });222  // retrait de la puce → retour à la zone visible223  await page.locator('.ms-pills .pill').first().click();224  await waitSettled(page, 1500);225  check('Puce retirée → polygone effacé', !/zone=/.test(page.url()));226  await ctx.close();227}228229console.log('— Mode manuel & zéro résultat —');230{231  const ctx = await newDesktop();232  const page = await ctx.newPage();233  await page.goto(`${BASE}/?view=carte&lat=46.81&lng=-71.23&zoom=12`,234    { waitUntil: 'domcontentloaded' });235  await page.waitForSelector('.ka-search-area-auto input', { timeout: 30000 });236  await waitSettled(page, 2500);237  await page.locator('.ka-search-area-auto input').setChecked(false);238  const mb = await canvasBox(page);239  const mmx = mb.x + mb.width / 2, mmy = mb.y + mb.height / 2;240  await page.mouse.move(mmx, mmy);241  await page.mouse.down();242  await page.mouse.move(mmx - 320, mmy - 30, { steps: 10 });243  await page.mouse.up();244  await page.waitForTimeout(900);245  const btn = await page.locator('.ka-search-area-btn').count();246  check('Case décochée + déplacement → « Rechercher dans cette zone »', btn === 1);247  check('C7 move=0 dans l’URL', /move=0/.test(page.url()));248249  // zéro résultat : plein nord250  await page.goto(`${BASE}/?view=carte&lat=55.5&lng=-70.0&zoom=9`,251    { waitUntil: 'domcontentloaded' });252  await page.waitForSelector('.ms-empty, .ka-empty', { timeout: 30000 });253  await waitSettled(page, 2000);254  const emptyTxt = await page.locator('.ms-empty h3').first().textContent().catch(() => '');255  check('C9 zone vide → état vide propre + « Élargir la zone »',256    (emptyTxt || '').includes('Aucun logement'),257    emptyTxt?.trim());258  const widen = await page.locator('.ms-empty .btn-primary').count();259  check('C9 bouton « Élargir la zone » présent', widen === 1);260  await page.screenshot({ path: `${OUT}05-zero-resultat.png` });261  await ctx.close();262}263264console.log('— URL partagée = recherche reproduite —');265{266  const ctx = await newDesktop();267  const page = await ctx.newPage();268  const shared = `${BASE}/?view=carte&price_max=1200&unit_type=3%C2%BD&lat=46.80&lng=-71.24&zoom=13&tri=recent`;269  await page.goto(shared, { waitUntil: 'domcontentloaded' });270  await page.waitForSelector('.ms-count b', { timeout: 30000 });271  await waitSettled(page, 2500);272  const total = await counterTotal(page);273  const nPts = await engineCount(page);274  const sortVal = await page.locator('.ms-sort select').inputValue();275  const zoom = await page.evaluate(() => Math.round(globalThis.__kaMap.getState().zoom * 10) / 10);276  check('C7 URL partagée : filtres + zone + tri reproduits',277    total === nPts && sortVal === 'recent' && Math.abs(zoom - 13) < 0.6,278    `${total} résultats, tri=${sortVal}, zoom=${zoom}`);279  await page.screenshot({ path: `${OUT}06-url-partagee.png` });280  await ctx.close();281}282283console.log('— Mobile : bascule + carrousel synchronisé —');284{285  const ctx = await browser.newContext({ ...devices['iPhone 14'], locale: 'fr-CA' });286  await ctx.addInitScript(initScript);287  const page = await ctx.newPage();288  await page.goto(`${BASE}/?view=carte&lat=46.81&lng=-71.23&zoom=13`,289    { waitUntil: 'domcontentloaded' });290  await page.waitForSelector('.ms-carousel', { timeout: 30000 });291  await waitSettled(page, 3000);292  const items = await page.locator('.ms-car-item').count();293  check('C8 carrousel mobile présent', items > 0, `${items} cartes`);294295  // marqueur → carrousel : sélection du 5e point → carte correspondante mise en avant296  const uid5 = await page.evaluate(() => [...globalThis.__kaMap['byId'].keys()][4]);297  await page.evaluate((u) => globalThis.__kaMap.select(u, 'map'), uid5);298  await page.waitForTimeout(1200);299  const selCar = await page.locator('.ms-car-item.sel').getAttribute('data-uid').catch(() => null);300  check('C8 taper un marqueur → carrousel défile sur l’annonce', selCar === uid5, `${selCar}`);301302  // carrousel → marqueur : balayage → sélection carte303  await page.locator('.ms-carousel').evaluate((el) => { el.scrollLeft += el.clientWidth * 2; });304  await page.waitForTimeout(700);305  const selId = await page.evaluate(() => globalThis.__kaMap.getSelectedId());306  check('C8 balayer le carrousel → sélection déplacée sur la carte',307    selId !== null && selId !== uid5, `${selId}`);308  await page.screenshot({ path: `${OUT}07-mobile-carrousel.png` });309310  // bascule Liste ↔ Carte sans perte d'état311  const urlCarte = page.url();312  await page.locator('.view-toggle button').first().click();   // Liste313  await page.waitForTimeout(800);314  await page.locator('.view-toggle button').nth(1).click();    // Carte315  await page.waitForSelector('.ms-carousel', { timeout: 20000 });316  await waitSettled(page, 2500);317  check('C8 bascule Liste ↔ Carte : état conservé (URL stable)',318    /view=carte/.test(page.url()) && /lat=/.test(page.url()),319    page.url().replace(BASE, ''));320  await page.screenshot({ path: `${OUT}08-mobile-carte.png` });321  await ctx.close();322}323324console.log('— Géolocalisation d’accueil —');325{326  const ctx = await browser.newContext({327    viewport: { width: 1440, height: 900 }, locale: 'fr-CA',328    geolocation: { latitude: 46.8139, longitude: -71.2080 },329    permissions: ['geolocation'],330  });331  await ctx.addInitScript(() => {332    localStorage.setItem('rentka-consent-v1',333      JSON.stringify({ essential: true, preferences: true, statistics: false, ts: 1 }));334  });335  const page = await ctx.newPage();336  await page.goto(`${BASE}/`, { waitUntil: 'domcontentloaded' });337  await page.waitForFunction(() => /view=carte/.test(location.search), null, { timeout: 20000 })338    .catch(() => {});339  const ok = /view=carte/.test(page.url()) && /lat=46\.81/.test(page.url());340  check('Arrivée + géoloc accordée → vue carte centrée sur l’utilisateur', ok,341    page.url().replace(BASE, ''));342  await page.waitForSelector('.ms-count b', { timeout: 30000 });343  await waitSettled(page, 2500);344  await page.screenshot({ path: `${OUT}09-geolocalisation.png` });345  await ctx.close();346}347348await browser.close();349console.log(`\n${passed} réussis, ${failed} échoués`);350process.exit(failed > 0 ? 1 : 0);351