Skip to content
This repository was archived by the owner on Oct 14, 2026. It is now read-only.

Commit 76e2850

Browse files
anuj-adobeCopilot
andcommitted
fix(html-analyzer): narrow Google Maps filtering
Preserve authored gmp-map and InfoWindow content while removing exact Google runtime roots and detached controls. Add coverage for similar customer classes, both filtering paths, markdown output, and Repsol-style partial DOM snapshots. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 7094069 commit 76e2850

2 files changed

Lines changed: 110 additions & 16 deletions

File tree

‎packages/spacecat-shared-html-analyzer/src/html-filter.js‎

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,33 +101,55 @@ const VIDEO_PLAYER_SELECTORS = [
101101
'.playkit-player', // Kaltura PlayKit
102102
].join(', ');
103103

104-
// Google Maps runtime DOM includes tile fallback/control text that pollutes extracted content.
105-
// Keep customer-authored location data around the map; strip only Google-owned widget nodes.
106-
const MAPS_WIDGET_SELECTORS = [
107-
'.gm-style', // Google Maps JS API root container
108-
// Controls can be detached from .gm-style in serialized or partial DOM snapshots.
104+
// Exact Google Maps runtime classes only; never match customer map wrappers by name or substring.
105+
// Open InfoWindow content is preserved before the runtime root is removed.
106+
const MAPS_RUNTIME_ROOT_SELECTOR = '.gm-style';
107+
const MAPS_CONTROL_SELECTORS = [
108+
// These controls can be detached from .gm-style in serialized or partial DOM snapshots.
109109
'.LGLeeN-keyboard-shortcuts-view',
110110
'.gm-style-cc',
111111
'.gm-bundled-control',
112112
'.gm-svpc',
113113
'.gmnoprint',
114-
'gmp-map', // Google Maps Web Component (Maps JS API v4+)
115114
].join(', ');
116115

117116
/**
118117
* Remove Google Maps widget containers (browser environment)
119118
* @param {Element} element - DOM element to filter
120119
*/
121120
function removeMapWidgets(element) {
122-
element.querySelectorAll(MAPS_WIDGET_SELECTORS).forEach((n) => n.remove());
121+
element.querySelectorAll(MAPS_RUNTIME_ROOT_SELECTOR).forEach((mapRoot) => {
122+
const detailedInfoWindows = mapRoot.querySelectorAll('.gm-style-iw-d');
123+
const infoWindows = detailedInfoWindows.length
124+
? detailedInfoWindows
125+
: mapRoot.querySelectorAll('.gm-style-iw');
126+
127+
infoWindows.forEach((infoWindow) => {
128+
mapRoot.parentNode?.insertBefore(infoWindow, mapRoot);
129+
});
130+
mapRoot.remove();
131+
});
132+
element.querySelectorAll(MAPS_CONTROL_SELECTORS).forEach((n) => n.remove());
123133
}
124134

125135
/**
126136
* Remove Google Maps widget containers (Node.js / Cheerio environment)
127137
* @param {CheerioAPI} $ - Cheerio instance
128138
*/
129139
function removeMapWidgetsCheerio($) {
130-
$(MAPS_WIDGET_SELECTORS).remove();
140+
$(MAPS_RUNTIME_ROOT_SELECTOR).each((i, mapRoot) => {
141+
const $mapRoot = $(mapRoot);
142+
const $detailedInfoWindows = $mapRoot.find('.gm-style-iw-d');
143+
const $infoWindows = $detailedInfoWindows.length
144+
? $detailedInfoWindows
145+
: $mapRoot.find('.gm-style-iw');
146+
147+
$infoWindows.each((j, infoWindow) => {
148+
$mapRoot.before(infoWindow);
149+
});
150+
$mapRoot.remove();
151+
});
152+
$(MAPS_CONTROL_SELECTORS).remove();
131153
}
132154

133155
/**

‎packages/spacecat-shared-html-analyzer/test/index.test.js‎

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ import {
2424

2525
function installBrowserDomParserShim() {
2626
// This minimal shim supports selector/removal behavior only; fixtures must not include scripts.
27+
const hadWindow = Object.hasOwn(globalThis, 'window');
28+
const hadDocument = Object.hasOwn(globalThis, 'document');
29+
const hadDOMParser = Object.hasOwn(globalThis, 'DOMParser');
2730
const previous = {
2831
window: globalThis.window,
2932
document: globalThis.document,
@@ -43,6 +46,21 @@ function installBrowserDomParserShim() {
4346
},
4447
};
4548
Object.defineProperties(node, {
49+
cheerioElement: {
50+
value: element,
51+
},
52+
parentNode: {
53+
get: () => {
54+
if (!element || !$(element).parent().length) {
55+
return null;
56+
}
57+
return {
58+
insertBefore(child, reference) {
59+
$(reference.cheerioElement).before(child.cheerioElement);
60+
},
61+
};
62+
},
63+
},
4664
textContent: {
4765
get: () => (element ? $(element).text() : $('html').text()),
4866
},
@@ -65,9 +83,12 @@ function installBrowserDomParserShim() {
6583
};
6684

6785
return () => {
68-
globalThis.window = previous.window;
69-
globalThis.document = previous.document;
70-
globalThis.DOMParser = previous.DOMParser;
86+
if (hadWindow) globalThis.window = previous.window;
87+
else delete globalThis.window;
88+
if (hadDocument) globalThis.document = previous.document;
89+
else delete globalThis.document;
90+
if (hadDOMParser) globalThis.DOMParser = previous.DOMParser;
91+
else delete globalThis.DOMParser;
7192
};
7293
}
7394

@@ -381,15 +402,20 @@ describe('HTML Visibility Analyzer', () => {
381402
<p>Encuentra tu estación más cercana.</p>
382403
<div id="map-container">
383404
<div class="gm-style">
384-
<div class="gm-style-iw">Map data ©2026 Google</div>
405+
<div class="gm-style-iw">
406+
<div class="gm-style-iw-d">
407+
<h2>Estación Repsol Centro</h2>
408+
<p>Calle Mayor 1, Madrid</p>
409+
</div>
410+
</div>
385411
<div class="gm-bundled-control">
386412
<div class="gmnoprint">
387413
<div class="gm-svpc">Pegman</div>
388414
<div>40.4168° N, 3.7038° W</div>
389415
<button>Zoom in</button>
390416
</div>
391417
</div>
392-
<div class="gm-style-cc">Terms of Use Report a map error</div>
418+
<div class="gm-style-cc">Map data ©2026 Google Terms of Use Report a map error</div>
393419
</div>
394420
</div>
395421
<p>Horario: Lunes a viernes 8:00 - 20:00</p>
@@ -400,6 +426,8 @@ describe('HTML Visibility Analyzer', () => {
400426
expect(text).to.include('Estaciones de servicio');
401427
expect(text).to.include('Encuentra tu estación más cercana.');
402428
expect(text).to.include('Horario');
429+
expect(text).to.include('Estación Repsol Centro');
430+
expect(text).to.include('Calle Mayor 1, Madrid');
403431
expect(text).to.not.include('Map data');
404432
expect(text).to.not.include('Pegman');
405433
expect(text).to.not.include('40.4168');
@@ -426,12 +454,16 @@ describe('HTML Visibility Analyzer', () => {
426454
<span>Keyboard shortcuts</span>
427455
</div>
428456
</div>
457+
<div class="gm-style-cc">Detached attribution</div>
458+
<div class="gm-bundled-control">Detached bundled control</div>
459+
<div class="gm-svpc">Detached Street View control</div>
460+
<div class="gmnoprint">Detached non-printing control</div>
429461
</div>
430462
</div>
431463
<p>Productos Diesel e+ Agrodiesel e+10</p>
432464
</body></html>`;
433465

434-
const text = await stripTagsToText(html, true);
466+
const text = await stripTagsToText(html, false);
435467

436468
expect(text).to.include('Buscador de Agencias Distribuidoras de gasóleo');
437469
expect(text).to.include('GASOLEOS PECES E HIJOS S.L.');
@@ -442,6 +474,10 @@ describe('HTML Visibility Analyzer', () => {
442474
expect(text).to.not.include('Report a map error');
443475
expect(text).to.not.include('Zoom in');
444476
expect(text).to.not.include('Keyboard shortcuts');
477+
expect(text).to.not.include('Detached attribution');
478+
expect(text).to.not.include('Detached bundled control');
479+
expect(text).to.not.include('Detached Street View control');
480+
expect(text).to.not.include('Detached non-printing control');
445481
});
446482

447483
it('should avoid removing customer-authored Google Maps wrapper content', async () => {
@@ -459,6 +495,17 @@ describe('HTML Visibility Analyzer', () => {
459495
<div data-google-maps>
460496
<span>Customer-authored store locator content.</span>
461497
</div>
498+
<gmp-map>
499+
<gmp-advanced-marker>
500+
<span>Madrid showroom: Calle de Alcalá 10.</span>
501+
</gmp-advanced-marker>
502+
</gmp-map>
503+
<div class="custom-gm-style">
504+
<span>Customer content with a similar class name.</span>
505+
</div>
506+
<div class="gmnoprint-content">
507+
<span>Printable customer directions.</span>
508+
</div>
462509
<p>Visit us at our office.</p>
463510
</body></html>`;
464511

@@ -470,6 +517,9 @@ describe('HTML Visibility Analyzer', () => {
470517
expect(text).to.include('Our office locations are shown on a Google map.');
471518
expect(text).to.include('Store locator fallback copy for visitors.');
472519
expect(text).to.include('Customer-authored store locator content.');
520+
expect(text).to.include('Madrid showroom: Calle de Alcalá 10.');
521+
expect(text).to.include('Customer content with a similar class name.');
522+
expect(text).to.include('Printable customer directions.');
473523
});
474524

475525
it('should remove Google Maps widgets in browser DOMParser mode', async () => {
@@ -480,18 +530,22 @@ describe('HTML Visibility Analyzer', () => {
480530
<div class="gm-style">
481531
<div>Sorry, we have no imagery here.</div>
482532
<div class="gm-style-cc">Map Data Terms</div>
533+
<div class="gm-style-iw">Open Madrid location details.</div>
483534
</div>
484-
<gmp-map>Zoom in Map data ©Google</gmp-map>
535+
<gmp-map>
536+
<gmp-advanced-marker>Customer-authored marker details.</gmp-advanced-marker>
537+
</gmp-map>
485538
<p>Real office content.</p>
486539
</body></html>`;
487540

488541
const text = await filterHtmlContent(html, true, true);
489542

490543
expect(text).to.include('Office locations');
491544
expect(text).to.include('Real office content.');
545+
expect(text).to.include('Open Madrid location details.');
546+
expect(text).to.include('Customer-authored marker details.');
492547
expect(text).to.not.include('Sorry, we have no imagery here');
493548
expect(text).to.not.include('Map Data');
494-
expect(text).to.not.include('Zoom in');
495549
} finally {
496550
restoreBrowser();
497551
}
@@ -549,6 +603,24 @@ describe('HTML Visibility Analyzer', () => {
549603
});
550604

551605
describe('generateMarkdownDiff + getAddedMarkdownBlocks', () => {
606+
it('should exclude Google Maps runtime text from rendered markdown', async () => {
607+
const serverHtml = '<html><body><h1>Locations</h1></body></html>';
608+
const clientHtml = `<html><body>
609+
<h1>Locations</h1>
610+
<div class="gm-style">
611+
<div>Sorry, we have no imagery here.</div>
612+
<div class="gm-style-cc">Map data ©2026 Google</div>
613+
</div>
614+
<p>Madrid office details.</p>
615+
</body></html>`;
616+
617+
const { currentRenderedHtml } = await generateMarkdownDiff(serverHtml, clientHtml, false);
618+
619+
expect(currentRenderedHtml).to.include('Madrid office details.');
620+
expect(currentRenderedHtml).to.not.include('Sorry, we have no imagery here.');
621+
expect(currentRenderedHtml).to.not.include('Map data');
622+
});
623+
552624
it('should not show JSON-LD as added when content is identical but DOM nesting differs', async () => {
553625
const jsonLd = JSON.stringify({ '@context': 'https://schema.org', '@type': 'WebPage', name: 'Test' });
554626
// Server-side: skip button has 3-space indentation (from DOM nesting)

0 commit comments

Comments
 (0)