- Add backend stale process monitoring API (/api/stale-processes) - Add users management route - Add frontend landing page and stale process monitor UI on /scraper-tools - Move old development scripts to backend/archive/ - Update frontend build with new features 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
168 lines
5.6 KiB
TypeScript
168 lines
5.6 KiB
TypeScript
import puppeteer from 'puppeteer-extra';
|
|
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
|
|
|
|
puppeteer.use(StealthPlugin());
|
|
|
|
async function testPuppeteerClick() {
|
|
let browser;
|
|
|
|
try {
|
|
console.log('🌐 Testing with REAL Puppeteer clicks...');
|
|
console.log('');
|
|
|
|
browser = await puppeteer.launch({
|
|
headless: true,
|
|
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
});
|
|
|
|
const page = await browser.newPage();
|
|
|
|
const url = 'https://curaleaf.com/stores/curaleaf-dispensary-phoenix-airport/brands';
|
|
console.log('Going to:', url);
|
|
|
|
await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });
|
|
await page.waitForTimeout(3000);
|
|
|
|
console.log('Current URL:', page.url());
|
|
|
|
if (page.url().includes('/age-gate')) {
|
|
console.log('\n═══ GATE 1: STATE SELECTOR ═══');
|
|
|
|
// Wait for and click the dropdown button using REAL Puppeteer click
|
|
await page.waitForSelector('button[role="combobox"]', { timeout: 10000 });
|
|
await page.click('button[role="combobox"]');
|
|
console.log('✅ Clicked dropdown with Puppeteer click');
|
|
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Take screenshot of dropdown open
|
|
await page.screenshot({ path: '/tmp/dropdown-open.png' });
|
|
console.log('📸 Screenshot: /tmp/dropdown-open.png');
|
|
|
|
// Wait for options to appear
|
|
await page.waitForSelector('[role="option"]', { timeout: 5000 });
|
|
|
|
// Get all options and find Arizona
|
|
const options = await page.$$('[role="option"]');
|
|
console.log(`Found ${options.length} options`);
|
|
|
|
let arizonaClicked = false;
|
|
for (const option of options) {
|
|
const text = await option.evaluate(el => el.textContent?.toLowerCase().trim());
|
|
if (text === 'arizona') {
|
|
console.log('Found Arizona option, clicking with REAL Puppeteer click...');
|
|
await option.click();
|
|
arizonaClicked = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (arizonaClicked) {
|
|
console.log('✅ Clicked Arizona with Puppeteer click');
|
|
|
|
// Wait for React to update
|
|
await page.waitForTimeout(5000);
|
|
|
|
// Screenshot after click
|
|
await page.screenshot({ path: '/tmp/after-real-click.png' });
|
|
console.log('📸 Screenshot: /tmp/after-real-click.png');
|
|
|
|
console.log('URL after click:', page.url());
|
|
|
|
// Check what buttons are now visible
|
|
const buttons = await page.evaluate(() => {
|
|
const allButtons = Array.from(document.querySelectorAll('button, a'));
|
|
return allButtons.map(btn => ({
|
|
text: btn.textContent?.trim() || '',
|
|
visible: (btn as HTMLElement).offsetParent !== null,
|
|
ariaLabel: btn.getAttribute('aria-label') || '',
|
|
role: btn.getAttribute('role') || ''
|
|
})).filter(b => b.visible && b.text.length > 0 && b.text.length < 100);
|
|
});
|
|
|
|
console.log('\n📋 VISIBLE BUTTONS:');
|
|
console.log(JSON.stringify(buttons, null, 2));
|
|
|
|
// Look for age confirmation button
|
|
console.log('\n═══ GATE 2: AGE CONFIRMATION ═══');
|
|
|
|
const ageButton = buttons.find(btn => {
|
|
const text = btn.text.toLowerCase();
|
|
return text.includes("i'm") ||
|
|
text.includes('21') ||
|
|
text.includes('yes') ||
|
|
text.includes('enter') ||
|
|
text.includes('continue');
|
|
});
|
|
|
|
if (ageButton) {
|
|
console.log('✅ Found age button:', ageButton.text);
|
|
|
|
// Click it with Puppeteer
|
|
const ageButtonClicked = await page.evaluate((btnText) => {
|
|
const buttons = Array.from(document.querySelectorAll('button, a'));
|
|
const btn = buttons.find(b => b.textContent?.trim() === btnText) as HTMLElement;
|
|
if (btn) {
|
|
btn.click();
|
|
return true;
|
|
}
|
|
return false;
|
|
}, ageButton.text);
|
|
|
|
if (ageButtonClicked) {
|
|
console.log('✅ Clicked age button');
|
|
|
|
// Wait for potential navigation or page update
|
|
await page.waitForTimeout(5000);
|
|
console.log('Final URL:', page.url());
|
|
|
|
// Try to scrape brands
|
|
console.log('\n═══ SCRAPING BRANDS ═══');
|
|
|
|
const brands = await page.evaluate(() => {
|
|
const selectors = [
|
|
'[data-testid*="brand"]',
|
|
'[class*="Brand"]',
|
|
'[class*="brand"]',
|
|
'a[href*="/brand/"]'
|
|
];
|
|
|
|
const found = new Set<string>();
|
|
|
|
selectors.forEach(selector => {
|
|
document.querySelectorAll(selector).forEach(el => {
|
|
const text = el.textContent?.trim();
|
|
if (text && text.length > 0 && text.length < 50) {
|
|
found.add(text);
|
|
}
|
|
});
|
|
});
|
|
|
|
return Array.from(found);
|
|
});
|
|
|
|
console.log(`Found ${brands.length} brands`);
|
|
if (brands.length > 0) {
|
|
console.log('─'.repeat(60));
|
|
brands.forEach((b, i) => console.log(` ${i + 1}. ${b}`));
|
|
console.log('─'.repeat(60));
|
|
}
|
|
}
|
|
} else {
|
|
console.log('⚠️ No age confirmation button found');
|
|
console.log('Available buttons:', buttons.map(b => b.text));
|
|
}
|
|
} else {
|
|
console.log('❌ Could not find Arizona option');
|
|
}
|
|
}
|
|
|
|
} catch (error: any) {
|
|
console.error('❌ Error:', error.message);
|
|
} finally {
|
|
if (browser) await browser.close();
|
|
}
|
|
}
|
|
|
|
testPuppeteerClick();
|