Last active
September 25, 2018 20:58
-
-
Save jafaircl/392004e0c46cec85394d2b486b0d48c2 to your computer and use it in GitHub Desktop.
AdWords Ad Testing Script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var emailAddress = '[email protected]'; | |
var useConversionRate = false; | |
var confidenceLevel = 0.95; | |
var thresholdOfCaring = 0.005; | |
var dateRange = 'ALL_TIME'; | |
var minImpressions = 100; | |
var apiUrl = 'https://example.com/ab-test'; | |
function main() { | |
var results = getTestResults(useConversionRate); | |
var html = renderEmail(results); | |
MailApp.sendEmail({ | |
to: emailAddress, | |
subject: 'A/B Test Results For ' + AdWordsApp.currentAccount().getName(), | |
htmlBody: html | |
}); | |
} | |
function renderEmail(results) { | |
return results.reduce(function (str, result) { | |
if (result !== false) { | |
return str + '<h2>' + result.control.campaignName + ' - ' + result.control.adGroupName + '</h2>' + renderAd(result.control) + renderStats(result.control, true) + | |
result.results.reduce(function (_str, _result) { | |
return _str + '<li style="display: inline-block; margin-right: 12px;">' + renderAd(_result) + renderStats(_result, false) + '</li>' | |
}, '<ul style="list-style: none;">') + '</ul>'; | |
} else { | |
return str; | |
} | |
}, '<html><head><style></style></head><body>') + '</body></html>'; | |
} | |
function renderAd(ad) { | |
return '<a href="' + ad.url + '" style="display: block; text-decoration: none; background-color: #fff; padding: 8px; margin: 8px; max-width: 300px; border: 1px solid #878787;">' + | |
'<h3 style="color: #1a0dab; font-size: 18px; font-weight: 400; line-height: 18px; margin: 0;">' + ad.headline1 + ' - ' + ad.headline2 + '</h3>' + | |
'<div style="color: #006621;">' + | |
'<span style="font-size: 11px; line-height: 11px; margin-right: 7px; padding: 1px 3px 0 2px; border: 1px solid #006621; border-radius: 3px;">Ad</span>' + | |
'<cite style="font-size: 14px; line-height: 18px; font-style: normal;">www.example.com/' + ad.path1 + '/' + ad.path2 + '</cite>' + | |
'</div>' + | |
'<p style="margin: 0; color: #545454; font-size: 13px; line-height: 18px;">' + ad.description + '</p>' + | |
'</a>' | |
} | |
function renderStats(ad, isControlAd) { | |
var erate = ad.engaged / ad.delivered; | |
var color; | |
if (ad.expectedLoss < thresholdOfCaring) { | |
if (ad.probability > confidenceLevel) { | |
color = 'green'; | |
} else if (ad.probability < 1 - confidenceLevel) { | |
color = 'red'; | |
} else { | |
color = 'inherit'; | |
} | |
} else { | |
color = 'inherit'; | |
} | |
var str = '<div style="border-radius: 2px; margin: 8px; padding: 8px; max-width: 300px; background-color: ' + color + '; color: ' + (color === 'inherit' ? 'inherit' : '#fff') + ';">' + | |
'<p>' + (useConversionRate ? 'Conversions' : 'Clicks') + ': ' + ad.engaged + '</p>' + | |
'<p>' + (useConversionRate ? 'Clicks' : 'Impressions') + ': ' + ad.delivered + '</p>' + | |
'<p>Enagement Rate: ' + (100 * erate).toFixed(2) + '%</p>'; | |
if (!isControlAd) { | |
str += '<p>Probability: ' + (100 * ad.probability).toFixed(2) + '%</p>'; | |
str += '<p>Expected Loss: ' + (100 * ad.expectedLoss).toFixed(2) + '%</p>'; | |
} | |
str += '</div>'; | |
return str; | |
} | |
function getTestResults(testConversions) { | |
var selector = AdWordsApp.adGroups() | |
.withCondition('AdvertisingChannelType = SEARCH') | |
.withCondition('Status = ENABLED') | |
.withCondition('CampaignStatus = ENABLED') | |
.withCondition('Impressions > ' + minImpressions) | |
.forDateRange(dateRange); | |
return map(selector, function (adGroup) { | |
var body = mapAdGroupAds(adGroup, testConversions ? 'getConversions' : 'getClicks', testConversions ? 'getClicks' : 'getImpressions'); | |
if (body.length > 1) { | |
return JSON.parse(fetchTestResult(body)); | |
} else { | |
return false; | |
} | |
}); | |
} | |
function fetchTestResult(body) { | |
return UrlFetchApp.fetch(apiUrl, { | |
'method': 'post', | |
'payload': { body: JSON.stringify(body) } | |
}).getContentText(); | |
} | |
function mapAdGroupAds(adGroup, engaged, delivered) { | |
var entity = adGroup.ads() | |
.withCondition('Status = ENABLED') | |
.withCondition('Impressions > ' + minImpressions) | |
.forDateRange(dateRange); | |
return map(entity, function (ad) { | |
var stats = ad.getStatsFor(dateRange); | |
return { | |
id: ad.getId(), | |
adGroupName: adGroup.getName(), | |
campaignName: adGroup.getCampaign().getName(), | |
headline1: ad.getHeadlinePart1(), | |
headline2: ad.getHeadlinePart2(), | |
description: ad.getDescription(), | |
path1: ad.getPath1(), | |
path2: ad.getPath2(), | |
url: ad.urls().getFinalUrl(), | |
engaged: stats[engaged](), | |
delivered: stats[delivered]() | |
}; | |
}); | |
} | |
function map(entity, callback) { | |
var retval = []; | |
var iterator = entity.get(); | |
while(iterator.hasNext()) { | |
var item = iterator.next(); | |
retval.push(callback(item)); | |
} | |
return retval; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uses a custom API solution. The returned data looks like this: