Comprehensive guide to testing your Google Ads scripts safely before deploying to live campaigns and debugging common issues.
Preview mode shows exactly what your script will do without making any actual changes. This is your most important testing tool.
// Bad: Minimal information function main() { Logger.log("Starting"); const campaigns = AdsApp.campaigns().get(); while (campaigns.hasNext()) { const campaign = campaigns.next(); // No logging of what's happening campaign.setBudget(1000); } Logger.log("Done"); }
// Good: Detailed progress tracking function main() { Logger.log("=== Budget Optimization Started ==="); Logger.log(`Target ROAS: ${TARGET_ROAS}`); const campaigns = AdsApp.campaigns() .withCondition('Status = ENABLED') .get(); Logger.log(`Found ${campaigns.totalNumEntities()} campaigns to process`); let processed = 0; while (campaigns.hasNext()) { const campaign = campaigns.next(); const oldBudget = campaign.getBudget(); Logger.log(`Processing: ${campaign.getName()}`); Logger.log(` Current budget: €${oldBudget}`); const newBudget = calculateOptimalBudget(campaign); campaign.setBudget(newBudget); processed++; Logger.log(` New budget: €${newBudget} (change: ${((newBudget/oldBudget-1)*100).toFixed(1)}%)`); } Logger.log(`=== Completed: ${processed} campaigns optimized ===`); }
// Comprehensive error handling example function main() { try { Logger.log("Starting budget optimization..."); const config = validateConfiguration(); const campaigns = getCampaignsToOptimize(); if (campaigns.totalNumEntities() === 0) { Logger.log("WARNING: No campaigns found matching criteria"); return; } let processed = 0; let errors = 0; while (campaigns.hasNext()) { try { const campaign = campaigns.next(); // Validate campaign state if (!campaign.isEnabled()) { Logger.log(`SKIP: ${campaign.getName()} is not enabled`); continue; } const result = optimizeCampaignBudget(campaign, config); if (result.success) { processed++; Logger.log(`SUCCESS: ${campaign.getName()} - ${result.message}`); } else { errors++; Logger.log(`ERROR: ${campaign.getName()} - ${result.error}`); } } catch (campaignError) { errors++; Logger.log(`CAMPAIGN ERROR: ${campaignError.message}`); // Continue processing other campaigns } } // Final summary Logger.log(`SUMMARY: ${processed} successful, ${errors} errors`); if (errors > 0) { Logger.log("WARNING: Some campaigns could not be optimized. Check logs above."); } } catch (mainError) { Logger.log(`FATAL ERROR: ${mainError.message}`); Logger.log("Stack trace: " + mainError.stack); // Send alert email for critical failures sendErrorNotification(mainError); } } function optimizeCampaignBudget(campaign, config) { try { // Validation const currentBudget = campaign.getBudget(); if (currentBudget <= 0) { return { success: false, error: "Invalid current budget" }; } // Calculate new budget const stats = campaign.getStatsFor('LAST_30_DAYS'); const newBudget = calculateOptimalBudget(stats, config); // Safety checks if (newBudget > config.maxBudget) { return { success: false, error: `Exceeds max budget: €${config.maxBudget}` }; } campaign.setBudget(newBudget); return { success: true, message: `Budget updated: €${currentBudget} → €${newBudget}` }; } catch (error) { return { success: false, error: error.message }; } }
→ Use batching, add progress tracking, limit data scope with .withCondition()
→ Track operation count, process fewer entities, use efficient filtering
→ Check account access level, verify script authorization, test with admin account
→ Add null checks, validate entity exists, handle empty result sets gracefully