azon_seeker/scripts/test-amazon-search-parser.ts

1211 lines
55 KiB
TypeScript

import assert from 'node:assert/strict';
import { JSDOM } from 'jsdom';
import { collectAmazonSearchPageData } from '../src/page-worker/web-injectors/amazon-search-parser';
const html = `
<main id="search">
<section data-component-type="sponsored-brand">
<span aria-label="Sponsored">Sponsored</span>
<a href="/stores/example"><span class="brand-logo">Example brand</span></a>
<h2>Example brand headline</h2>
<article data-asin="B000000001">
<h3>Brand product one</h3>
<a href="/dp/B000000001"><img src="https://example.com/brand-1.jpg" /></a>
</article>
<article data-asin="B000000002">
<h3>Brand product two</h3>
<a href="/dp/B000000002"><img src="https://example.com/brand-2.jpg" /></a>
</article>
</section>
<div class="s-main-slot">
<div class="ordinary-result-grid">
<article data-component-type="s-search-result" data-asin="B000000003">
<span>Amazon's Choice</span>
<h2>Organic product</h2>
<a href="/dp/B000000003"><img class="s-image" src="https://example.com/organic.jpg" /></a>
<span class="a-price"><span class="a-offscreen">$29.99</span></span>
</article>
<article data-component-type="s-search-result" data-asin="B000000004">
<a aria-label="View Sponsored information or leave ad feedback"></a>
<h2>Sponsored product</h2>
<a href="/sspa/click?url=%2Fdp%2FB000000004"><img class="s-image" src="https://example.com/sp.jpg" /></a>
<span class="a-price"><span class="a-offscreen">$33.93</span></span>
<span class="a-price a-text-price" data-a-strike="true"><span class="a-offscreen">$39.99</span></span>
<span class="a-price a-text-price apex-priceperunit-value"><span class="a-offscreen">$0.28</span></span>
</article>
<article data-component-type="s-search-result" data-asin="B000000007">
<h2>Second organic product</h2>
<a href="/dp/B000000007"><img class="s-image" src="https://example.com/organic-2.jpg" /></a>
</article>
</div>
<section class="custom-sponsored-module">
<span>Sponsored</span>
<article data-asin="B000000005">
<h3>Unknown sponsored product</h3>
<a href="/dp/B000000005"><img src="https://example.com/unknown.jpg" /></a>
</article>
</section>
<section class="bottom-sponsored-video-module">
<video></video>
<span>Sponsored</span>
<a href="/stores/video-brand"><span class="brand-logo">Video brand</span></a>
<h2>Video brand headline</h2>
<div class="puis-card-container">
<div class="puisg-row">
<article data-asin="B000000008">
<h3>Sponsored Brands video product</h3>
<a href="/dp/B000000008"><img src="https://example.com/brand-video.jpg" /></a>
</article>
</div>
</div>
</section>
<section style="display: none">
<span>Sponsored</span>
<article data-asin="B000000009">
<h3 aria-label="Sponsored Ad - hidden preloaded product">Hidden preloaded product</h3>
<a href="/sspa/click?url=%2Fdp%2FB000000009">
<img src="https://example.com/hidden.jpg" />
</a>
</article>
</section>
<section aria-hidden="true">
<span>Sponsored</span>
<article data-asin="B000000010">
<h3>Hidden accessibility product</h3>
<a href="/dp/B000000010"><img src="https://example.com/hidden-aria.jpg" /></a>
</article>
</section>
</div>
<aside id="rightResultsATF" data-component-type="sponsored-display">
<span>Sponsored</span>
<article data-asin="B000000006">
<h3>Display product</h3>
<a href="/dp/B000000006"><img src="https://example.com/display.jpg" /></a>
</article>
</aside>
</main>
`;
const dom = new JSDOM(html, { url: 'https://www.amazon.com/s?k=mirror' });
Object.assign(globalThis, {
document: dom.window.document,
Node: dom.window.Node,
Element: dom.window.Element,
HTMLElement: dom.window.HTMLElement,
HTMLAnchorElement: dom.window.HTMLAnchorElement,
HTMLImageElement: dom.window.HTMLImageElement,
});
async function main() {
const items = await collectAmazonSearchPageData({ pattern: 'pattern-2' });
assert.equal(items.length, 8);
const byAsin = new Map(items.map((item) => [item.asin, item]));
assert.equal(byAsin.get('B000000003')?.adType, 'organic');
assert.equal(byAsin.get('B000000004')?.adType, 'sponsoredProducts');
assert.equal(byAsin.get('B000000004')?.price, '$33.93');
assert.equal(byAsin.get('B000000004')?.strikethroughPrice, '$39.99');
assert.equal(byAsin.get('B000000004')?.link, 'https://www.amazon.com/dp/B000000004');
assert.equal(byAsin.get('B000000004')?.placement, 'middleOfSearch');
assert.equal(byAsin.get('B000000001')?.adType, 'sponsoredBrands');
assert.equal(byAsin.get('B000000002')?.adType, 'sponsoredBrands');
assert.equal(byAsin.get('B000000001')?.placement, 'topOfSearch');
assert.equal(byAsin.get('B000000001')?.placementId, byAsin.get('B000000002')?.placementId);
assert.equal(byAsin.get('B000000001')?.adProductIndex, 1);
assert.equal(byAsin.get('B000000002')?.adProductIndex, 2);
assert.equal(byAsin.get('B000000005')?.adType, 'sponsoredProducts');
assert.equal(byAsin.get('B000000005')?.placement, 'bottomOfSearch');
assert.equal(byAsin.get('B000000006')?.adType, 'sponsoredDisplay');
assert.equal(byAsin.get('B000000006')?.placement, 'sidebar');
assert.equal(byAsin.get('B000000008')?.adType, 'sponsoredBrands');
assert.equal(byAsin.get('B000000008')?.adFormat, 'video');
assert.equal(byAsin.get('B000000008')?.placement, 'bottomOfSearch');
assert.equal(items.filter((item) => item.asin === 'B000000008').length, 1);
assert.equal(byAsin.has('B000000009'), false);
assert.equal(byAsin.has('B000000010'), false);
assert.deepEqual(
items.map((item) => item.pageRank),
[1, 2, 3, 4, 5, 6, 7, 8],
);
const hiddenDisclosureDom = new JSDOM(
`
<main id="search">
<div class="s-main-slot">
<article data-component-type="s-search-result" data-asin="B000000011">
<h2>Visible organic product</h2>
<span
aria-label="Sponsored Ad - hidden disclosure"
style="position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0)"
>Sponsored</span>
<a href="/sspa/click?url=%2Fdp%2FB000000011">
<img class="s-image" src="https://example.com/visible-product.jpg" />
</a>
</article>
</div>
</main>
`,
{ url: 'https://www.amazon.com/s?k=mirror' },
);
Object.assign(globalThis, {
document: hiddenDisclosureDom.window.document,
Node: hiddenDisclosureDom.window.Node,
Element: hiddenDisclosureDom.window.Element,
HTMLElement: hiddenDisclosureDom.window.HTMLElement,
HTMLAnchorElement: hiddenDisclosureDom.window.HTMLAnchorElement,
HTMLImageElement: hiddenDisclosureDom.window.HTMLImageElement,
});
const hiddenDisclosureItems = await collectAmazonSearchPageData({ pattern: 'pattern-2' });
assert.equal(hiddenDisclosureItems.length, 1);
assert.equal(hiddenDisclosureItems[0].adType, 'organic');
const groupedAdsDom = new JSDOM(
`
<main id="search">
<div class="s-main-slot">
<section class="shared-sponsored-products">
<span aria-label="Sponsored">Sponsored</span>
<a href="/stores/reolink" style="display: none">Hidden brand destination</a>
<article data-asin="B000000012">
<h3>Grouped sponsored product one</h3>
<a href="/dp/B000000012"><img src="https://example.com/grouped-1.jpg" /></a>
</article>
<article data-asin="B000000013">
<h3>Grouped sponsored product two</h3>
<a href="/dp/B000000013"><img src="https://example.com/grouped-2.jpg" /></a>
</article>
</section>
</div>
<aside class="left-sponsored-display" data-component-type="sponsored-display">
<span aria-label="Sponsored">Sponsored</span>
<article>
<h3>Left rail display product</h3>
<a href="/dp/B000000014"><img src="https://example.com/display-left.jpg" /></a>
</article>
</aside>
</main>
`,
{ url: 'https://www.amazon.com/s?k=mirror&page=8' },
);
Object.assign(globalThis, {
document: groupedAdsDom.window.document,
Node: groupedAdsDom.window.Node,
Element: groupedAdsDom.window.Element,
HTMLElement: groupedAdsDom.window.HTMLElement,
HTMLAnchorElement: groupedAdsDom.window.HTMLAnchorElement,
HTMLImageElement: groupedAdsDom.window.HTMLImageElement,
});
const groupedAds = await collectAmazonSearchPageData({ pattern: 'pattern-2' });
const groupedAdsByAsin = new Map(groupedAds.map((item) => [item.asin, item]));
assert.equal(groupedAdsByAsin.get('B000000012')?.adType, 'sponsoredProducts');
assert.equal(groupedAdsByAsin.get('B000000013')?.adType, 'sponsoredProducts');
assert.equal(
groupedAdsByAsin.get('B000000012')?.placementId,
groupedAdsByAsin.get('B000000013')?.placementId,
);
assert.equal(groupedAdsByAsin.get('B000000012')?.adProductIndex, 1);
assert.equal(groupedAdsByAsin.get('B000000013')?.adProductIndex, 2);
assert.equal(groupedAdsByAsin.get('B000000014')?.adType, 'sponsoredDisplay');
assert.equal(groupedAdsByAsin.get('B000000014')?.placement, 'sidebar');
const liveSponsoredBrandsDom = new JSDOM(
`
<main id="search">
<div class="s-main-slot">
<div class="s-widget AdHolder" data-cel-widget="CardInstanceArlo">
<div class="sb-headline">Arlo Security for any Home</div>
<span>Sponsored</span>
<div data-asin="B08AR2S6MC">
<span>Video Doorbell</span>
<a href="/sspa/click?ie=UTF8&url=%2Fdp%2FB08AR2S6MC">
<img data-src="https://example.com/arlo-1.jpg" src="https://example.com/arlo-1.jpg" />
</a>
</div>
<div data-asin="B0ARLO0002">
<span>Outdoor Camera</span>
<a href="/sspa/click?url=%2Fdp%2FB0ARLO0002">
<img src="https://example.com/arlo-2.jpg" />
</a>
</div>
<div data-asin="B0ARLO0003">
<span>Bundle</span>
<a href="/sspa/click?url=%2Fdp%2FB0ARLO0003">
<img src="https://example.com/arlo-3.jpg" />
</a>
</div>
</div>
<article data-component-type="s-search-result" data-asin="B09RING001">
<span>Sponsored</span>
<h2>Ring Spotlight Cam Plus, Battery</h2>
<a href="/sspa/click?url=%2Fdp%2FB09RING001"><img class="s-image" src="https://example.com/ring.jpg" /></a>
</article>
<article data-component-type="s-search-result" data-asin="B09ORG0001">
<h2>Organic camera</h2>
<a href="/dp/B09ORG0001"><img class="s-image" src="https://example.com/org.jpg" /></a>
</article>
<div class="sbv-container AdHolder">
<span>Sponsored</span>
<video></video>
<div class="sbv-copy">PROTECT YOUR EQUIPMENT &amp; FACILITIES</div>
<div data-asin="B0VOSKER01" class="sbv-product">
<h2>Vosker VKX Solar Powered 4G-LTE Outdoor Cellular Security Camera</h2>
<a href="/sspa/click?url=%2Fdp%2FB0VOSKER01"><img src="https://example.com/vosker.jpg" /></a>
<span class="a-price"><span class="a-offscreen">$379.99</span></span>
</div>
</div>
<section class="brands-related-to-your-search AdHolder">
<h2>Brands related to your search</h2>
<span>Sponsored</span>
<div class="brand-card" data-asin="B0BRAND001">
<img src="https://example.com/vosker-brand.jpg" alt="VOSKER" />
<a href="/sspa/click?url=%2Fdp%2FB0BRAND001">VOSKER</a>
</div>
<div class="brand-card">
<img src="https://example.com/reolink-brand.jpg" alt="Reolink" />
<a href="/sspa/click?url=%2Fgp%2Fproduct%2FB0BRAND002">Reolink</a>
</div>
</section>
</div>
</main>
`,
{ url: 'https://www.amazon.com/s?k=home+security' },
);
Object.assign(globalThis, {
document: liveSponsoredBrandsDom.window.document,
Node: liveSponsoredBrandsDom.window.Node,
Element: liveSponsoredBrandsDom.window.Element,
HTMLElement: liveSponsoredBrandsDom.window.HTMLElement,
HTMLAnchorElement: liveSponsoredBrandsDom.window.HTMLAnchorElement,
HTMLImageElement: liveSponsoredBrandsDom.window.HTMLImageElement,
});
const liveSponsoredBrands = await collectAmazonSearchPageData({ pattern: 'pattern-2' });
const liveByAsin = new Map(liveSponsoredBrands.map((item) => [item.asin, item]));
assert.equal(liveByAsin.get('B08AR2S6MC')?.adType, 'sponsoredBrands');
assert.equal(liveByAsin.get('B0ARLO0002')?.adType, 'sponsoredBrands');
assert.equal(liveByAsin.get('B0ARLO0003')?.adType, 'sponsoredBrands');
assert.equal(liveByAsin.get('B08AR2S6MC')?.placement, 'topOfSearch');
assert.equal(liveByAsin.get('B09RING001')?.adType, 'sponsoredProducts');
assert.equal(liveByAsin.get('B09ORG0001')?.adType, 'organic');
assert.equal(liveByAsin.get('B0VOSKER01')?.adType, 'sponsoredBrands');
assert.equal(liveByAsin.get('B0VOSKER01')?.adFormat, 'video');
assert.equal(liveByAsin.get('B0VOSKER01')?.placement, 'bottomOfSearch');
assert.equal(liveByAsin.get('B0BRAND001')?.adType, 'sponsoredBrands');
assert.equal(liveByAsin.get('B0BRAND002')?.adType, 'sponsoredBrands');
const lazyImageDom = new JSDOM(
`
<main id="search">
<div class="s-main-slot">
<div class="s-widget AdHolder">
<div class="sb-headline">Arlo Security for any Home</div>
<span>Sponsored</span>
<div data-asin="B08AR2LAZY">
<h3>Video Doorbell</h3>
<a href="/sspa/click?url=%2Fdp%2FB08AR2LAZY">
<img data-src="https://example.com/arlo-lazy.jpg" />
</a>
</div>
</div>
</div>
</main>
`,
{ url: 'https://www.amazon.com/s?k=home+security' },
);
Object.assign(globalThis, {
document: lazyImageDom.window.document,
Node: lazyImageDom.window.Node,
Element: lazyImageDom.window.Element,
HTMLElement: lazyImageDom.window.HTMLElement,
HTMLAnchorElement: lazyImageDom.window.HTMLAnchorElement,
HTMLImageElement: lazyImageDom.window.HTMLImageElement,
});
const lazyImageItems = await collectAmazonSearchPageData({ pattern: 'pattern-2' });
assert.equal(lazyImageItems.length, 1);
assert.equal(lazyImageItems[0]?.asin, 'B08AR2LAZY');
assert.equal(lazyImageItems[0]?.adType, 'sponsoredBrands');
assert.equal(lazyImageItems[0]?.imageSrc, 'https://example.com/arlo-lazy.jpg');
const classificationBoundaryDom = new JSDOM(
`
<main id="search">
<div class="s-main-slot">
<div class="s-widget AdHolder" data-cel-widget="CardInstanceArlo">
<div class="sb-headline">Arlo Security for any Home</div>
<span>Sponsored</span>
<div data-asin="B08ARLOSB1">
<span>Video Doorbell</span>
<a href="/sspa/click?url=%2Fdp%2FB08ARLOSB1">
<img src="https://example.com/arlo-banner-1.jpg" />
</a>
</div>
<div data-asin="B08ARLOSB2">
<span>Outdoor Camera</span>
<a href="/sspa/click?url=%2Fdp%2FB08ARLOSB2">
<img src="https://example.com/arlo-banner-2.jpg" />
</a>
</div>
</div>
<div class="s-widget AdHolder" data-cel-widget="search-grid-wrap">
<article data-component-type="s-search-result" data-asin="B0ARLOGRID" class="AdHolder">
<span>Sponsored</span>
<h2>Arlo Video Doorbell Wired</h2>
<a href="/sspa/click?url=%2Fdp%2FB0ARLOGRID">
<img class="s-image" src="https://example.com/arlo-grid.jpg" />
</a>
</article>
<article data-component-type="s-search-result" data-asin="B09RINGSP1" class="AdHolder">
<span>Sponsored</span>
<h2>Ring Spotlight Cam Plus, Battery</h2>
<a href="/sspa/click?url=%2Fdp%2FB09RINGSP1">
<img class="s-image" src="https://example.com/ring-grid.jpg" />
</a>
</article>
<article data-component-type="s-search-result" data-asin="B09ORGKEEP">
<h2>Organic camera</h2>
<a href="/dp/B09ORGKEEP"><img class="s-image" src="https://example.com/org-keep.jpg" /></a>
</article>
</div>
<article data-component-type="s-search-result" data-asin="B0TRACKSP1" class="AdHolder">
<span>Sponsored</span>
<h2>Arlo Go 2 LTE Security Camera</h2>
<a href="/sspa/click?url=%2Fdp%2FB0TRACKSP1">
<img class="s-image" src="https://example.com/arlo-track.jpg" />
</a>
<a href="/gp/product/B0TRACKXX2">related tracking</a>
</article>
<article data-component-type="s-search-result" data-asin="B0DEMOSP01" class="AdHolder">
<span>Sponsored</span>
<h2>Ring Indoor Cam with demo</h2>
<video></video>
<a href="/dp/B0DEMOSP01">
<img class="s-image" src="https://example.com/ring-demo.jpg" />
</a>
</article>
<div class="sbv-container AdHolder">
<span>Sponsored</span>
<video></video>
<div class="sbv-copy">PROTECT YOUR EQUIPMENT &amp; FACILITIES</div>
<div data-asin="B0VOSKER02" class="sbv-product">
<h2>Vosker VKX Solar Powered 4G-LTE Outdoor Cellular Security Camera</h2>
<a href="/sspa/click?url=%2Fdp%2FB0VOSKER02">
<img src="https://example.com/vosker-2.jpg" />
</a>
</div>
</div>
<article data-component-type="s-search-result" data-asin="B0NOADVID1">
<h2>Organic with product video</h2>
<video></video>
<a href="/dp/B0NOADVID1">
<img class="s-image" src="https://example.com/organic-video.jpg" />
</a>
</article>
<section class="brands-related-to-your-search AdHolder">
<h2>Brands related to your search</h2>
<span>Sponsored</span>
<div class="brand-card" data-asin="B0BRANDWL1">
<img src="https://example.com/vosker-wall.jpg" alt="VOSKER" />
<a href="/sspa/click?url=%2Fdp%2FB0BRANDWL1">VOSKER</a>
</div>
</section>
</div>
<aside id="rightResultsATF" data-component-type="sponsored-display">
<span>Sponsored</span>
<article data-asin="B0SIDEBAR1">
<h3>Sidebar display product</h3>
<a href="/dp/B0SIDEBAR1"><img src="https://example.com/sidebar.jpg" /></a>
</article>
</aside>
</main>
`,
{ url: 'https://www.amazon.com/s?k=home+security' },
);
Object.assign(globalThis, {
document: classificationBoundaryDom.window.document,
Node: classificationBoundaryDom.window.Node,
Element: classificationBoundaryDom.window.Element,
HTMLElement: classificationBoundaryDom.window.HTMLElement,
HTMLAnchorElement: classificationBoundaryDom.window.HTMLAnchorElement,
HTMLImageElement: classificationBoundaryDom.window.HTMLImageElement,
});
const classificationBoundary = await collectAmazonSearchPageData({ pattern: 'pattern-2' });
const boundaryByAsin = new Map(classificationBoundary.map((item) => [item.asin, item]));
assert.equal(boundaryByAsin.get('B08ARLOSB1')?.adType, 'sponsoredBrands');
assert.equal(boundaryByAsin.get('B08ARLOSB2')?.adType, 'sponsoredBrands');
assert.equal(boundaryByAsin.get('B0ARLOGRID')?.adType, 'sponsoredProducts');
assert.equal(boundaryByAsin.get('B09RINGSP1')?.adType, 'sponsoredProducts');
assert.equal(boundaryByAsin.get('B09ORGKEEP')?.adType, 'organic');
assert.equal(boundaryByAsin.get('B0TRACKSP1')?.adType, 'sponsoredProducts');
assert.equal(boundaryByAsin.has('B0TRACKXX2'), false);
assert.equal(boundaryByAsin.get('B0DEMOSP01')?.adType, 'sponsoredProducts');
assert.equal(boundaryByAsin.get('B0VOSKER02')?.adType, 'sponsoredBrands');
assert.equal(boundaryByAsin.get('B0VOSKER02')?.adFormat, 'video');
assert.equal(boundaryByAsin.get('B0NOADVID1')?.adType, 'organic');
assert.equal(boundaryByAsin.get('B0BRANDWL1')?.adType, 'sponsoredBrands');
assert.equal(boundaryByAsin.get('B0SIDEBAR1')?.adType, 'sponsoredDisplay');
assert.equal(boundaryByAsin.get('B0SIDEBAR1')?.placement, 'sidebar');
const thematicLoomDom = new JSDOM(
`
<main id="search">
<div class="s-main-slot">
<article data-component-type="s-search-result" data-asin="B0ORGLOOM1">
<h2>Organic camera</h2>
<a href="/dp/B0ORGLOOM1"><img class="s-image" src="https://example.com/org-loom.jpg" /></a>
</article>
<div
class="s-widget-container celwidget slot=MAIN template=FEATURED_ASINS_LIST widget=loom-desktop-bottom-slot_sptw-trending-hardlines-bottom-na-page2plus-t1"
cel_widget_id="MAIN-FEATURED_ASINS_LIST-22"
>
<div class="a-section s-include-content-margin">
<h2 id="loom-desktop-bottom-slot_featuredasins-heading">Trending now</h2>
<span
data-action="multi-ad-feedback-form-trigger"
data-multi-ad-feedback-form-trigger='{"adPlacementMetaData":{"slotName":"sp_search_thematic_btf-trending-hardlines","feedbackType":"sponsoredProductsLoom"}}'
>
<a aria-label="View Sponsored information or leave ad feedback" class="s-widget-sponsored-label-text">Sponsored</a>
</span>
<span data-component-type="s-searchgrid-carousel" class="s-searchgrid-carousel">
<ol class="a-carousel">
<li class="a-carousel-card" aria-hidden="false">
<div data-asin="B0B2MPYRKP" class="s-result-item">
<div class="puis-card-container">
<h2>iulock Keypad Smart Door Lock</h2>
<a href="/sspa/click?url=%2Fdp%2FB0B2MPYRKP&amp;widgetName=sp_search_thematic_btf">
<img class="s-image" src="https://example.com/iulock.jpg" />
</a>
</div>
</div>
</li>
<li class="a-carousel-card" aria-hidden="false">
<div data-asin="B0F8HS3RRT" class="s-result-item">
<div class="puis-card-container">
<h2>Richelock fingerprint lock</h2>
<a href="/sspa/click?url=%2Fdp%2FB0F8HS3RRT&amp;widgetName=sp_search_thematic_btf">
<img class="s-image" src="https://example.com/richelock.jpg" />
</a>
</div>
</div>
</li>
<li class="a-carousel-card" aria-hidden="false">
<div data-asin="B0GQX6GHK2" class="s-result-item">
<div class="puis-card-container">
<h2>Swann MaxRanger4K</h2>
<a href="/sspa/click?url=%2Fdp%2FB0GQX6GHK2&amp;widgetName=sp_search_thematic_btf">
<img class="s-image" src="https://example.com/swann.jpg" />
</a>
</div>
</div>
</li>
<li class="a-carousel-card" aria-hidden="false" style="visibility: hidden;">
<div data-asin="B0CN95HR9V" class="s-result-item">
<div class="puis-card-container">
<h2>Solar Fence Lights</h2>
<a href="/sspa/click?url=%2Fdp%2FB0CN95HR9V&amp;widgetName=sp_search_thematic_btf">
<img class="s-image" src="https://example.com/solar.jpg" />
</a>
</div>
</div>
</li>
<li class="a-carousel-card" aria-hidden="true" style="visibility: hidden;">
<div data-asin="B07M68S376" class="s-result-item">
<div class="puis-card-container">
<h2>SECRUI Wireless Doorbell</h2>
<a href="/sspa/click?url=%2Fdp%2FB07M68S376&amp;widgetName=sp_search_thematic_btf">
<img class="s-image" src="https://example.com/secrui.jpg" />
</a>
</div>
</div>
</li>
</ol>
</span>
</div>
</div>
<div class="sbv-container AdHolder">
<span>Sponsored</span>
<video></video>
<div class="sbv-copy">PROTECT YOUR EQUIPMENT &amp; FACILITIES</div>
<div data-asin="B0VOSKERLM" class="sbv-product">
<h2>Vosker VKX Solar Powered camera</h2>
<a href="/sspa/click?url=%2Fdp%2FB0VOSKERLM"><img src="https://example.com/vosker-loom.jpg" /></a>
</div>
</div>
<div class="s-widget AdHolder" data-cel-widget="CardInstanceArlo">
<div class="sb-headline">Arlo Security for any Home</div>
<span>Sponsored</span>
<div data-asin="B08ARLOLM1">
<span>Video Doorbell</span>
<a href="/sspa/click?url=%2Fdp%2FB08ARLOLM1">
<img src="https://example.com/arlo-loom.jpg" />
</a>
</div>
</div>
</div>
</main>
`,
{ url: 'https://www.amazon.com/s?k=home+security' },
);
Object.assign(globalThis, {
document: thematicLoomDom.window.document,
Node: thematicLoomDom.window.Node,
Element: thematicLoomDom.window.Element,
HTMLElement: thematicLoomDom.window.HTMLElement,
HTMLAnchorElement: thematicLoomDom.window.HTMLAnchorElement,
HTMLImageElement: thematicLoomDom.window.HTMLImageElement,
});
const thematicLoom = await collectAmazonSearchPageData({ pattern: 'pattern-2' });
const thematicByAsin = new Map(thematicLoom.map((item) => [item.asin, item]));
assert.equal(thematicByAsin.get('B0ORGLOOM1')?.adType, 'organic');
assert.equal(thematicByAsin.get('B0B2MPYRKP')?.adType, 'sponsoredProducts');
assert.equal(thematicByAsin.get('B0F8HS3RRT')?.adType, 'sponsoredProducts');
assert.equal(thematicByAsin.get('B0GQX6GHK2')?.adType, 'sponsoredProducts');
assert.equal(
thematicByAsin.get('B0B2MPYRKP')?.placementId,
thematicByAsin.get('B0F8HS3RRT')?.placementId,
);
assert.equal(thematicByAsin.get('B0B2MPYRKP')?.adProductIndex, 1);
assert.equal(thematicByAsin.get('B0F8HS3RRT')?.adProductIndex, 2);
assert.equal(thematicByAsin.get('B0GQX6GHK2')?.adProductIndex, 3);
assert.equal(thematicByAsin.has('B0CN95HR9V'), false);
assert.equal(thematicByAsin.has('B07M68S376'), false);
assert.equal(thematicByAsin.get('B0VOSKERLM')?.adType, 'sponsoredBrands');
assert.equal(thematicByAsin.get('B0VOSKERLM')?.adFormat, 'video');
assert.equal(thematicByAsin.get('B08ARLOLM1')?.adType, 'sponsoredBrands');
const organicLoomDom = new JSDOM(
`
<main id="search">
<div class="s-main-slot">
<article data-component-type="s-search-result" data-asin="B0ORGCFV01">
<h2>Organic camera</h2>
<a href="/dp/B0ORGCFV01"><img class="s-image" src="https://example.com/org-cfv.jpg" /></a>
</article>
<div
class="s-widget-container celwidget slot=MAIN template=FEATURED_ASINS_LIST widget=loom-desktop-bottom-slot_sptw-frequently-viewed-bottom-na"
cel_widget_id="MAIN-FEATURED_ASINS_LIST-23"
>
<div class="a-section s-include-content-margin">
<h2 id="loom-desktop-bottom-slot_featuredasins-heading">Customers frequently viewed</h2>
<span
data-action="multi-ad-feedback-form-trigger"
data-multi-ad-feedback-form-trigger='{"adPlacementMetaData":{"slotName":"sp_search_thematic_btf-frequently-viewed","feedbackType":"sponsoredProductsLoom"}}'
></span>
<span
aria-label="Sponsored"
style="position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0)"
>Sponsored</span>
<span data-component-type="s-searchgrid-carousel" class="s-searchgrid-carousel">
<ol class="a-carousel">
<li class="a-carousel-card" aria-hidden="false">
<div data-asin="B0CFV00001" class="s-result-item">
<div class="puis-card-container">
<h2>ANNKE security camera</h2>
<a href="/sspa/click?url=%2Fdp%2FB0CFV00001&amp;widgetName=sp_search_thematic_btf">
<img class="s-image" src="https://example.com/annke.jpg" />
</a>
</div>
</div>
</li>
<li class="a-carousel-card" aria-hidden="false">
<div data-asin="B0CFV00002" class="s-result-item">
<div class="puis-card-container">
<h2>Night Owl camera</h2>
<a href="/sspa/click?url=%2Fdp%2FB0CFV00002&amp;widgetName=sp_search_thematic_btf">
<img class="s-image" src="https://example.com/nightowl.jpg" />
</a>
</div>
</div>
</li>
</ol>
</span>
</div>
</div>
<div
class="s-widget-container celwidget slot=MAIN template=FEATURED_ASINS_LIST widget=loom-desktop-bottom-slot_sptw-recently-bought-bottom-na"
cel_widget_id="MAIN-FEATURED_ASINS_LIST-24"
>
<h2>Recently bought and rated</h2>
<span data-component-type="s-searchgrid-carousel" class="s-searchgrid-carousel">
<ol class="a-carousel">
<li class="a-carousel-card" aria-hidden="false">
<div data-asin="B0RBR00001" class="s-result-item">
<div class="puis-card-container">
<h2>Rated doorbell</h2>
<a href="/dp/B0RBR00001">
<img class="s-image" src="https://example.com/rated.jpg" />
</a>
</div>
</div>
</li>
</ol>
</span>
</div>
</div>
</main>
`,
{ url: 'https://www.amazon.com/s?k=home+security' },
);
Object.assign(globalThis, {
document: organicLoomDom.window.document,
Node: organicLoomDom.window.Node,
Element: organicLoomDom.window.Element,
HTMLElement: organicLoomDom.window.HTMLElement,
HTMLAnchorElement: organicLoomDom.window.HTMLAnchorElement,
HTMLImageElement: organicLoomDom.window.HTMLImageElement,
});
const organicLoom = await collectAmazonSearchPageData({ pattern: 'pattern-2' });
const organicLoomByAsin = new Map(organicLoom.map((item) => [item.asin, item]));
assert.equal(organicLoomByAsin.get('B0ORGCFV01')?.adType, 'organic');
assert.equal(organicLoomByAsin.get('B0CFV00001')?.adType, 'organic');
assert.equal(organicLoomByAsin.get('B0CFV00002')?.adType, 'organic');
assert.equal(organicLoomByAsin.get('B0RBR00001')?.adType, 'organic');
assert.equal(
organicLoom.some((item) => item.adType === 'sponsoredProducts'),
false,
);
const organicVideoStripDom = new JSDOM(
`
<main id="search">
<div class="s-main-slot">
<article data-component-type="s-search-result" data-asin="B0ORGVID01">
<h2>Organic camera</h2>
<a href="/dp/B0ORGVID01"><img class="s-image" src="https://example.com/org-vid.jpg" /></a>
</article>
<div class="sbv-container AdHolder">
<video></video>
<a href="/stores/chamberlain"><span class="brand-logo">Chamberlain</span></a>
<div class="sb-headline">Chamberlain myQ</div>
<div class="sbv-copy">Control your garage from anywhere</div>
<div data-asin="B0NOSBV001" class="sbv-product">
<h2>Chamberlain garage opener</h2>
<a href="/dp/B0NOSBV001"><img src="https://example.com/chamberlain-nosponsored.jpg" /></a>
</div>
</div>
<div class="sbv-container AdHolder">
<span
aria-label="Sponsored"
style="position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0)"
>Sponsored</span>
<video></video>
<a href="/stores/vosker"><span class="brand-logo">Vosker</span></a>
<div class="sbv-copy">PROTECT YOUR EQUIPMENT</div>
<div data-asin="B0HIDDENSB" class="sbv-product">
<h2>Vosker solar camera</h2>
<a href="/sspa/click?url=%2Fdp%2FB0HIDDENSB"><img src="https://example.com/vosker-hidden.jpg" /></a>
</div>
</div>
<div class="sbv-container AdHolder">
<span>Sponsored</span>
<video></video>
<a href="/stores/vosker"><span class="brand-logo">Vosker</span></a>
<div class="sbv-copy">PROTECT YOUR EQUIPMENT</div>
<div data-asin="B0YESBV001" class="sbv-product">
<h2>Vosker with visible sponsored</h2>
<a href="/sspa/click?url=%2Fdp%2FB0YESBV001"><img src="https://example.com/vosker-yes.jpg" /></a>
</div>
</div>
</div>
</main>
`,
{ url: 'https://www.amazon.com/s?k=home+security' },
);
Object.assign(globalThis, {
document: organicVideoStripDom.window.document,
Node: organicVideoStripDom.window.Node,
Element: organicVideoStripDom.window.Element,
HTMLElement: organicVideoStripDom.window.HTMLElement,
HTMLAnchorElement: organicVideoStripDom.window.HTMLAnchorElement,
HTMLImageElement: organicVideoStripDom.window.HTMLImageElement,
});
const organicVideoStrip = await collectAmazonSearchPageData({ pattern: 'pattern-2' });
const videoStripByAsin = new Map(organicVideoStrip.map((item) => [item.asin, item]));
assert.equal(videoStripByAsin.get('B0ORGVID01')?.adType, 'organic');
assert.equal(videoStripByAsin.get('B0NOSBV001')?.adType, 'organic');
assert.equal(videoStripByAsin.get('B0HIDDENSB')?.adType, 'organic');
assert.equal(videoStripByAsin.get('B0YESBV001')?.adType, 'sponsoredBrands');
assert.equal(videoStripByAsin.get('B0YESBV001')?.adFormat, 'video');
assert.deepEqual(
organicVideoStrip.filter((item) => item.adType === 'sponsoredBrands').map((item) => item.asin),
['B0YESBV001'],
);
const liveBrandWallDom = new JSDOM(
`
<main id="search">
<div class="s-main-slot">
<article data-component-type="s-search-result" data-asin="B0ORGGRID1">
<h2>Organic camera</h2>
<a href="/dp/B0ORGGRID1"><img class="s-image" src="https://example.com/org-grid.jpg" /></a>
</article>
<div
class="s-result-item s-widget AdHolder s-flex-full-width"
data-asin=""
cel_widget_id="multi-brand-creative-desktop_loom-desktop-brand-footer-slot_46"
>
<span aria-label="Brands related to your search" role="heading">Brands related to your search</span>
<span aria-label="Leave feedback on Sponsored ad" tabindex="0" role="button">Sponsored</span>
<div class="sbx-desktop" data-aid="301209426575402" data-slot="desktop-hsa-3psl">
<a href="/stores/page/VOSKER?lp_asins=B0D5Z3V464%2CB0FY2QCXRJ%2CB0FY2MRK56&amp;lp_query=home%20security&amp;lp_slot=desktop-hsa-3psl">
<img
alt="Vosker"
src="https://images-na.ssl-images-amazon.com/images/G/01/x-locale/common/grey-pixel.gif"
data-src="https://example.com/vosker-wall.jpg"
/>
</a>
</div>
<div class="sbx-desktop" data-aid="301252396855702" data-slot="desktop-hsa-3psl">
<a href="/stores/page/REOLINK?lp_asins=B0C2PY2KTJ%2CB0FM45MSS8&amp;lp_slot=desktop-hsa-3psl">
<img
alt="REOLINK"
src="https://images-na.ssl-images-amazon.com/images/G/01/x-locale/common/grey-pixel.gif"
data-src="https://example.com/reolink-wall.jpg"
/>
</a>
</div>
</div>
</div>
</main>
`,
{ url: 'https://www.amazon.com/s?k=home+security' },
);
Object.assign(globalThis, {
document: liveBrandWallDom.window.document,
Node: liveBrandWallDom.window.Node,
Element: liveBrandWallDom.window.Element,
HTMLElement: liveBrandWallDom.window.HTMLElement,
HTMLAnchorElement: liveBrandWallDom.window.HTMLAnchorElement,
HTMLImageElement: liveBrandWallDom.window.HTMLImageElement,
});
const liveBrandWall = await collectAmazonSearchPageData({ pattern: 'pattern-2' });
const liveBrandWallByAsin = new Map(liveBrandWall.map((item) => [item.asin, item]));
assert.equal(liveBrandWallByAsin.get('B0ORGGRID1')?.adType, 'organic');
assert.equal(liveBrandWallByAsin.get('B0D5Z3V464')?.adType, 'sponsoredBrands');
assert.equal(liveBrandWallByAsin.get('B0C2PY2KTJ')?.adType, 'sponsoredBrands');
assert.equal(liveBrandWallByAsin.get('B0D5Z3V464')?.title, 'Vosker');
assert.equal(
liveBrandWallByAsin.get('B0D5Z3V464')?.imageSrc,
'https://example.com/vosker-wall.jpg',
);
assert.equal(
liveBrandWallByAsin.get('B0C2PY2KTJ')?.imageSrc,
'https://example.com/reolink-wall.jpg',
);
assert.equal(liveBrandWallByAsin.has('B0FY2QCXRJ'), false);
const influencerPicksDom = new JSDOM(
`
<main id="search">
<div class="s-main-slot">
<article data-component-type="s-search-result" data-asin="B0ORGINF01">
<h2>Organic camera</h2>
<a href="/dp/B0ORGINF01"><img class="s-image" src="https://example.com/org-inf.jpg" /></a>
</article>
<section class="s-widget-container celwidget">
<h2>Picks from Amazon Influencers</h2>
<span>Sponsored</span>
<span>From Amazon Influencer storefronts</span>
<div data-asin="B0ANNKE001" class="puis-card-container">
<h2>ANNKE 4K PoE Security Camera System</h2>
<a href="/sspa/click?url=%2Fdp%2FB0ANNKE001"><img src="https://example.com/annke.jpg" /></a>
</div>
<div data-asin="B0NIGHTO01" class="puis-card-container">
<h2>Night Owl DVR</h2>
<a href="/shop/nightowlstorefront"><span>storefront</span></a>
<a href="/sspa/click?url=%2Fdp%2FB0NIGHTO01"><img src="https://example.com/nightowl.jpg" /></a>
</div>
<div data-asin="B0SOLAR001" class="puis-card-container">
<h2>Solar Street Lights</h2>
<a href="/sspa/click?url=%2Fdp%2FB0SOLAR001"><img src="https://example.com/solar.jpg" /></a>
</div>
</section>
<div class="s-widget AdHolder">
<div class="creative-collage">
<img src="https://example.com/jobsite.jpg" alt="jobsite" />
<img src="https://example.com/camera-closeup.jpg" alt="camera" />
<button aria-label="Pause"></button>
<img src="https://example.com/packaging.jpg" alt="packaging" />
</div>
<div data-asin="B0VOSKERVK">
<span>Sponsored</span>
<h2>Vosker VKX Solar Powered 4G-LTE Outdoor Cellular Security Camera</h2>
<a href="/sspa/click?url=%2Fdp%2FB0VOSKERVK">
<img src="https://example.com/vosker-vkx.jpg" />
</a>
<span class="a-price"><span class="a-offscreen">$379.99</span></span>
</div>
</div>
<div class="sbv-container AdHolder">
<div class="sbv-creative">
<img src="https://example.com/jobsite2.jpg" alt="PROTECT YOUR INVESTMENT" />
<img src="https://example.com/closeup2.jpg" alt="CELLULAR SECURITY CAMERA" />
<button class="sbv-pause" aria-label="Pause"></button>
</div>
<div data-asin="B0VOSKERSB" class="sbv-product">
<span>Sponsored</span>
<h2>Vosker VKX Solar Powered camera</h2>
<a href="/sspa/click?url=%2Fdp%2FB0VOSKERSB">
<img src="https://example.com/vosker-sb.jpg" />
</a>
</div>
</div>
<article data-component-type="s-search-result" data-asin="B0VIEWVOSK">
<span>Sponsored</span>
<span class="previously-viewed-badge">Previously viewed</span>
<h2>Vosker VKX Solar Powered 4G-LTE Outdoor Cellular Security Camera</h2>
<a href="/sspa/click?url=%2Fdp%2FB0VIEWVOSK">
<img class="s-image" src="https://example.com/vosker-grid.jpg" />
</a>
<span class="a-price"><span class="a-offscreen">$149.99</span></span>
</article>
<article data-component-type="s-search-result" data-asin="B0OWLLABS1">
<span>Sponsored</span>
<h2>Owl Labs Meeting Owl 3</h2>
<a href="/sspa/click?url=%2Fdp%2FB0OWLLABS1">
<img class="s-image" src="https://example.com/owl.jpg" />
</a>
<span class="a-price"><span class="a-offscreen">$1,099.00</span></span>
</article>
<div class="s-related-searches AdHolder">
<h2>Related searches</h2>
<a href="/s?k=home+security+camera">home security camera</a>
<a href="/s?k=outdoor+camera">outdoor camera</a>
</div>
</div>
</main>
`,
{ url: 'https://www.amazon.com/s?k=home+security' },
);
Object.assign(globalThis, {
document: influencerPicksDom.window.document,
Node: influencerPicksDom.window.Node,
Element: influencerPicksDom.window.Element,
HTMLElement: influencerPicksDom.window.HTMLElement,
HTMLAnchorElement: influencerPicksDom.window.HTMLAnchorElement,
HTMLImageElement: influencerPicksDom.window.HTMLImageElement,
});
const influencerPicks = await collectAmazonSearchPageData({ pattern: 'pattern-2' });
const influencerByAsin = new Map(influencerPicks.map((item) => [item.asin, item]));
assert.equal(influencerByAsin.get('B0ORGINF01')?.adType, 'organic');
assert.equal(influencerByAsin.get('B0ANNKE001')?.adType, 'sponsoredProducts');
assert.equal(influencerByAsin.get('B0NIGHTO01')?.adType, 'sponsoredProducts');
assert.equal(influencerByAsin.get('B0SOLAR001')?.adType, 'sponsoredProducts');
assert.equal(
influencerByAsin.get('B0ANNKE001')?.placementId,
influencerByAsin.get('B0NIGHTO01')?.placementId,
);
assert.equal(influencerByAsin.get('B0VOSKERVK')?.adType, 'sponsoredBrands');
assert.equal(influencerByAsin.get('B0VOSKERVK')?.adFormat, 'standard');
assert.equal(influencerByAsin.get('B0VOSKERSB')?.adType, 'sponsoredBrands');
assert.equal(influencerByAsin.get('B0VIEWVOSK')?.adType, 'sponsoredProducts');
assert.equal(influencerByAsin.get('B0OWLLABS1')?.adType, 'sponsoredProducts');
assert.equal(
influencerPicks.some((item) => /related searches/i.test(item.title)),
false,
);
const shopByTypeDom = new JSDOM(
`
<main id="search">
<div class="s-main-slot">
<article data-component-type="s-search-result" data-asin="B0ORGCAM01">
<h2>Organic camera</h2>
<a href="/dp/B0ORGCAM01"><img class="s-image" src="https://example.com/org-cam.jpg" /></a>
</article>
<div class="sbv-container AdHolder">
<span>Sponsored</span>
<video></video>
<a href="/stores/stealthcam"><span class="brand-logo">STEALTH CAM</span></a>
<div class="sb-headline">STEALTH CAM</div>
<div data-asin="B0STEALTH1" class="sbv-product">
<h2>Stealth Cam trail camera</h2>
<a href="/sspa/click?url=%2Fdp%2FB0STEALTH1">
<img src="https://example.com/stealth.jpg" />
</a>
</div>
</div>
<div
class="s-widget-container celwidget slot=MAIN template=SHOP_BY_TYPE widget=shop-by-type-desktop"
cel_widget_id="MAIN-SHOP_BY_TYPE-8"
>
<h2>Shop cameras by type</h2>
<span>Sponsored</span>
<div class="shop-by-type-column">
<h3>security system</h3>
<div>
<h2>Night Owl 2-Way Audio</h2>
<a href="https://redirect.amazon.com/x/c/abc123?url=%2Fdp%2FB0NIGHTOWL">
<img
src="https://images-na.ssl-images-amazon.com/images/G/01/x-locale/common/grey-pixel.gif"
data-src="https://example.com/nightowl-type.jpg"
/>
</a>
<span class="a-price"><span class="a-offscreen">$549.99</span></span>
</div>
</div>
<div class="shop-by-type-column">
<h3>webcam</h3>
<div>
<h2>Prisual PTZ Camera</h2>
<a href="https://redirect.amazon.com/x/c/def456?url=%2Fdp%2FB0PRISUAL1">
<img
src="https://images-na.ssl-images-amazon.com/images/G/01/x-locale/common/grey-pixel.gif"
data-src="https://example.com/prisual-type.jpg"
/>
</a>
<span class="a-price"><span class="a-offscreen">$494.09</span></span>
</div>
</div>
<div class="shop-by-type-column">
<h3>instant print</h3>
<div>
<h2>Kids Instant Print Camera</h2>
<a href="https://redirect.amazon.com/x/c/ghi789?url=%2Fdp%2FB0KIDINST1">
<img data-src="https://example.com/instant-type.jpg" />
</a>
</div>
</div>
<div class="shop-by-type-column">
<h3>point and shoot</h3>
<div>
<h2>Digital Camera 4K</h2>
<a href="https://redirect.amazon.com/x/c/jkl012?url=%2Fdp%2FB0POINTSH1">
<img data-src="https://example.com/pointshoot-type.jpg" />
</a>
</div>
</div>
<div class="shop-by-type-column">
<h3>film camera</h3>
<div>
<h2>Kodak EKTAR H35N</h2>
<a href="https://direct.amazon.com/x/c/QmToken/https://www.amazon.com/dp/B0KODAKH35">
<img data-src="https://example.com/kodak-type.jpg" />
</a>
</div>
</div>
</div>
</div>
</main>
`,
{ url: 'https://www.amazon.com/s?k=camera' },
);
Object.assign(globalThis, {
document: shopByTypeDom.window.document,
Node: shopByTypeDom.window.Node,
Element: shopByTypeDom.window.Element,
HTMLElement: shopByTypeDom.window.HTMLElement,
HTMLAnchorElement: shopByTypeDom.window.HTMLAnchorElement,
HTMLImageElement: shopByTypeDom.window.HTMLImageElement,
});
const shopByType = await collectAmazonSearchPageData({ pattern: 'pattern-2' });
const shopByTypeByAsin = new Map(shopByType.map((item) => [item.asin, item]));
const shopByTypeAsins = ['B0NIGHTOWL', 'B0PRISUAL1', 'B0KIDINST1', 'B0POINTSH1', 'B0KODAKH35'];
const shopByTypeItems = shopByTypeAsins.map((asin) => shopByTypeByAsin.get(asin));
assert.ok(shopByTypeItems.filter(Boolean).length > 0, 'Shop by type cards must be collected');
assert.equal(shopByTypeItems.filter(Boolean).length, 5);
assert.equal(shopByTypeByAsin.get('B0NIGHTOWL')?.asin, 'B0NIGHTOWL');
assert.equal(shopByTypeByAsin.get('B0NIGHTOWL')?.title, 'Night Owl 2-Way Audio');
assert.equal(
shopByTypeByAsin.get('B0NIGHTOWL')?.imageSrc,
'https://example.com/nightowl-type.jpg',
);
assert.equal(shopByTypeByAsin.get('B0NIGHTOWL')?.price, '$549.99');
assert.equal(shopByTypeByAsin.get('B0PRISUAL1')?.asin, 'B0PRISUAL1');
assert.equal(shopByTypeByAsin.get('B0PRISUAL1')?.title, 'Prisual PTZ Camera');
assert.equal(shopByTypeByAsin.get('B0KIDINST1')?.asin, 'B0KIDINST1');
assert.equal(shopByTypeByAsin.get('B0KIDINST1')?.title, 'Kids Instant Print Camera');
assert.equal(shopByTypeByAsin.get('B0POINTSH1')?.asin, 'B0POINTSH1');
assert.equal(shopByTypeByAsin.get('B0POINTSH1')?.title, 'Digital Camera 4K');
assert.equal(shopByTypeByAsin.get('B0KODAKH35')?.asin, 'B0KODAKH35');
assert.equal(shopByTypeByAsin.get('B0KODAKH35')?.title, 'Kodak EKTAR H35N');
assert.ok(shopByTypeItems.every((item) => item?.asin && item.title && item.imageSrc));
assert.equal(shopByTypeByAsin.get('B0ORGCAM01')?.adType, 'organic');
assert.equal(shopByTypeByAsin.get('B0STEALTH1')?.adType, 'sponsoredBrands');
assert.equal(shopByTypeByAsin.get('B0STEALTH1')?.adFormat, 'video');
assert.ok(shopByTypeItems.every((item) => item?.adType === 'sponsoredProducts'));
assert.equal(
shopByTypeByAsin.get('B0NIGHTOWL')?.placementId,
shopByTypeByAsin.get('B0PRISUAL1')?.placementId,
);
assert.equal(shopByTypeByAsin.get('B0NIGHTOWL')?.adProductIndex, 1);
assert.equal(shopByTypeByAsin.get('B0PRISUAL1')?.adProductIndex, 2);
assert.equal(
shopByType.some((item) =>
/^(security system|webcam|instant print|point and shoot|film camera)$/i.test(item.title),
),
false,
);
const organicShopByTypeDom = new JSDOM(
`
<main id="search">
<div class="s-main-slot">
<article data-component-type="s-search-result" data-asin="B0ORGTYPE0">
<h2>Organic camera</h2>
<a href="/dp/B0ORGTYPE0"><img class="s-image" src="https://example.com/org-type0.jpg" /></a>
</article>
<div
class="s-widget-container celwidget slot=MAIN template=SHOP_BY_TYPE widget=shop-by-type-desktop"
cel_widget_id="MAIN-SHOP_BY_TYPE-9"
>
<h2>Shop cameras by type</h2>
<div class="shop-by-type-column">
<h3>webcam</h3>
<div data-asin="B0ORGTYPE1">
<h2>Organic type webcam</h2>
<a href="/dp/B0ORGTYPE1">
<img class="s-image" src="https://example.com/org-type1.jpg" />
</a>
</div>
</div>
<div class="shop-by-type-column">
<h3>film camera</h3>
<div>
<h2>Organic type film camera</h2>
<a href="https://redirect.amazon.com/x/c/org?url=%2Fdp%2FB0ORGTYPE2">
<img data-src="https://example.com/org-type2.jpg" />
</a>
</div>
</div>
</div>
</div>
</main>
`,
{ url: 'https://www.amazon.com/s?k=camera' },
);
Object.assign(globalThis, {
document: organicShopByTypeDom.window.document,
Node: organicShopByTypeDom.window.Node,
Element: organicShopByTypeDom.window.Element,
HTMLElement: organicShopByTypeDom.window.HTMLElement,
HTMLAnchorElement: organicShopByTypeDom.window.HTMLAnchorElement,
HTMLImageElement: organicShopByTypeDom.window.HTMLImageElement,
});
const organicShopByType = await collectAmazonSearchPageData({ pattern: 'pattern-2' });
const organicShopByTypeByAsin = new Map(organicShopByType.map((item) => [item.asin, item]));
assert.equal(organicShopByTypeByAsin.get('B0ORGTYPE1')?.asin, 'B0ORGTYPE1');
assert.equal(organicShopByTypeByAsin.get('B0ORGTYPE1')?.title, 'Organic type webcam');
assert.equal(organicShopByTypeByAsin.get('B0ORGTYPE2')?.asin, 'B0ORGTYPE2');
assert.equal(organicShopByTypeByAsin.get('B0ORGTYPE2')?.title, 'Organic type film camera');
assert.equal(organicShopByTypeByAsin.get('B0ORGTYPE0')?.adType, 'organic');
assert.equal(organicShopByTypeByAsin.get('B0ORGTYPE1')?.adType, 'organic');
assert.equal(organicShopByTypeByAsin.get('B0ORGTYPE2')?.adType, 'organic');
assert.equal(
organicShopByType.some(
(item) => item.adType === 'sponsoredProducts' || item.adType === 'sponsoredBrands',
),
false,
);
const unknownTemplatesDom = new JSDOM(
`
<main id="search">
<div class="s-main-slot">
<article data-component-type="s-search-result" data-asin="B0GENORG01">
<h2>Generic organic product</h2>
<a href="/dp/B0GENORG01"><img src="https://example.com/generic-organic.jpg" /></a>
</article>
<section class="future-layout-alpha" data-ad-details='{"adType":"sponsoredProducts"}'>
<h2>A heading that is not Shop by type</h2>
<span aria-label="Sponsored">Sponsored</span>
<div class="future-card-one">
<h3>Future SP one</h3>
<a href="https://redirect.amazon.com/x/c/new1?url=%2Fdp%2FB0GENSP001">
<img src="https://example.com/generic-sp-1.jpg" />
</a>
</div>
<div class="future-card-two">
<h3>Future SP two</h3>
<a href="https://direct.amazon.com/x/c/new2/https://www.amazon.com/dp/B0GENSP002">
<img src="https://example.com/generic-sp-2.jpg" />
</a>
</div>
</section>
<section class="future-layout-beta" data-component-type="sponsored-brand-vNext">
<span title="Sponsored">Sponsored</span>
<a href="/stores/future-brand"><span class="brand-logo">Future Brand</span></a>
<h2 class="future-copy">Future Brand collection</h2>
<div data-asin="B0GENSB001">
<h3>Future SB product</h3>
<a href="/dp/B0GENSB001"><img src="https://example.com/generic-sb.jpg" /></a>
</div>
</section>
<section class="future-layout-gamma" data-ad-details='{"adType":"displayAd"}'>
<span aria-label="Sponsored">Sponsored</span>
<div data-asin="B0GENSD001">
<h3>Future SD product</h3>
<a href="/dp/B0GENSD001"><img src="https://example.com/generic-sd.jpg" /></a>
</div>
</section>
</div>
</main>
`,
{ url: 'https://www.amazon.com/s?k=future-layout' },
);
Object.assign(globalThis, {
document: unknownTemplatesDom.window.document,
Node: unknownTemplatesDom.window.Node,
Element: unknownTemplatesDom.window.Element,
HTMLElement: unknownTemplatesDom.window.HTMLElement,
HTMLAnchorElement: unknownTemplatesDom.window.HTMLAnchorElement,
HTMLImageElement: unknownTemplatesDom.window.HTMLImageElement,
});
const unknownTemplates = await collectAmazonSearchPageData({ pattern: 'pattern-2' });
const unknownTemplatesByAsin = new Map(unknownTemplates.map((item) => [item.asin, item]));
assert.equal(unknownTemplatesByAsin.get('B0GENORG01')?.adType, 'organic');
assert.equal(unknownTemplatesByAsin.get('B0GENSP001')?.adType, 'sponsoredProducts');
assert.equal(unknownTemplatesByAsin.get('B0GENSP002')?.adType, 'sponsoredProducts');
assert.equal(
unknownTemplatesByAsin.get('B0GENSP001')?.placementId,
unknownTemplatesByAsin.get('B0GENSP002')?.placementId,
);
assert.equal(unknownTemplatesByAsin.get('B0GENSB001')?.adType, 'sponsoredBrands');
assert.equal(unknownTemplatesByAsin.get('B0GENSD001')?.adType, 'sponsoredDisplay');
assert.ok(
['B0GENSP001', 'B0GENSP002', 'B0GENSB001', 'B0GENSD001'].every((asin) =>
unknownTemplatesByAsin.has(asin),
),
'Visible SP/SB/SD products must be collected even when their wrapper classes are unknown',
);
console.log('Amazon search parser fixtures passed.');
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});