Force new git SHA to avoid CI scientific notation bug. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
212 lines
6.9 KiB
TypeScript
212 lines
6.9 KiB
TypeScript
/**
|
|
* Find and interact with "load more brands" selector
|
|
*/
|
|
|
|
import puppeteer, { Page } from 'puppeteer';
|
|
|
|
const STORE_ID = 'best';
|
|
|
|
async function sleep(ms: number): Promise<void> {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
async function bypassAgeGate(page: Page): Promise<void> {
|
|
const ageGate = await page.$('[data-testid="age-gate-modal"]');
|
|
if (ageGate) {
|
|
console.log(' Age gate detected, bypassing...');
|
|
const btn = await page.$('[data-testid="age-gate-submit-button"]');
|
|
if (btn) await btn.click();
|
|
await sleep(2000);
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
console.log('='.repeat(60));
|
|
console.log('Finding "Load More Brands" control');
|
|
console.log('='.repeat(60));
|
|
|
|
const browser = await puppeteer.launch({
|
|
headless: true,
|
|
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
|
});
|
|
|
|
const page = await browser.newPage();
|
|
await page.setViewport({ width: 1920, height: 1080 });
|
|
|
|
// Don't block stylesheets - might affect layout
|
|
await page.setRequestInterception(true);
|
|
page.on('request', (req) => {
|
|
if (['image', 'font', 'media'].includes(req.resourceType())) {
|
|
req.abort();
|
|
} else {
|
|
req.continue();
|
|
}
|
|
});
|
|
|
|
const url = `https://${STORE_ID}.treez.io/onlinemenu/brands?customerType=ADULT`;
|
|
console.log(`\nNavigating to ${url}`);
|
|
|
|
await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });
|
|
await sleep(3000);
|
|
await bypassAgeGate(page);
|
|
await sleep(2000);
|
|
|
|
// Find all selects and dropdowns
|
|
console.log('\n[1] Looking for select elements...');
|
|
|
|
const selectInfo = await page.evaluate(() => {
|
|
const results: any[] = [];
|
|
|
|
// Native select elements
|
|
document.querySelectorAll('select').forEach((sel, i) => {
|
|
const options = Array.from(sel.options).map(o => ({ value: o.value, text: o.text }));
|
|
results.push({
|
|
type: 'select',
|
|
id: sel.id || `select-${i}`,
|
|
class: sel.className,
|
|
options: options.slice(0, 10),
|
|
totalOptions: sel.options.length,
|
|
});
|
|
});
|
|
|
|
return results;
|
|
});
|
|
|
|
console.log('Native selects found:', JSON.stringify(selectInfo, null, 2));
|
|
|
|
// Look for custom dropdown buttons
|
|
console.log('\n[2] Looking for dropdown/button elements...');
|
|
|
|
const dropdownInfo = await page.evaluate(() => {
|
|
const results: any[] = [];
|
|
|
|
// Look for common dropdown patterns
|
|
const selectors = [
|
|
'[class*="dropdown"]',
|
|
'[class*="Dropdown"]',
|
|
'[class*="select"]',
|
|
'[class*="Select"]',
|
|
'[class*="picker"]',
|
|
'[class*="Picker"]',
|
|
'[role="listbox"]',
|
|
'[role="combobox"]',
|
|
'button[aria-haspopup]',
|
|
'[class*="brand"] button',
|
|
'[class*="Brand"] button',
|
|
'[class*="filter"]',
|
|
'[class*="Filter"]',
|
|
];
|
|
|
|
selectors.forEach(sel => {
|
|
document.querySelectorAll(sel).forEach((el, i) => {
|
|
const text = el.textContent?.trim().slice(0, 100) || '';
|
|
const className = el.className?.toString?.().slice(0, 100) || '';
|
|
if (text.toLowerCase().includes('brand') || text.toLowerCase().includes('more') || text.toLowerCase().includes('all')) {
|
|
results.push({
|
|
selector: sel,
|
|
tag: el.tagName,
|
|
class: className,
|
|
text: text.slice(0, 50),
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
return results;
|
|
});
|
|
|
|
console.log('Dropdown-like elements:', JSON.stringify(dropdownInfo.slice(0, 10), null, 2));
|
|
|
|
// Look for any element containing "brand" text
|
|
console.log('\n[3] Looking for elements with "brand" or "more" text...');
|
|
|
|
const brandTextElements = await page.evaluate(() => {
|
|
const results: any[] = [];
|
|
const textContent = ['brand', 'more', 'load', 'view all', 'show all'];
|
|
|
|
document.querySelectorAll('button, a, [role="button"], select, [class*="select"]').forEach(el => {
|
|
const text = el.textContent?.toLowerCase() || '';
|
|
if (textContent.some(t => text.includes(t))) {
|
|
results.push({
|
|
tag: el.tagName,
|
|
class: el.className?.toString?.().slice(0, 80) || '',
|
|
text: el.textContent?.trim().slice(0, 100) || '',
|
|
href: el.getAttribute('href') || '',
|
|
});
|
|
}
|
|
});
|
|
|
|
return results;
|
|
});
|
|
|
|
console.log('Elements with brand/more text:', JSON.stringify(brandTextElements.slice(0, 15), null, 2));
|
|
|
|
// Count current brand sections
|
|
console.log('\n[4] Counting brand sections...');
|
|
|
|
const brandSections = await page.evaluate(() => {
|
|
// Look for brand section headers or containers
|
|
const sections: { title: string; productCount: number }[] = [];
|
|
|
|
document.querySelectorAll('[class*="products_product__section"]').forEach(section => {
|
|
const header = section.querySelector('h2, h3, [class*="heading"]');
|
|
const title = header?.textContent?.trim() || 'Unknown';
|
|
const products = section.querySelectorAll('a[class*="product_product__"]');
|
|
sections.push({ title, productCount: products.length });
|
|
});
|
|
|
|
return sections;
|
|
});
|
|
|
|
console.log(`Found ${brandSections.length} brand sections:`);
|
|
brandSections.slice(0, 20).forEach(s => console.log(` - ${s.title}: ${s.productCount} products`));
|
|
|
|
// Take a screenshot
|
|
await page.screenshot({ path: '/tmp/treez-brands-full.png', fullPage: true });
|
|
console.log('\n[5] Full page screenshot saved to /tmp/treez-brands-full.png');
|
|
|
|
// Try scrolling to bottom to trigger any lazy loading
|
|
console.log('\n[6] Scrolling to load more content...');
|
|
|
|
let previousHeight = 0;
|
|
for (let i = 0; i < 20; i++) {
|
|
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
|
await sleep(1500);
|
|
|
|
const currentHeight = await page.evaluate(() => document.body.scrollHeight);
|
|
const sectionCount = await page.evaluate(() =>
|
|
document.querySelectorAll('[class*="products_product__section"]').length
|
|
);
|
|
|
|
console.log(` Scroll ${i + 1}: height=${currentHeight}, sections=${sectionCount}`);
|
|
|
|
if (currentHeight === previousHeight) {
|
|
console.log(' No new content, stopping');
|
|
break;
|
|
}
|
|
previousHeight = currentHeight;
|
|
}
|
|
|
|
// Final count
|
|
const finalSections = await page.evaluate(() => {
|
|
const sections: { title: string; productCount: number }[] = [];
|
|
document.querySelectorAll('[class*="products_product__section"]').forEach(section => {
|
|
const header = section.querySelector('h2, h3, [class*="heading"]');
|
|
const title = header?.textContent?.trim() || 'Unknown';
|
|
const products = section.querySelectorAll('a[class*="product_product__"]');
|
|
sections.push({ title, productCount: products.length });
|
|
});
|
|
return sections;
|
|
});
|
|
|
|
console.log(`\n[7] After scrolling: ${finalSections.length} brand sections`);
|
|
finalSections.forEach(s => console.log(` - ${s.title}: ${s.productCount} products`));
|
|
|
|
const totalProducts = finalSections.reduce((sum, s) => sum + s.productCount, 0);
|
|
console.log(`\nTotal products across all sections: ${totalProducts}`);
|
|
|
|
await browser.close();
|
|
}
|
|
|
|
main().catch(console.error);
|