← back to Dear Bubbe Nextjs
archived/tests/purge-cache-puppeteer.js
254 lines
#!/usr/bin/env node
/**
* Cloudflare Cache Purge with Puppeteer
* Automates login and cache clearing using browser automation
*/
require('dotenv').config({ path: '.env.local' });
const puppeteer = require('puppeteer');
const CONFIG = {
email: process.env.CLOUDFLARE_EMAIL,
password: process.env.CLOUDFLARE_PASSWORD,
accountId: process.env.CLOUDFLARE_ACCOUNT_ID,
domain: process.env.CLOUDFLARE_DOMAIN,
headless: true, // Set to false for debugging
timeout: 60000,
};
// Validate environment variables
function validateConfig() {
const missing = [];
if (!CONFIG.email) missing.push('CLOUDFLARE_EMAIL');
if (!CONFIG.password) missing.push('CLOUDFLARE_PASSWORD');
if (!CONFIG.accountId) missing.push('CLOUDFLARE_ACCOUNT_ID');
if (!CONFIG.domain) missing.push('CLOUDFLARE_DOMAIN');
if (missing.length > 0) {
console.error('❌ Missing required environment variables in .env.local:');
missing.forEach(v => console.error(` - ${v}`));
process.exit(1);
}
}
async function purgeCloudflareCache() {
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log(' Cloudflare Cache Purge (Puppeteer)');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log('');
validateConfig();
const browser = await puppeteer.launch({
headless: CONFIG.headless,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
],
});
let page;
try {
page = await browser.newPage();
// Set viewport
await page.setViewport({ width: 1920, height: 1080 });
// Set longer timeout
page.setDefaultTimeout(CONFIG.timeout);
console.log('→ Navigating to Cloudflare login...');
await page.goto('https://dash.cloudflare.com/login', {
waitUntil: 'networkidle2',
});
console.log('→ Entering credentials...');
// Wait for email input and enter email
await page.waitForSelector('input[type="email"]');
await page.type('input[type="email"]', CONFIG.email, { delay: 50 });
// Wait for password input and enter password
await page.waitForSelector('input[type="password"]');
await page.type('input[type="password"]', CONFIG.password, { delay: 50 });
console.log('→ Submitting login form...');
// Click login button
await page.click('button[type="submit"]');
// Wait for navigation after login
console.log('→ Waiting for login to complete...');
await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 30000 });
// Check if we need to handle 2FA or security challenge
const currentUrl = page.url();
if (currentUrl.includes('challenge') || currentUrl.includes('verify')) {
console.log('⚠️ Security challenge detected!');
console.log(' You may need to manually verify the login.');
console.log(' Opening browser in non-headless mode...');
// If headless, we need to abort and tell user to run with headless: false
if (CONFIG.headless) {
throw new Error('Security challenge detected. Please run with headless: false to complete verification manually.');
}
// Wait for user to complete challenge
console.log(' Please complete the security challenge in the browser...');
await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 120000 });
}
console.log('✓ Login successful');
// Navigate to the domain's caching page
const cachingUrl = `https://dash.cloudflare.com/${CONFIG.accountId}/${CONFIG.domain}/caching/configuration`;
console.log(`→ Navigating to caching configuration...`);
await page.goto(cachingUrl, { waitUntil: 'networkidle2' });
// Wait a moment for the page to fully load
await page.waitForTimeout(2000);
console.log('→ Looking for "Purge Everything" button...');
// Try multiple selectors for the purge button
const purgeButtonSelectors = [
'button[data-testid="purge-everything-btn"]',
'button:has-text("Purge Everything")',
'button >> text=Purge Everything',
'button >> text=Purge',
];
let purgeButton = null;
for (const selector of purgeButtonSelectors) {
try {
purgeButton = await page.$(selector);
if (purgeButton) {
console.log(`✓ Found button with selector: ${selector}`);
break;
}
} catch (e) {
// Continue to next selector
}
}
if (!purgeButton) {
// Try finding by text content
const buttons = await page.$$('button');
for (const button of buttons) {
const text = await page.evaluate(el => el.textContent, button);
if (text && text.includes('Purge Everything')) {
purgeButton = button;
console.log('✓ Found button by text content');
break;
}
}
}
if (!purgeButton) {
console.log('⚠️ Could not find "Purge Everything" button');
console.log(' Taking screenshot for debugging...');
await page.screenshot({ path: '/tmp/cloudflare-purge-error.png', fullPage: true });
throw new Error('Purge Everything button not found. Screenshot saved to /tmp/cloudflare-purge-error.png');
}
console.log('→ Clicking "Purge Everything" button...');
await purgeButton.click();
// Wait for confirmation dialog
await page.waitForTimeout(1000);
console.log('→ Looking for confirmation button...');
// Try to find and click the confirmation button
const confirmSelectors = [
'button[data-testid="confirmation-modal-confirm"]',
'button >> text=Purge',
'button >> text=Confirm',
'button:has-text("Purge")',
];
let confirmButton = null;
for (const selector of confirmSelectors) {
try {
confirmButton = await page.$(selector);
if (confirmButton) {
console.log(`✓ Found confirmation with selector: ${selector}`);
break;
}
} catch (e) {
// Continue
}
}
if (confirmButton) {
console.log('→ Confirming purge...');
await confirmButton.click();
await page.waitForTimeout(2000);
console.log('✓ Cache purge initiated');
} else {
console.log('⚠️ Could not find confirmation button, purge may require manual confirmation');
await page.screenshot({ path: '/tmp/cloudflare-confirm-error.png', fullPage: true });
}
// Check for success message
await page.waitForTimeout(2000);
const pageContent = await page.content();
if (pageContent.includes('success') || pageContent.includes('purged') || pageContent.includes('cleared')) {
console.log('✓ Cache purged successfully');
} else {
console.log('⚠️ Purge status unclear, please verify manually');
}
// Take final screenshot
console.log('→ Taking final screenshot...');
await page.screenshot({ path: '/tmp/cloudflare-purge-final.png', fullPage: true });
console.log('✓ Screenshot saved to /tmp/cloudflare-purge-final.png');
} catch (error) {
console.error('');
console.error('❌ Error during cache purge:');
console.error(' ', error.message);
if (page) {
try {
await page.screenshot({ path: '/tmp/cloudflare-error.png', fullPage: true });
console.error(' Screenshot saved to /tmp/cloudflare-error.png');
} catch (e) {
// Ignore screenshot errors
}
}
throw error;
} finally {
if (browser) {
await browser.close();
}
}
console.log('');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log(' ✓ Puppeteer automation completed');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log('');
}
// Run if called directly
if (require.main === module) {
purgeCloudflareCache()
.then(() => {
console.log('✓ Success');
process.exit(0);
})
.catch(error => {
console.error('✗ Failed:', error.message);
process.exit(1);
});
}
module.exports = { purgeCloudflareCache };