Skip to content

Instantly share code, notes, and snippets.

@jafaircl
Last active September 25, 2018 20:58
Show Gist options
  • Save jafaircl/392004e0c46cec85394d2b486b0d48c2 to your computer and use it in GitHub Desktop.
Save jafaircl/392004e0c46cec85394d2b486b0d48c2 to your computer and use it in GitHub Desktop.
AdWords Ad Testing Script
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;
}
@jafaircl
Copy link
Author

jafaircl commented Sep 25, 2018

Uses a custom API solution. The returned data looks like this:

{
  "control": {
    "id": ...,
    "headline1": ...,
    "headline2": ...,
    "description": ...,
    "path1": ...,
    "path2": ...,
    "engaged": 323,
    "delivered": 6325
  },
  "results": [
    {
      "id": ...,
      "headline1": ...,
      "headline2": ...,
      "description": ...,
      "path1": ...,
      "path2": ...,
      "engaged": 17,
      "delivered": 507,
      "probability": 0.042743504500878535,
      "expectedLoss": 0.0008050480976888296
    },
   ...
  ]
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment