Comparing version 0.6.5 to 1.0.0
@@ -18,4 +18,4 @@ /** | ||
/*! autotrack.js v0.6.5 */ | ||
!function t(e,i,n){function r(o,s){if(!i[o]){if(!e[o]){var l="function"==typeof require&&require;if(!s&&l)return l(o,!0);if(a)return a(o,!0);var u=new Error("Cannot find module '"+o+"'");throw u.code="MODULE_NOT_FOUND",u}var d=i[o]={exports:{}};e[o][0].call(d.exports,function(t){var i=e[o][1][t];return r(i?i:t)},d,d.exports,t,e,i,n)}return i[o].exports}for(var a="function"==typeof require&&require,o=0;o<n.length;o++)r(n[o]);return r}({1:[function(t,e,i){e.exports={DEV_ID:"i5iSjo"}},{}],2:[function(t,e,i){function n(t,e){if(window.addEventListener){this.opts=a(e,{attributePrefix:"data-"}),this.tracker=t;var i=this.opts.attributePrefix,n="["+i+"event-category]["+i+"event-action]";this.delegate=r(document,n,"click",this.handleEventClicks.bind(this))}}var r=t("delegate"),a=t("../utilities").defaults,o=t("../provide");n.prototype.handleEventClicks=function(t){var e=t.delegateTarget,i=this.opts.attributePrefix;this.tracker.send("event",{eventCategory:e.getAttribute(i+"event-category"),eventAction:e.getAttribute(i+"event-action"),eventLabel:e.getAttribute(i+"event-label"),eventValue:e.getAttribute(i+"event-value")})},n.prototype.remove=function(){this.delegate.destroy(),this.delegate=null,this.tracker=null,this.opts=null},o("eventTracker",n)},{"../provide":8,"../utilities":9,delegate:13}],3:[function(t,e,i){function n(t,e){window.matchMedia&&(this.opts=o(e,{mediaQueryDefinitions:!1,mediaQueryChangeTemplate:this.changeTemplate,mediaQueryChangeTimeout:1e3}),s(this.opts.mediaQueryDefinitions)&&(this.opts.mediaQueryDefinitions=l(this.opts.mediaQueryDefinitions),this.tracker=t,this.changeListeners=[],this.processMediaQueries()))}function r(t){return c[t]?c[t]:(c[t]=window.matchMedia(t),c[t])}var a=t("debounce"),o=t("../utilities").defaults,s=t("../utilities").isObject,l=t("../utilities").toArray,u=t("../provide"),d="(not set)",c={};n.prototype.processMediaQueries=function(){this.opts.mediaQueryDefinitions.forEach(function(t){if(t.name&&t.dimensionIndex){var e=this.getMatchName(t);this.tracker.set("dimension"+t.dimensionIndex,e),this.addChangeListeners(t)}}.bind(this))},n.prototype.getMatchName=function(t){var e;return t.items.forEach(function(t){r(t.media).matches&&(e=t)}),e?e.name:d},n.prototype.addChangeListeners=function(t){t.items.forEach(function(e){var i=r(e.media),n=a(function(){this.handleChanges(t)}.bind(this),this.opts.mediaQueryChangeTimeout);i.addListener(n),this.changeListeners.push({mql:i,fn:n})}.bind(this))},n.prototype.handleChanges=function(t){var e=this.getMatchName(t),i=this.tracker.get("dimension"+t.dimensionIndex);e!==i&&(this.tracker.set("dimension"+t.dimensionIndex,e),this.tracker.send("event",t.name,"change",this.opts.mediaQueryChangeTemplate(i,e)))},n.prototype.remove=function(){for(var t,e=0;t=this.changeListeners[e];e++)t.mql.removeListener(t.fn);this.changeListeners=null,this.tracker=null,this.opts=null},n.prototype.changeTemplate=function(t,e){return t+" => "+e},u("mediaQueryTracker",n)},{"../provide":8,"../utilities":9,debounce:12}],4:[function(t,e,i){function n(t,e){window.addEventListener&&(this.opts=r(e,{shouldTrackOutboundForm:this.shouldTrackOutboundForm}),this.tracker=t,this.delegate=a(document,"form","submit",this.handleFormSubmits.bind(this)))}var r=t("../utilities").defaults,a=t("delegate"),o=t("../provide"),s=t("../utilities");n.prototype.handleFormSubmits=function(t){var e=t.delegateTarget,i=e.getAttribute("action"),n={transport:"beacon"};this.opts.shouldTrackOutboundForm(e)&&(navigator.sendBeacon||(t.preventDefault(),n.hitCallback=s.withTimeout(function(){e.submit()})),this.tracker.send("event","Outbound Form","submit",i,n))},n.prototype.shouldTrackOutboundForm=function(t){var e=t.getAttribute("action");return e&&0===e.indexOf("http")&&e.indexOf(location.hostname)<0},n.prototype.remove=function(){this.delegate.destroy(),this.delegate=null,this.tracker=null,this.opts=null},o("outboundFormTracker",n)},{"../provide":8,"../utilities":9,delegate:13}],5:[function(t,e,i){function n(t,e){window.addEventListener&&(this.opts=r(e,{shouldTrackOutboundLink:this.shouldTrackOutboundLink}),this.tracker=t,this.delegate=a(document,"a","click",this.handleLinkClicks.bind(this)))}var r=t("../utilities").defaults,a=t("delegate"),o=t("../provide");n.prototype.handleLinkClicks=function(t){var e=t.delegateTarget;this.opts.shouldTrackOutboundLink(e)&&(navigator.sendBeacon||(e.target="_blank"),this.tracker.send("event","Outbound Link","click",e.href,{transport:"beacon"}))},n.prototype.shouldTrackOutboundLink=function(t){return t.hostname!=location.hostname&&0===t.protocol.indexOf("http")},n.prototype.remove=function(){this.delegate.destroy(),this.delegate=null,this.tracker=null,this.opts=null},o("outboundLinkTracker",n)},{"../provide":8,"../utilities":9,delegate:13}],6:[function(t,e,i){function n(t,e){if(window.addEventListener){this.opts=r(e,{attributePrefix:"data-"}),this.tracker=t;var i=this.opts.attributePrefix,n="["+i+"social-network]["+i+"social-action]["+i+"social-target]";this.handleSocialClicks=this.handleSocialClicks.bind(this),this.addWidgetListeners=this.addWidgetListeners.bind(this),this.addTwitterEventHandlers=this.addTwitterEventHandlers.bind(this),this.handleTweetEvents=this.handleTweetEvents.bind(this),this.handleFollowEvents=this.handleFollowEvents.bind(this),this.handleLikeEvents=this.handleLikeEvents.bind(this),this.handleUnlikeEvents=this.handleUnlikeEvents.bind(this),this.delegate=a(document,n,"click",this.handleSocialClicks),"complete"!=document.readyState?window.addEventListener("load",this.addWidgetListeners):this.addWidgetListeners()}}var r=t("../utilities").defaults,a=t("delegate"),o=t("../provide");n.prototype.addWidgetListeners=function(){window.FB&&this.addFacebookEventHandlers(),window.twttr&&this.addTwitterEventHandlers()},n.prototype.handleSocialClicks=function(t){var e=t.delegateTarget,i=this.opts.attributePrefix;this.tracker.send("social",{socialNetwork:e.getAttribute(i+"social-network"),socialAction:e.getAttribute(i+"social-action"),socialTarget:e.getAttribute(i+"social-target")})},n.prototype.addTwitterEventHandlers=function(){try{twttr.ready(function(){twttr.events.bind("tweet",this.handleTweetEvents),twttr.events.bind("follow",this.handleFollowEvents)}.bind(this))}catch(t){}},n.prototype.removeTwitterEventHandlers=function(){try{twttr.ready(function(){twttr.events.unbind("tweet",this.handleTweetEvents),twttr.events.unbind("follow",this.handleFollowEvents)}.bind(this))}catch(t){}},n.prototype.addFacebookEventHandlers=function(){try{FB.Event.subscribe("edge.create",this.handleLikeEvents),FB.Event.subscribe("edge.remove",this.handleUnlikeEvents)}catch(t){}},n.prototype.removeFacebookEventHandlers=function(){try{FB.Event.unsubscribe("edge.create",this.handleLikeEvents),FB.Event.unsubscribe("edge.remove",this.handleUnlikeEvents)}catch(t){}},n.prototype.handleTweetEvents=function(t){if("tweet"==t.region){var e=t.data.url||t.target.getAttribute("data-url")||location.href;this.tracker.send("social","Twitter","tweet",e)}},n.prototype.handleFollowEvents=function(t){if("follow"==t.region){var e=t.data.screen_name||t.target.getAttribute("data-screen-name");this.tracker.send("social","Twitter","follow",e)}},n.prototype.handleLikeEvents=function(t){this.tracker.send("social","Facebook","like",t)},n.prototype.handleUnlikeEvents=function(t){this.tracker.send("social","Facebook","unlike",t)},n.prototype.remove=function(){window.removeEventListener("load",this.addWidgetListeners),this.removeFacebookEventHandlers(),this.removeTwitterEventHandlers(),this.delegate.destroy(),this.delegate=null,this.tracker=null,this.opts=null,this.handleSocialClicks=null,this.addWidgetListeners=null,this.addTwitterEventHandlers=null,this.handleTweetEvents=null,this.handleFollowEvents=null,this.handleLikeEvents=null,this.handleUnlikeEvents=null},o("socialTracker",n)},{"../provide":8,"../utilities":9,delegate:13}],7:[function(t,e,i){function n(t,e){history.pushState&&window.addEventListener&&(this.opts=a(e,{shouldTrackUrlChange:this.shouldTrackUrlChange}),this.tracker=t,this.path=r(),this.updateTrackerData=this.updateTrackerData.bind(this),this.originalPushState=history.pushState,history.pushState=function(t,e){o(t)&&e&&(t.title=e),this.originalPushState.apply(history,arguments),this.updateTrackerData()}.bind(this),this.originalReplaceState=history.replaceState,history.replaceState=function(t,e){o(t)&&e&&(t.title=e),this.originalReplaceState.apply(history,arguments),this.updateTrackerData(!1)}.bind(this),window.addEventListener("popstate",this.updateTrackerData))}function r(){return location.pathname+location.search}var a=t("../utilities").defaults,o=t("../utilities").isObject,s=t("../provide");n.prototype.updateTrackerData=function(t){t=t!==!1,setTimeout(function(){var e=this.path,i=r();e!=i&&this.opts.shouldTrackUrlChange.call(this,i,e)&&(this.path=i,this.tracker.set({page:i,title:o(history.state)&&history.state.title||document.title}),t&&this.tracker.send("pageview"))}.bind(this),0)},n.prototype.shouldTrackUrlChange=function(t,e){return t&&e},n.prototype.remove=function(){window.removeEventListener("popstate",this.updateTrackerData),history.replaceState=this.originalReplaceState,history.pushState=this.originalPushState,this.tracker=null,this.opts=null,this.path=null,this.updateTrackerData=null,this.originalReplaceState=null,this.originalPushState=null},s("urlChangeTracker",n)},{"../provide":8,"../utilities":9}],8:[function(t,e,i){var n=t("./constants"),r=t("./utilities");(window.gaDevIds=window.gaDevIds||[]).push(n.DEV_ID),e.exports=function(t,e){var i=window.GoogleAnalyticsObject||"ga";window[i]=window[i]||function(){(window[i].q=window[i].q||[]).push(arguments)},window[i]("provide",t,e),window.gaplugins=window.gaplugins||{},window.gaplugins[r.capitalize(t)]=e}},{"./constants":1,"./utilities":9}],9:[function(t,e,i){var n={withTimeout:function(t,e){var i=!1;return setTimeout(t,e||2e3),function(){i||(i=!0,t())}},defaults:function(t,e){var i={};"object"!=typeof t&&(t={}),"object"!=typeof e&&(e={});for(var n in e)e.hasOwnProperty(n)&&(i[n]=t.hasOwnProperty(n)?t[n]:e[n]);return i},capitalize:function(t){return t.charAt(0).toUpperCase()+t.slice(1)},isObject:function(t){return"object"==typeof t&&null!==t},isArray:Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)},toArray:function(t){return n.isArray(t)?t:[t]}};e.exports=n},{}],10:[function(t,e,i){var n=t("matches-selector");e.exports=function(t,e,i){for(var r=i?t:t.parentNode;r&&r!==document;){if(n(r,e))return r;r=r.parentNode}}},{"matches-selector":14}],11:[function(t,e,i){function n(){return(new Date).getTime()}e.exports=Date.now||n},{}],12:[function(t,e,i){var n=t("date-now");e.exports=function(t,e,i){function r(){var d=n()-l;e>d&&d>0?a=setTimeout(r,e-d):(a=null,i||(u=t.apply(s,o),a||(s=o=null)))}var a,o,s,l,u;return null==e&&(e=100),function(){s=this,o=arguments,l=n();var d=i&&!a;return a||(a=setTimeout(r,e)),d&&(u=t.apply(s,o),s=o=null),u}}},{"date-now":11}],13:[function(t,e,i){function n(t,e,i,n,a){var o=r.apply(this,arguments);return t.addEventListener(i,o,a),{destroy:function(){t.removeEventListener(i,o,a)}}}function r(t,e,i,n){return function(i){i.delegateTarget=a(i.target,e,!0),i.delegateTarget&&n.call(t,i)}}var a=t("closest");e.exports=n},{closest:10}],14:[function(t,e,i){function n(t,e){if(a)return a.call(t,e);for(var i=t.parentNode.querySelectorAll(e),n=0;n<i.length;++n)if(i[n]==t)return!0;return!1}var r=Element.prototype,a=r.matchesSelector||r.webkitMatchesSelector||r.mozMatchesSelector||r.msMatchesSelector||r.oMatchesSelector;e.exports=n},{}],15:[function(t,e,i){function n(t,e){var i=window[window.GoogleAnalyticsObject||"ga"],n=t.get("name");i(n+".require","eventTracker",e),i(n+".require","mediaQueryTracker",e),i(n+".require","outboundFormTracker",e),i(n+".require","outboundLinkTracker",e),i(n+".require","socialTracker",e),i(n+".require","urlChangeTracker",e)}t("./event-tracker"),t("./media-query-tracker"),t("./outbound-form-tracker"),t("./outbound-link-tracker"),t("./social-tracker"),t("./url-change-tracker");var r=t("../provide");r("autotrack",n)},{"../provide":8,"./event-tracker":2,"./media-query-tracker":3,"./outbound-form-tracker":4,"./outbound-link-tracker":5,"./social-tracker":6,"./url-change-tracker":7}]},{},[15]); | ||
/*! autotrack.js v1.0.0 */ | ||
!function t(e,i,n){function s(o,a){if(!i[o]){if(!e[o]){var l="function"==typeof require&&require;if(!a&&l)return l(o,!0);if(r)return r(o,!0);var c=new Error("Cannot find module '"+o+"'");throw c.code="MODULE_NOT_FOUND",c}var h=i[o]={exports:{}};e[o][0].call(h.exports,function(t){var i=e[o][1][t];return s(i?i:t)},h,h.exports,t,e,i,n)}return i[o].exports}for(var r="function"==typeof require&&require,o=0;o<n.length;o++)s(n[o]);return s}({1:[function(t,e,i){e.exports={VERSION:"1.0.0",DEV_ID:"i5iSjo",VERSION_PARAM:"&_av",USAGE_PARAM:"&_au",NULL_DIMENSION:"(not set)"}},{}],2:[function(t,e,i){function n(){console.error("Requiring the `autotrack` plugin no longer requires all sub-plugins be default. See https://goo.gl/sZ2WrW for details.")}var s=t("../provide");s("autotrack",n)},{"../provide":12}],3:[function(t,e,i){function n(t,e){l.track(t,l.plugins.CLEAN_URL_TRACKER),this.opts=s({stripQuery:!1,queryDimensionIndex:null,indexFilename:null,trailingSlash:null},e),this.tracker=t,this.overrideTrackerBuildHitTask()}var s=t("object-assign"),r=t("dom-utils/lib/parse-url"),o=t("../constants"),a=t("../provide"),l=t("../usage");n.prototype.cleanUrlTask=function(t){var e=t.get("location"),i=t.get("page"),n=r(i||e),s=n.pathname,a=s;if(this.opts.indexFilename){var l=a.split("/");this.opts.indexFilename==l[l.length-1]&&(l[l.length-1]="",a=l.join("/"))}if("remove"==this.opts.trailingSlash)a=a.replace(/\/+$/,"");else if("add"==this.opts.trailingSlash){var c=/\.\w+$/.test(a);c||"/"==a.substr(-1)||(a+="/")}this.opts.stripQuery&&this.opts.queryDimensionIndex&&t.set("dimension"+this.opts.queryDimensionIndex,n.query||o.NULL_DIMENSION),t.set("page",a+(this.opts.stripQuery?"":n.search))},n.prototype.overrideTrackerBuildHitTask=function(){this.originalTrackerBuildHitTask=this.tracker.get("buildHitTask"),this.tracker.set("buildHitTask",function(t){this.cleanUrlTask(t),this.originalTrackerBuildHitTask(t)}.bind(this))},n.prototype.remove=function(){this.tracker.set("sendHitTask",this.originalTrackerSendHitTask)},a("cleanUrlTracker",n)},{"../constants":1,"../provide":12,"../usage":13,"dom-utils/lib/parse-url":22,"object-assign":23}],4:[function(t,e,i){function n(t,e){if(a.track(t,a.plugins.EVENT_TRACKER),window.addEventListener){this.opts=s({events:["click"],fieldsObj:{},attributePrefix:"ga-",hitFilter:null},e),this.tracker=t,this.handleEvents=this.handleEvents.bind(this);var i="["+this.opts.attributePrefix+"on]";this.delegates={},this.opts.events.forEach(function(t){this.delegates[t]=r(document,t,i,this.handleEvents,{deep:!0,useCapture:!0})}.bind(this))}}var s=t("object-assign"),r=t("dom-utils/lib/delegate"),o=t("../provide"),a=t("../usage"),l=t("../utilities").createFieldsObj,c=t("../utilities").getAttributeFields;n.prototype.handleEvents=function(t,e){var i=this.opts.attributePrefix;if(t.type==e.getAttribute(i+"on")){var n={transport:"beacon"},r=c(e,i),o=s({},this.opts.fieldsObj,r),a=r.hitType||"event";this.tracker.send(a,l(n,o,this.tracker,this.opts.hitFilter,e))}},n.prototype.remove=function(){Object.keys(this.delegates).forEach(function(t){this.delegates[t].destroy()}.bind(this))},o("eventTracker",n)},{"../provide":12,"../usage":13,"../utilities":14,"dom-utils/lib/delegate":18,"object-assign":23}],5:[function(t,e,i){function n(t,e){if(a.track(t,a.plugins.IMPRESSION_TRACKER),window.IntersectionObserver&&window.MutationObserver){this.opts=r({elements:[],rootMargin:"0px",fieldsObj:{},attributePrefix:"ga-",hitFilter:null},e),this.tracker=t,this.handleDomMutations=this.handleDomMutations.bind(this),this.walkNodeTree=this.walkNodeTree.bind(this),this.handleIntersectionChanges=this.handleIntersectionChanges.bind(this),this.startObserving=this.startObserving.bind(this),this.observeElement=this.observeElement.bind(this),this.handleDomElementRemoved=this.handleDomElementRemoved.bind(this);var i=this.deriveDataFromConfigOptions();this.items=i.items,this.elementMap=i.elementMap,this.threshold=i.threshold,this.intersectionObserver=this.initIntersectionObserver(),this.mutationObserver=this.initMutationObserver(),c(this.startObserving)}}function s(t,e){if(0===t){var i=e.intersectionRect;return i.top>0||i.bottom>0||i.left>0||i.right>0}return e.intersectionRatio>=t}var r=t("object-assign"),o=t("../provide"),a=t("../usage"),l=t("../utilities").createFieldsObj,c=t("../utilities").domReady,h=t("../utilities").getAttributeFields;n.prototype.deriveDataFromConfigOptions=function(){var t=[],e=[],i={};return this.opts.elements.forEach(function(n){"string"==typeof n&&(n={id:n}),t.push(n=r({threshold:0,trackFirstImpressionOnly:!0},n)),i[n.id]=null,e.push(n.threshold)}),{items:t,elementMap:i,threshold:e}},n.prototype.initMutationObserver=function(){return new MutationObserver(this.handleDomMutations)},n.prototype.initIntersectionObserver=function(){return new IntersectionObserver(this.handleIntersectionChanges,{rootMargin:this.opts.rootMargin,threshold:this.threshold})},n.prototype.startObserving=function(){Object.keys(this.elementMap).forEach(this.observeElement),this.mutationObserver.observe(document.body,{childList:!0,subtree:!0}),requestAnimationFrame(function(){})},n.prototype.observeElement=function(t){var e=this.elementMap[t]||(this.elementMap[t]=document.getElementById(t));e&&this.intersectionObserver.observe(e)},n.prototype.handleDomMutations=function(t){for(var e,i=0;e=t[i];i++){for(var n,s=0;n=e.removedNodes[s];s++)this.walkNodeTree(n,this.handleDomElementRemoved);for(var r,o=0;r=e.addedNodes[o];o++)this.walkNodeTree(r,this.observeElement)}},n.prototype.walkNodeTree=function(t,e){1==t.nodeType&&t.id in this.elementMap&&e(t.id);for(var i,n=0;i=t.childNodes[n];n++)this.walkNodeTree(i,e)},n.prototype.handleIntersectionChanges=function(t){for(var e,i=0;e=t[i];i++)for(var n,r=0;n=this.items[r];r++)e.target.id===n.id&&s(n.threshold,e)&&(this.handleImpression(n.id),n.trackFirstImpressionOnly&&(this.items.splice(r,1),r--,this.possiblyUnobserveElement(n.id)));0===this.items.length&&this.remove()},n.prototype.handleImpression=function(t){var e=document.getElementById(t),i={transport:"beacon",eventCategory:"Viewport",eventAction:"impression",eventLabel:t},n=r({},this.opts.fieldsObj,h(e,this.opts.attributePrefix));this.tracker.send("event",l(i,n,this.tracker,this.opts.hitFilter,e))},n.prototype.possiblyUnobserveElement=function(t){this.itemsIncludesId(t)||(this.intersectionObserver.unobserve(this.elementMap[t]),delete this.elementMap[t])},n.prototype.handleDomElementRemoved=function(t){this.intersectionObserver.unobserve(this.elementMap[t]),this.elementMap[t]=null},n.prototype.itemsIncludesId=function(t){return this.items.some(function(e){return t==e.id})},n.prototype.remove=function(){this.mutationObserver.disconnect(),this.intersectionObserver.disconnect()},o("impressionTracker",n)},{"../provide":12,"../usage":13,"../utilities":14,"object-assign":23}],6:[function(t,e,i){function n(t,e){c.track(t,c.plugins.MEDIA_QUERY_TRACKER),window.matchMedia&&(this.opts=r({definitions:null,changeTemplate:this.changeTemplate,changeTimeout:1e3,fieldsObj:{},hitFilter:null},e),u(this.opts.definitions)&&(this.opts.definitions=d(this.opts.definitions),this.tracker=t,this.changeListeners=[],this.processMediaQueries()))}function s(t){return p[t]?p[t]:(p[t]=window.matchMedia(t),p[t])}var r=t("object-assign"),o=t("debounce"),a=t("../constants"),l=t("../provide"),c=t("../usage"),h=t("../utilities").createFieldsObj,u=t("../utilities").isObject,d=t("../utilities").toArray,p={};n.prototype.processMediaQueries=function(){this.opts.definitions.forEach(function(t){if(t.name&&t.dimensionIndex){var e=this.getMatchName(t);this.tracker.set("dimension"+t.dimensionIndex,e),this.addChangeListeners(t)}}.bind(this))},n.prototype.getMatchName=function(t){var e;return t.items.forEach(function(t){s(t.media).matches&&(e=t)}),e?e.name:a.NULL_DIMENSION},n.prototype.addChangeListeners=function(t){t.items.forEach(function(e){var i=s(e.media),n=o(function(){this.handleChanges(t)}.bind(this),this.opts.changeTimeout);i.addListener(n),this.changeListeners.push({mql:i,fn:n})}.bind(this))},n.prototype.handleChanges=function(t){var e=this.getMatchName(t),i=this.tracker.get("dimension"+t.dimensionIndex);if(e!==i){this.tracker.set("dimension"+t.dimensionIndex,e);var n={eventCategory:t.name,eventAction:"change",eventLabel:this.opts.changeTemplate(i,e)};this.tracker.send("event",h(n,this.opts.fieldsObj,this.tracker,this.opts.hitFilter))}},n.prototype.remove=function(){for(var t,e=0;t=this.changeListeners[e];e++)t.mql.removeListener(t.fn)},n.prototype.changeTemplate=function(t,e){return t+" => "+e},l("mediaQueryTracker",n)},{"../constants":1,"../provide":12,"../usage":13,"../utilities":14,debounce:16,"object-assign":23}],7:[function(t,e,i){function n(t,e){l.track(t,l.plugins.OUTBOUND_FORM_TRACKER),window.addEventListener&&(this.opts=s({formSelector:"form",shouldTrackOutboundForm:this.shouldTrackOutboundForm,fieldsObj:{},attributePrefix:"ga-",hitFilter:null},e),this.tracker=t,this.delegate=r(document,"submit","form",this.handleFormSubmits.bind(this),{deep:!0,useCapture:!0}))}var s=t("object-assign"),r=t("dom-utils/lib/delegate"),o=t("dom-utils/lib/parse-url"),a=t("../provide"),l=t("../usage"),c=t("../utilities").createFieldsObj,h=t("../utilities").getAttributeFields,u=t("../utilities").withTimeout;n.prototype.handleFormSubmits=function(t,e){var i=o(e.action).href,n={transport:"beacon",eventCategory:"Outbound Form",eventAction:"submit",eventLabel:i};if(this.opts.shouldTrackOutboundForm(e,o)){navigator.sendBeacon||(t.preventDefault(),n.hitCallback=u(function(){e.submit()}));var r=s({},this.opts.fieldsObj,h(e,this.opts.attributePrefix));this.tracker.send("event",c(n,r,this.tracker,this.opts.hitFilter,e))}},n.prototype.shouldTrackOutboundForm=function(t,e){var i=e(t.action);return i.hostname!=location.hostname&&"http"==i.protocol.slice(0,4)},n.prototype.remove=function(){this.delegate.destroy()},a("outboundFormTracker",n)},{"../provide":12,"../usage":13,"../utilities":14,"dom-utils/lib/delegate":18,"dom-utils/lib/parse-url":22,"object-assign":23}],8:[function(t,e,i){function n(t,e){l.track(t,l.plugins.OUTBOUND_LINK_TRACKER),window.addEventListener&&(this.opts=s({events:["click"],linkSelector:"a",shouldTrackOutboundLink:this.shouldTrackOutboundLink,fieldsObj:{},attributePrefix:"ga-",hitFilter:null},e),this.tracker=t,this.handleLinkInteractions=this.handleLinkInteractions.bind(this),this.delegates={},this.opts.events.forEach(function(t){this.delegates[t]=r(document,t,this.opts.linkSelector,this.handleLinkInteractions,{deep:!0,useCapture:!0})}.bind(this)))}var s=t("object-assign"),r=t("dom-utils/lib/delegate"),o=t("dom-utils/lib/parse-url"),a=t("../provide"),l=t("../usage"),c=t("../utilities").createFieldsObj,h=t("../utilities").getAttributeFields;n.prototype.handleLinkInteractions=function(t,e){if(this.opts.shouldTrackOutboundLink(e,o)){navigator.sendBeacon||(e.target="_blank");var i={transport:"beacon",eventCategory:"Outbound Link",eventAction:t.type,eventLabel:e.href},n=s({},this.opts.fieldsObj,h(e,this.opts.attributePrefix));this.tracker.send("event",c(i,n,this.tracker,this.opts.hitFilter,e))}},n.prototype.shouldTrackOutboundLink=function(t,e){var i=e(t.href);return i.hostname!=location.hostname&&"http"==i.protocol.slice(0,4)},n.prototype.remove=function(){Object.keys(this.delegates).forEach(function(t){this.delegates[t].destroy()}.bind(this))},a("outboundLinkTracker",n)},{"../provide":12,"../usage":13,"../utilities":14,"dom-utils/lib/delegate":18,"dom-utils/lib/parse-url":22,"object-assign":23}],9:[function(t,e,i){function n(t,e){o.track(t,o.plugins.PAGE_VISIBILITY_TRACKER),window.addEventListener&&(this.opts=s({sessionTimeout:c,changeTemplate:this.changeTemplate,hiddenMetricIndex:null,visibleMetricIndex:null,fieldsObj:{},hitFilter:null},e),this.tracker=t,this.visibilityState=document.visibilityState,this.lastVisibilityChangeTime=+new Date,this.handleVisibilityStateChange=this.handleVisibilityStateChange.bind(this),this.overrideTrackerSendMethod(),this.overrideTrackerSendHitTask(),document.addEventListener("visibilitychange",this.handleVisibilityStateChange))}var s=t("object-assign"),r=t("../provide"),o=t("../usage"),a=t("../utilities").createFieldsObj,l=t("../utilities").isObject,c=30;n.prototype.handleVisibilityStateChange=function(){var t;if(this.prevVisibilityState=this.visibilityState,this.visibilityState=document.visibilityState,this.sessionHasTimedOut()){if("hidden"==this.visibilityState)return;"visible"==this.visibilityState&&(t={transport:"beacon"},this.tracker.send("pageview",a(t,this.opts.fieldsObj,this.tracker,this.opts.hitFilter)))}else{var e=Math.round((new Date-this.lastVisibilityChangeTime)/1e3)||1;t={transport:"beacon",eventCategory:"Page Visibility",eventAction:"change",eventLabel:this.opts.changeTemplate(this.prevVisibilityState,this.visibilityState),eventValue:e},"hidden"==this.visibilityState&&(t.nonInteraction=!0);var i=this.opts[this.prevVisibilityState+"MetricIndex"];i&&(t["metric"+i]=e),this.tracker.send("event",a(t,this.opts.fieldsObj,this.tracker,this.opts.hitFilter))}this.lastVisibilityChangeTime=+new Date},n.prototype.sessionHasTimedOut=function(){var t=(new Date-this.lastHitTime)/6e4;return this.opts.sessionTimeout<t},n.prototype.overrideTrackerSendMethod=function(){this.originalTrackerSendMethod=this.tracker.send,this.tracker.send=function(){var t=Array.prototype.slice.call(arguments),e=t[0],i=l(e)?e.hitType:e,n="pageview"==i;if(!n&&this.sessionHasTimedOut()){var s={transport:"beacon"};this.originalTrackerSendMethod.call(this.tracker,"pageview",a(s,this.opts.fieldsObj,this.tracker,this.opts.hitFilter))}this.originalTrackerSendMethod.apply(this.tracker,t)}.bind(this)},n.prototype.overrideTrackerSendHitTask=function(){this.originalTrackerSendHitTask=this.tracker.get("sendHitTask"),this.lastHitTime=+new Date,this.tracker.set("sendHitTask",function(t){this.originalTrackerSendHitTask(t),this.lastHitTime=+new Date}.bind(this))},n.prototype.changeTemplate=function(t,e){return t+" => "+e},n.prototype.remove=function(){this.tracker.set("sendHitTask",this.originalTrackerSendHitTask),this.tracker.send=this.originalTrackerSendMethod,document.removeEventListener("visibilitychange",this.handleVisibilityStateChange)},r("pageVisibilityTracker",n)},{"../provide":12,"../usage":13,"../utilities":14,"object-assign":23}],10:[function(t,e,i){function n(t,e){o.track(t,o.plugins.SOCIAL_WIDGET_TRACKER),window.addEventListener&&(this.opts=s({fieldsObj:{},hitFilter:null},e),this.tracker=t,this.addWidgetListeners=this.addWidgetListeners.bind(this),this.addTwitterEventHandlers=this.addTwitterEventHandlers.bind(this),this.handleTweetEvents=this.handleTweetEvents.bind(this),this.handleFollowEvents=this.handleFollowEvents.bind(this),this.handleLikeEvents=this.handleLikeEvents.bind(this),this.handleUnlikeEvents=this.handleUnlikeEvents.bind(this),"complete"!=document.readyState?window.addEventListener("load",this.addWidgetListeners):this.addWidgetListeners())}var s=t("object-assign"),r=t("../provide"),o=t("../usage"),a=t("../utilities").createFieldsObj;n.prototype.addWidgetListeners=function(){window.FB&&this.addFacebookEventHandlers(),window.twttr&&this.addTwitterEventHandlers()},n.prototype.addTwitterEventHandlers=function(){try{twttr.ready(function(){twttr.events.bind("tweet",this.handleTweetEvents),twttr.events.bind("follow",this.handleFollowEvents)}.bind(this))}catch(t){}},n.prototype.removeTwitterEventHandlers=function(){try{twttr.ready(function(){twttr.events.unbind("tweet",this.handleTweetEvents),twttr.events.unbind("follow",this.handleFollowEvents)}.bind(this))}catch(t){}},n.prototype.addFacebookEventHandlers=function(){try{FB.Event.subscribe("edge.create",this.handleLikeEvents),FB.Event.subscribe("edge.remove",this.handleUnlikeEvents)}catch(t){}},n.prototype.removeFacebookEventHandlers=function(){try{FB.Event.unsubscribe("edge.create",this.handleLikeEvents),FB.Event.unsubscribe("edge.remove",this.handleUnlikeEvents)}catch(t){}},n.prototype.handleTweetEvents=function(t){if("tweet"==t.region){var e=t.data.url||t.target.getAttribute("data-url")||location.href,i={transport:"beacon",socialNetwork:"Twitter",socialAction:"tweet",socialTarget:e};this.tracker.send("social",a(i,this.opts.fieldsObj,this.tracker,this.opts.hitFilter))}},n.prototype.handleFollowEvents=function(t){if("follow"==t.region){var e=t.data.screen_name||t.target.getAttribute("data-screen-name"),i={transport:"beacon",socialNetwork:"Twitter",socialAction:"follow",socialTarget:e};this.tracker.send("social",a(i,this.opts.fieldsObj,this.tracker,this.opts.hitFilter))}},n.prototype.handleLikeEvents=function(t){var e={transport:"beacon",socialNetwork:"Facebook",socialAction:"like",socialTarget:t};this.tracker.send("social",a(e,this.opts.fieldsObj,this.tracker,this.opts.hitFilter))},n.prototype.handleUnlikeEvents=function(t){var e={transport:"beacon",socialNetwork:"Facebook",socialAction:"unlike",socialTarget:t};this.tracker.send("social",a(e,this.opts.fieldsObj,this.tracker,this.opts.hitFilter))},n.prototype.remove=function(){window.removeEventListener("load",this.addWidgetListeners),this.removeFacebookEventHandlers(),this.removeTwitterEventHandlers()},r("socialWidgetTracker",n)},{"../provide":12,"../usage":13,"../utilities":14,"object-assign":23}],11:[function(t,e,i){function n(t,e){a.track(t,a.plugins.URL_CHANGE_TRACKER),history.pushState&&window.addEventListener&&(this.opts=r({shouldTrackUrlChange:this.shouldTrackUrlChange,fieldsObj:{},hitFilter:null},e),this.tracker=t,this.path=s(),this.updateTrackerData=this.updateTrackerData.bind(this),this.originalPushState=history.pushState,history.pushState=function(t,e){c(t)&&e&&(t.title=e),this.originalPushState.apply(history,arguments),this.updateTrackerData()}.bind(this),this.originalReplaceState=history.replaceState,history.replaceState=function(t,e){c(t)&&e&&(t.title=e),this.originalReplaceState.apply(history,arguments),this.updateTrackerData(!1)}.bind(this),window.addEventListener("popstate",this.updateTrackerData))}function s(){return location.pathname+location.search}var r=t("object-assign"),o=t("../provide"),a=t("../usage"),l=t("../utilities").createFieldsObj,c=t("../utilities").isObject;n.prototype.updateTrackerData=function(t){t=t!==!1,setTimeout(function(){var e=this.path,i=s();if(e!=i&&this.opts.shouldTrackUrlChange.call(this,i,e)&&(this.path=i,this.tracker.set({page:i,title:c(history.state)&&history.state.title||document.title}),t)){var n={transport:"beacon"};this.tracker.send("pageview",l(n,this.opts.fieldsObj,this.tracker,this.opts.hitFilter))}}.bind(this),0)},n.prototype.shouldTrackUrlChange=function(t,e){return t&&e},n.prototype.remove=function(){window.removeEventListener("popstate",this.updateTrackerData),history.replaceState=this.originalReplaceState,history.pushState=this.originalPushState,this.tracker=null,this.opts=null,this.path=null,this.updateTrackerData=null,this.originalReplaceState=null,this.originalPushState=null},o("urlChangeTracker",n)},{"../provide":12,"../usage":13,"../utilities":14,"object-assign":23}],12:[function(t,e,i){var n=t("./constants"),s=t("./utilities");(window.gaDevIds=window.gaDevIds||[]).push(n.DEV_ID),e.exports=function(t,e){var i=window.GoogleAnalyticsObject||"ga";window[i]=window[i]||function(){(window[i].q=window[i].q||[]).push(arguments)},window[i]("provide",t,e),window.gaplugins=window.gaplugins||{},window.gaplugins[s.capitalize(t)]=e}},{"./constants":1,"./utilities":14}],13:[function(t,e,i){function n(t){return parseInt(t||"0",16).toString(2)}function s(t){return parseInt(t||"0",2).toString(16)}function r(t,e){if(t.length<e)for(var i=e-t.length;i;)t="0"+t,i--;return t}function o(t,e){return t.substr(0,e)+1+t.substr(e+1)}function a(t,e){var i=t.get(c.USAGE_PARAM),a=r(n(i),u);a=o(a,u-e),t.set(c.USAGE_PARAM,s(a))}function l(t){t.set(c.VERSION_PARAM,c.VERSION)}var c=t("./constants"),h={CLEAN_URL_TRACKER:1,EVENT_TRACKER:2,IMPRESSION_TRACKER:3,MEDIA_QUERY_TRACKER:4,OUTBOUND_FORM_TRACKER:5,OUTBOUND_LINK_TRACKER:6,PAGE_VISIBILITY_TRACKER:7,SOCIAL_WIDGET_TRACKER:8,URL_CHANGE_TRACKER:9},u=9;e.exports={track:function(t,e){l(t),a(t,e)},plugins:h}},{"./constants":1}],14:[function(t,e,i){var n=t("object-assign"),s=t("dom-utils/lib/get-attributes"),r={createFieldsObj:function(t,e,i,s,r){if("function"==typeof s){var o=i.get("buildHitTask");return{buildHitTask:function(i){i.set(t,null,!0),i.set(e,null,!0),s(i,r),o(i)}}}return n({},t,e)},getAttributeFields:function(t,e){var i=s(t),n={};return Object.keys(i).forEach(function(t){if(0===t.indexOf(e)&&t!=e+"on"){var s=i[t];"true"==s&&(s=!0),"false"==s&&(s=!1);var o=r.camelCase(t.slice(e.length));n[o]=s}}),n},domReady:function(t){"loading"==document.readyState?document.addEventListener("DOMContentLoaded",function e(){document.removeEventListener("DOMContentLoaded",e),t()}):t()},withTimeout:function(t,e){var i=!1;return setTimeout(t,e||2e3),function(){i||(i=!0,t())}},camelCase:function(t){return t.replace(/[\-\_]+(\w?)/g,function(t,e){return e.toUpperCase()})},capitalize:function(t){return t.charAt(0).toUpperCase()+t.slice(1)},isObject:function(t){return"object"==typeof t&&null!==t},isArray:Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)},toArray:function(t){return r.isArray(t)?t:[t]}};e.exports=r},{"dom-utils/lib/get-attributes":19,"object-assign":23}],15:[function(t,e,i){function n(){return(new Date).getTime()}e.exports=Date.now||n},{}],16:[function(t,e,i){var n=t("date-now");e.exports=function(t,e,i){function s(){var h=n()-l;h<e&&h>0?r=setTimeout(s,e-h):(r=null,i||(c=t.apply(a,o),r||(a=o=null)))}var r,o,a,l,c;return null==e&&(e=100),function(){a=this,o=arguments,l=n();var h=i&&!r;return r||(r=setTimeout(s,e)),h&&(c=t.apply(a,o),a=o=null),c}}},{"date-now":15}],17:[function(t,e,i){var n=t("./matches"),s=t("./parents");e.exports=function(t,e,i){if(t&&1==t.nodeType&&e)for(var r,o=(i?[t]:[]).concat(s(t)),a=0;r=o[a];a++)if(n(r,e))return r}},{"./matches":20,"./parents":21}],18:[function(t,e,i){var n=t("./closest"),s=t("./matches");e.exports=function(t,e,i,r,o){o=o||{};var a=function(t){if(o.deep&&"function"==typeof t.deepPath)for(var e,a=t.deepPath(),l=0;e=a[l];l++)1==e.nodeType&&s(e,i)&&(c=e);else var c=n(t.target,i,!0);c&&r.call(c,t,c)};return t.addEventListener(e,a,o.useCapture),{destroy:function(){t.removeEventListener(e,a,o.useCapture)}}}},{"./closest":17,"./matches":20}],19:[function(t,e,i){e.exports=function(t){var e={};if(!t||1!=t.nodeType)return e;var i=t.attributes;if(0===i.length)return{};for(var n,s=0;n=i[s];s++)e[n.name]=n.value;return e}},{}],20:[function(t,e,i){function n(t,e){if("string"!=typeof e)return!1;if(r)return r.call(t,e);for(var i,n=t.parentNode.querySelectorAll(e),s=0;i=n[s];s++)if(i==t)return!0;return!1}var s=Element.prototype,r=s.matches||s.matchesSelector||s.webkitMatchesSelector||s.mozMatchesSelector||s.msMatchesSelector||s.oMatchesSelector;e.exports=function(t,e){if(t&&1==t.nodeType&&e){if("string"==typeof e||1==e.nodeType)return t==e||n(t,e);if("length"in e)for(var i,s=0;i=e[s];s++)if(t==i||n(t,i))return!0}return!1}},{}],21:[function(t,e,i){e.exports=function(t){for(var e=[];t&&t.parentNode&&1==t.parentNode.nodeType;)e.push(t=t.parentNode);return e}},{}],22:[function(t,e,i){var n="80",s="443",r=RegExp(":("+n+"|"+s+")$"),o=document.createElement("a"),a={};e.exports=function l(t){if(t=t&&"."!=t?t:location.href,a[t])return a[t];if(o.href=t,"."==t.charAt(0))return l(o.href);var e=o.protocol&&":"!=o.protocol?o.protocol:location.protocol,i=o.port==n||o.port==s?"":o.port;i="0"==i?"":i;var c=""==o.host?location.host:o.host,h=""==o.hostname?location.hostname:o.hostname;c=c.replace(r,"");var u=o.origin?o.origin:e+"//"+c,d="/"==o.pathname.charAt(0)?o.pathname:"/"+o.pathname;return a[t]={hash:o.hash,host:c,hostname:h,href:o.href,origin:u,pathname:d,port:i,protocol:e,search:o.search,fragment:o.hash.slice(1),path:d+o.search,query:o.search.slice(1)}}},{}],23:[function(t,e,i){"use strict";function n(t){if(null===t||void 0===t)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(t)}function s(){try{if(!Object.assign)return!1;var t=new String("abc");if(t[5]="de","5"===Object.getOwnPropertyNames(t)[0])return!1;for(var e={},i=0;i<10;i++)e["_"+String.fromCharCode(i)]=i;var n=Object.getOwnPropertyNames(e).map(function(t){return e[t]});if("0123456789"!==n.join(""))return!1;var s={};return"abcdefghijklmnopqrst".split("").forEach(function(t){s[t]=t}),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},s)).join("")}catch(r){return!1}}var r=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable;e.exports=s()?Object.assign:function(t,e){for(var i,s,a=n(t),l=1;l<arguments.length;l++){i=Object(arguments[l]);for(var c in i)r.call(i,c)&&(a[c]=i[c]);if(Object.getOwnPropertySymbols){s=Object.getOwnPropertySymbols(i);for(var h=0;h<s.length;h++)o.call(i,s[h])&&(a[s[h]]=i[s[h]])}}return a}},{}],24:[function(t,e,i){t("./plugins/clean-url-tracker"),t("./plugins/event-tracker"),t("./plugins/impression-tracker"),t("./plugins/media-query-tracker"),t("./plugins/outbound-form-tracker"),t("./plugins/outbound-link-tracker"),t("./plugins/page-visibility-tracker"),t("./plugins/social-widget-tracker"),t("./plugins/url-change-tracker"),t("./plugins/autotrack")},{"./plugins/autotrack":2,"./plugins/clean-url-tracker":3,"./plugins/event-tracker":4,"./plugins/impression-tracker":5,"./plugins/media-query-tracker":6,"./plugins/outbound-form-tracker":7,"./plugins/outbound-link-tracker":8,"./plugins/page-visibility-tracker":9,"./plugins/social-widget-tracker":10,"./plugins/url-change-tracker":11}]},{},[24]); | ||
//# sourceMappingURL=autotrack.js.map |
# Changelog | ||
This document lists the changes between each minor and patch versions. For changes between major versions, see the [Upgrade Reference](/docs/upgrading.md) | ||
### 1.0.0 (2016-06-30) | ||
- See the [Upgrade Reference](/docs/upgrading.md) for a full list of change. | ||
### 0.6.5 (2016-04-13) | ||
@@ -4,0 +10,0 @@ |
@@ -19,3 +19,9 @@ /** | ||
module.exports = { | ||
DEV_ID: 'i5iSjo' | ||
VERSION: '1.0.0', | ||
DEV_ID: 'i5iSjo', | ||
VERSION_PARAM: '&_av', | ||
USAGE_PARAM: '&_au', | ||
NULL_DIMENSION: '(not set)' | ||
}; |
@@ -18,9 +18,3 @@ /** | ||
// Imports sub-plugins. | ||
require('./event-tracker'); | ||
require('./media-query-tracker'); | ||
require('./outbound-form-tracker'); | ||
require('./outbound-link-tracker'); | ||
require('./social-tracker'); | ||
require('./url-change-tracker'); | ||
/* eslint no-console: ["error", {allow: ["error"]}] */ | ||
@@ -33,18 +27,9 @@ | ||
/** | ||
* | ||
* Requires all sub-plugins via a single plugin. | ||
* Warns users that this functionality is deprecated as of version 1.0 | ||
* @deprecated | ||
* @constructor | ||
* @param {Object} tracker Passed internally by analytics.js | ||
* @param {?Object} opts Passed by the require command. | ||
*/ | ||
function Autotrack(tracker, opts) { | ||
var ga = window[window.GoogleAnalyticsObject || 'ga']; | ||
var name = tracker.get('name'); | ||
ga(name + '.require', 'eventTracker', opts); | ||
ga(name + '.require', 'mediaQueryTracker', opts); | ||
ga(name + '.require', 'outboundFormTracker', opts); | ||
ga(name + '.require', 'outboundLinkTracker', opts); | ||
ga(name + '.require', 'socialTracker', opts); | ||
ga(name + '.require', 'urlChangeTracker', opts); | ||
function Autotrack() { | ||
console.error('Requiring the `autotrack` plugin no longer requires all ' + | ||
'sub-plugins be default. See https://goo.gl/sZ2WrW for details.'); | ||
} | ||
@@ -51,0 +36,0 @@ |
@@ -18,5 +18,8 @@ /** | ||
var delegate = require('delegate'); | ||
var defaults = require('../utilities').defaults; | ||
var assign = require('object-assign'); | ||
var delegate = require('dom-utils/lib/delegate'); | ||
var provide = require('../provide'); | ||
var usage = require('../usage'); | ||
var createFieldsObj = require('../utilities').createFieldsObj; | ||
var getAttributeFields = require('../utilities').getAttributeFields; | ||
@@ -32,16 +35,27 @@ | ||
usage.track(tracker, usage.plugins.EVENT_TRACKER); | ||
// Feature detects to prevent errors in unsupporting browsers. | ||
if (!window.addEventListener) return; | ||
this.opts = defaults(opts, { | ||
attributePrefix: 'data-' | ||
}); | ||
this.opts = assign({ | ||
events: ['click'], | ||
fieldsObj: {}, | ||
attributePrefix: 'ga-', | ||
hitFilter: null | ||
}, opts); | ||
this.tracker = tracker; | ||
var prefix = this.opts.attributePrefix; | ||
var selector = '[' + prefix + 'event-category][' + prefix + 'event-action]'; | ||
// Binds methods. | ||
this.handleEvents = this.handleEvents.bind(this); | ||
this.delegate = delegate(document, selector, | ||
'click', this.handleEventClicks.bind(this)); | ||
var selector = '[' + this.opts.attributePrefix + 'on]'; | ||
// Creates a mapping of events to their delegates | ||
this.delegates = {}; | ||
this.opts.events.forEach(function(event) { | ||
this.delegates[event] = delegate(document, event, selector, | ||
this.handleEvents, {deep: true, useCapture: true}); | ||
}.bind(this)); | ||
} | ||
@@ -53,14 +67,18 @@ | ||
* @param {Event} event The DOM click event. | ||
* @param {Element} element The delegated DOM element target. | ||
*/ | ||
EventTracker.prototype.handleEventClicks = function(event) { | ||
EventTracker.prototype.handleEvents = function(event, element) { | ||
var link = event.delegateTarget; | ||
var prefix = this.opts.attributePrefix; | ||
this.tracker.send('event', { | ||
eventCategory: link.getAttribute(prefix + 'event-category'), | ||
eventAction: link.getAttribute(prefix + 'event-action'), | ||
eventLabel: link.getAttribute(prefix + 'event-label'), | ||
eventValue: link.getAttribute(prefix + 'event-value') | ||
}); | ||
// Ensures the event type matches the one specified on the element. | ||
if (event.type != element.getAttribute(prefix + 'on')) return; | ||
var defaultFields = {transport: 'beacon'}; | ||
var attributeFields = getAttributeFields(element, prefix); | ||
var userFields = assign({}, this.opts.fieldsObj, attributeFields); | ||
var hitType = attributeFields.hitType || 'event'; | ||
this.tracker.send(hitType, createFieldsObj( | ||
defaultFields, userFields, this.tracker, this.opts.hitFilter, element)); | ||
}; | ||
@@ -73,6 +91,5 @@ | ||
EventTracker.prototype.remove = function() { | ||
this.delegate.destroy(); | ||
this.delegate = null; | ||
this.tracker = null; | ||
this.opts = null; | ||
Object.keys(this.delegates).forEach(function(key) { | ||
this.delegates[key].destroy(); | ||
}.bind(this)); | ||
}; | ||
@@ -79,0 +96,0 @@ |
@@ -18,16 +18,13 @@ /** | ||
var assign = require('object-assign'); | ||
var debounce = require('debounce'); | ||
var defaults = require('../utilities').defaults; | ||
var constants = require('../constants'); | ||
var provide = require('../provide'); | ||
var usage = require('../usage'); | ||
var createFieldsObj = require('../utilities').createFieldsObj; | ||
var isObject = require('../utilities').isObject; | ||
var toArray = require('../utilities').toArray; | ||
var provide = require('../provide'); | ||
/** | ||
* Sets the string to use when no custom dimension value is available. | ||
*/ | ||
var NULL_DIMENSION = '(not set)'; | ||
/** | ||
* Declares the MediaQueryListener instance cache. | ||
@@ -46,15 +43,19 @@ */ | ||
usage.track(tracker, usage.plugins.MEDIA_QUERY_TRACKER); | ||
// Feature detects to prevent errors in unsupporting browsers. | ||
if (!window.matchMedia) return; | ||
this.opts = defaults(opts, { | ||
mediaQueryDefinitions: false, | ||
mediaQueryChangeTemplate: this.changeTemplate, | ||
mediaQueryChangeTimeout: 1000 | ||
}); | ||
this.opts = assign({ | ||
definitions: null, | ||
changeTemplate: this.changeTemplate, | ||
changeTimeout: 1000, | ||
fieldsObj: {}, | ||
hitFilter: null | ||
}, opts); | ||
// Exits early if media query data doesn't exist. | ||
if (!isObject(this.opts.mediaQueryDefinitions)) return; | ||
if (!isObject(this.opts.definitions)) return; | ||
this.opts.mediaQueryDefinitions = toArray(this.opts.mediaQueryDefinitions); | ||
this.opts.definitions = toArray(this.opts.definitions); | ||
this.tracker = tracker; | ||
@@ -72,3 +73,3 @@ this.changeListeners = []; | ||
MediaQueryTracker.prototype.processMediaQueries = function() { | ||
this.opts.mediaQueryDefinitions.forEach(function(definition) { | ||
this.opts.definitions.forEach(function(definition) { | ||
// Only processes definitions with a name and index. | ||
@@ -100,3 +101,3 @@ if (definition.name && definition.dimensionIndex) { | ||
}); | ||
return match ? match.name : NULL_DIMENSION; | ||
return match ? match.name : constants.NULL_DIMENSION; | ||
}; | ||
@@ -116,3 +117,3 @@ | ||
this.handleChanges(definition); | ||
}.bind(this), this.opts.mediaQueryChangeTimeout); | ||
}.bind(this), this.opts.changeTimeout); | ||
@@ -137,4 +138,10 @@ mql.addListener(fn); | ||
this.tracker.set('dimension' + definition.dimensionIndex, newValue); | ||
this.tracker.send('event', definition.name, 'change', | ||
this.opts.mediaQueryChangeTemplate(oldValue, newValue)); | ||
var defaultFields = { | ||
eventCategory: definition.name, | ||
eventAction: 'change', | ||
eventLabel: this.opts.changeTemplate(oldValue, newValue) | ||
}; | ||
this.tracker.send('event', createFieldsObj( | ||
defaultFields, this.opts.fieldsObj, this.tracker, this.opts.hitFilter)); | ||
} | ||
@@ -151,5 +158,2 @@ }; | ||
} | ||
this.changeListeners = null; | ||
this.tracker = null; | ||
this.opts = null; | ||
}; | ||
@@ -160,3 +164,3 @@ | ||
* Sets the default formatting of the change event label. | ||
* This can be overridden by setting the `mediaQueryChangeTemplate` option. | ||
* This can be overridden by setting the `changeTemplate` option. | ||
* @param {string} oldValue The value of the media query prior to the change. | ||
@@ -163,0 +167,0 @@ * @param {string} newValue The value of the media query after the change. |
@@ -18,6 +18,10 @@ /** | ||
var defaults = require('../utilities').defaults; | ||
var delegate = require('delegate'); | ||
var assign = require('object-assign'); | ||
var delegate = require('dom-utils/lib/delegate'); | ||
var parseUrl = require('dom-utils/lib/parse-url'); | ||
var provide = require('../provide'); | ||
var utilities = require('../utilities'); | ||
var usage = require('../usage'); | ||
var createFieldsObj = require('../utilities').createFieldsObj; | ||
var getAttributeFields = require('../utilities').getAttributeFields; | ||
var withTimeout = require('../utilities').withTimeout; | ||
@@ -33,13 +37,19 @@ | ||
usage.track(tracker, usage.plugins.OUTBOUND_FORM_TRACKER); | ||
// Feature detects to prevent errors in unsupporting browsers. | ||
if (!window.addEventListener) return; | ||
this.opts = defaults(opts, { | ||
shouldTrackOutboundForm: this.shouldTrackOutboundForm | ||
}); | ||
this.opts = assign({ | ||
formSelector: 'form', | ||
shouldTrackOutboundForm: this.shouldTrackOutboundForm, | ||
fieldsObj: {}, | ||
attributePrefix: 'ga-', | ||
hitFilter: null | ||
}, opts); | ||
this.tracker = tracker; | ||
this.delegate = delegate(document, 'form', | ||
'submit', this.handleFormSubmits.bind(this)); | ||
this.delegate = delegate(document, 'submit', 'form', | ||
this.handleFormSubmits.bind(this), {deep: true, useCapture: true}); | ||
} | ||
@@ -55,10 +65,15 @@ | ||
* @param {Event} event The DOM submit event. | ||
* @param {Element} form The delegated event target. | ||
*/ | ||
OutboundFormTracker.prototype.handleFormSubmits = function(event) { | ||
OutboundFormTracker.prototype.handleFormSubmits = function(event, form) { | ||
var form = event.delegateTarget; | ||
var action = form.getAttribute('action'); | ||
var fieldsObj = {transport: 'beacon'}; | ||
var action = parseUrl(form.action).href; | ||
var defaultFields = { | ||
transport: 'beacon', | ||
eventCategory: 'Outbound Form', | ||
eventAction: 'submit', | ||
eventLabel: action | ||
}; | ||
if (this.opts.shouldTrackOutboundForm(form)) { | ||
if (this.opts.shouldTrackOutboundForm(form, parseUrl)) { | ||
@@ -69,3 +84,3 @@ if (!navigator.sendBeacon) { | ||
event.preventDefault(); | ||
fieldsObj.hitCallback = utilities.withTimeout(function() { | ||
defaultFields.hitCallback = withTimeout(function() { | ||
form.submit(); | ||
@@ -75,3 +90,7 @@ }); | ||
this.tracker.send('event', 'Outbound Form', 'submit', action, fieldsObj); | ||
var userFields = assign({}, this.opts.fieldsObj, | ||
getAttributeFields(form, this.opts.attributePrefix)); | ||
this.tracker.send('event', createFieldsObj( | ||
defaultFields, userFields, this.tracker, this.opts.hitFilter, form)); | ||
} | ||
@@ -86,9 +105,11 @@ }; | ||
* @param {Element} form The form that was submitted. | ||
* @param {Function} parseUrl A cross-browser utility method for url parsing. | ||
* @return {boolean} Whether or not the form should be tracked. | ||
*/ | ||
OutboundFormTracker.prototype.shouldTrackOutboundForm = function(form) { | ||
var action = form.getAttribute('action'); | ||
return action && | ||
action.indexOf('http') === 0 && | ||
action.indexOf(location.hostname) < 0; | ||
OutboundFormTracker.prototype.shouldTrackOutboundForm = | ||
function(form, parseUrl) { | ||
var url = parseUrl(form.action); | ||
return url.hostname != location.hostname && | ||
url.protocol.slice(0, 4) == 'http'; | ||
}; | ||
@@ -100,7 +121,4 @@ | ||
*/ | ||
OutboundFormTracker.prototype.remove = function() { | ||
OutboundFormTracker.prototype.remove = function() { | ||
this.delegate.destroy(); | ||
this.delegate = null; | ||
this.tracker = null; | ||
this.opts = null; | ||
}; | ||
@@ -107,0 +125,0 @@ |
@@ -18,5 +18,9 @@ /** | ||
var defaults = require('../utilities').defaults; | ||
var delegate = require('delegate'); | ||
var assign = require('object-assign'); | ||
var delegate = require('dom-utils/lib/delegate'); | ||
var parseUrl = require('dom-utils/lib/parse-url'); | ||
var provide = require('../provide'); | ||
var usage = require('../usage'); | ||
var createFieldsObj = require('../utilities').createFieldsObj; | ||
var getAttributeFields = require('../utilities').getAttributeFields; | ||
@@ -32,13 +36,27 @@ | ||
usage.track(tracker, usage.plugins.OUTBOUND_LINK_TRACKER); | ||
// Feature detects to prevent errors in unsupporting browsers. | ||
if (!window.addEventListener) return; | ||
this.opts = defaults(opts, { | ||
shouldTrackOutboundLink: this.shouldTrackOutboundLink | ||
}); | ||
this.opts = assign({ | ||
events: ['click'], | ||
linkSelector: 'a', | ||
shouldTrackOutboundLink: this.shouldTrackOutboundLink, | ||
fieldsObj: {}, | ||
attributePrefix: 'ga-', | ||
hitFilter: null | ||
}, opts); | ||
this.tracker = tracker; | ||
this.delegate = delegate(document, 'a', | ||
'click', this.handleLinkClicks.bind(this)); | ||
// Binds methods. | ||
this.handleLinkInteractions = this.handleLinkInteractions.bind(this); | ||
// Creates a mapping of events to their delegates | ||
this.delegates = {}; | ||
this.opts.events.forEach(function(event) { | ||
this.delegates[event] = delegate(document, event, this.opts.linkSelector, | ||
this.handleLinkInteractions, {deep: true, useCapture: true}); | ||
}.bind(this)); | ||
} | ||
@@ -48,11 +66,12 @@ | ||
/** | ||
* Handles all clicks on link elements. A link is considered an outbound link | ||
* its hostname property does not match location.hostname. When the beacon | ||
* transport method is not available, the links target is set to "_blank" to | ||
* ensure the hit can be sent. | ||
* Handles all interactions on link elements. A link is considered an outbound | ||
* link if its hostname property does not match location.hostname. When the | ||
* beacon transport method is not available, the links target is set to | ||
* "_blank" to ensure the hit can be sent. | ||
* @param {Event} event The DOM click event. | ||
* @param {Element} link The delegated event target. | ||
*/ | ||
OutboundLinkTracker.prototype.handleLinkClicks = function(event) { | ||
var link = event.delegateTarget; | ||
if (this.opts.shouldTrackOutboundLink(link)) { | ||
OutboundLinkTracker.prototype.handleLinkInteractions = function(event, link) { | ||
if (this.opts.shouldTrackOutboundLink(link, parseUrl)) { | ||
// Opens outbound links in a new tab if the browser doesn't support | ||
@@ -63,5 +82,15 @@ // the beacon transport method. | ||
} | ||
this.tracker.send('event', 'Outbound Link', 'click', link.href, { | ||
transport: 'beacon' | ||
}); | ||
var defaultFields = { | ||
transport: 'beacon', | ||
eventCategory: 'Outbound Link', | ||
eventAction: event.type, | ||
eventLabel: link.href | ||
}; | ||
var userFields = assign({}, this.opts.fieldsObj, | ||
getAttributeFields(link, this.opts.attributePrefix)); | ||
this.tracker.send('event', createFieldsObj( | ||
defaultFields, userFields, this.tracker, this.opts.hitFilter, link)); | ||
} | ||
@@ -76,7 +105,11 @@ }; | ||
* @param {Element} link The link that was clicked on. | ||
* @param {Function} parseUrl A cross-browser utility method for url parsing. | ||
* @return {boolean} Whether or not the link should be tracked. | ||
*/ | ||
OutboundLinkTracker.prototype.shouldTrackOutboundLink = function(link) { | ||
return link.hostname != location.hostname && | ||
link.protocol.indexOf('http') === 0; | ||
OutboundLinkTracker.prototype.shouldTrackOutboundLink = | ||
function(link, parseUrl) { | ||
var url = parseUrl(link.href); | ||
return url.hostname != location.hostname && | ||
url.protocol.slice(0, 4) == 'http'; | ||
}; | ||
@@ -89,6 +122,5 @@ | ||
OutboundLinkTracker.prototype.remove = function() { | ||
this.delegate.destroy(); | ||
this.delegate = null; | ||
this.tracker = null; | ||
this.opts = null; | ||
Object.keys(this.delegates).forEach(function(key) { | ||
this.delegates[key].destroy(); | ||
}.bind(this)); | ||
}; | ||
@@ -95,0 +127,0 @@ |
@@ -18,5 +18,7 @@ /** | ||
var defaults = require('../utilities').defaults; | ||
var assign = require('object-assign'); | ||
var provide = require('../provide'); | ||
var usage = require('../usage'); | ||
var createFieldsObj = require('../utilities').createFieldsObj; | ||
var isObject = require('../utilities').isObject; | ||
var provide = require('../provide'); | ||
@@ -32,8 +34,12 @@ | ||
usage.track(tracker, usage.plugins.URL_CHANGE_TRACKER); | ||
// Feature detects to prevent errors in unsupporting browsers. | ||
if (!history.pushState || !window.addEventListener) return; | ||
if (!history.pushState || !window.addEventListener) return; | ||
this.opts = defaults(opts, { | ||
shouldTrackUrlChange: this.shouldTrackUrlChange | ||
}); | ||
this.opts = assign({ | ||
shouldTrackUrlChange: this.shouldTrackUrlChange, | ||
fieldsObj: {}, | ||
hitFilter: null | ||
}, opts); | ||
@@ -105,3 +111,7 @@ this.tracker = tracker; | ||
if (shouldSendPageview) this.tracker.send('pageview'); | ||
if (shouldSendPageview) { | ||
var defaultFields = {transport: 'beacon'}; | ||
this.tracker.send('pageview', createFieldsObj(defaultFields, | ||
this.opts.fieldsObj, this.tracker, this.opts.hitFilter)); | ||
} | ||
} | ||
@@ -108,0 +118,0 @@ }.bind(this), 0); |
@@ -18,5 +18,89 @@ /** | ||
var assign = require('object-assign'); | ||
var getAttributes = require('dom-utils/lib/get-attributes'); | ||
var utilities = { | ||
/** | ||
* Accepts default and user override fields and an optional tracker, hit | ||
* filter, and target element and returns a single object that can be used in | ||
* `ga('send', ...)` commands. | ||
* @param {Object} defaultFields The default fields to return. | ||
* @param {Object} userFields Fields set by the user to override the defaults. | ||
* @param {Object} opt_tracker The tracker object to apply the hit filter to. | ||
* @param {Function} opt_hitFilter A filter function that gets | ||
* called with the tracker model right before the `buildHitTask`. It can | ||
* be used to modify the model for the current hit only. | ||
* @param {Element} opt_target If the hit originated from an interaction | ||
* with a DOM element, hitFilter is invoked with that element as the | ||
* second argument. | ||
* @return {Object} The final fields object. | ||
*/ | ||
createFieldsObj: function( | ||
defaultFields, userFields, opt_tracker, opt_hitFilter, opt_target) { | ||
if (typeof opt_hitFilter == 'function') { | ||
var originalBuildHitTask = opt_tracker.get('buildHitTask'); | ||
return { | ||
buildHitTask: function(model) { | ||
model.set(defaultFields, null, true); | ||
model.set(userFields, null, true); | ||
opt_hitFilter(model, opt_target); | ||
originalBuildHitTask(model); | ||
} | ||
}; | ||
} | ||
else { | ||
return assign({}, defaultFields, userFields); | ||
} | ||
}, | ||
/** | ||
* Retrieves the attributes from an DOM element and returns a fields object | ||
* for all attributes matching the passed prefix string. | ||
* @param {Element} element The DOM element to get attributes from. | ||
* @param {string} prefix An attribute prefix. Only the attributes matching | ||
* the prefix will be returned on the fields object. | ||
* @return {Object} An object of analytics.js fields and values | ||
*/ | ||
getAttributeFields: function(element, prefix) { | ||
var attributes = getAttributes(element); | ||
var attributeFields = {}; | ||
Object.keys(attributes).forEach(function(attribute) { | ||
// The `on` prefix is used for event handling but isn't a field. | ||
if (attribute.indexOf(prefix) === 0 && attribute != prefix + 'on') { | ||
var value = attributes[attribute]; | ||
// Detects Boolean value strings. | ||
if (value == 'true') value = true; | ||
if (value == 'false') value = false; | ||
var field = utilities.camelCase(attribute.slice(prefix.length)); | ||
attributeFields[field] = value; | ||
} | ||
}); | ||
return attributeFields; | ||
}, | ||
domReady: function(callback) { | ||
if (document.readyState == 'loading') { | ||
document.addEventListener('DOMContentLoaded', function fn() { | ||
document.removeEventListener('DOMContentLoaded', fn); | ||
callback(); | ||
}); | ||
} else { | ||
callback(); | ||
} | ||
}, | ||
/** | ||
* Accepts a function and returns a wrapped version of the function that is | ||
@@ -44,27 +128,11 @@ * expected to be called elsewhere in the system. If it's not called | ||
/** | ||
* Accepts an object of overrides and defaults and returns a new object | ||
* with the values merged. For each key in defaults, if there's a | ||
* corresponding value in overrides, it gets used. | ||
* @param {Object} overrides The object with properties to override. | ||
* @param {?Object} defaults The object with properties to use as defaults. | ||
* @return {Object} The final, merged object. | ||
* Accepts a string containing hyphen or underscore word separators and | ||
* converts it to camelCase. | ||
* @param {string} str The string to camelCase. | ||
* @return {string} The camelCased version of the string. | ||
*/ | ||
defaults: function(overrides, defaults) { | ||
var result = {}; | ||
if (typeof overrides != 'object') { | ||
overrides = {}; | ||
} | ||
if (typeof defaults != 'object') { | ||
defaults = {}; | ||
} | ||
for (var key in defaults) { | ||
if (defaults.hasOwnProperty(key)) { | ||
result[key] = overrides.hasOwnProperty(key) ? | ||
overrides[key] : defaults[key]; | ||
} | ||
} | ||
return result; | ||
camelCase: function(str) { | ||
return str.replace(/[\-\_]+(\w?)/g, function(match, p1) { | ||
return p1.toUpperCase(); | ||
}); | ||
}, | ||
@@ -71,0 +139,0 @@ |
{ | ||
"name": "autotrack", | ||
"version": "0.6.5", | ||
"description": "Automatic + enhanced analytics.js tracking for common user interactions", | ||
"main": "lib/plugins/autotrack.js", | ||
"version": "1.0.0", | ||
"description": "Automatic and enhanced Google Analytics tracking for common user interactions on the web", | ||
"main": "lib", | ||
"scripts": { | ||
@@ -34,3 +34,4 @@ "build": "gulp build", | ||
"debounce": "^1.0.0", | ||
"delegate": "^3.0.0" | ||
"dom-utils": "^0.2.2", | ||
"object-assign": "^4.0.1" | ||
}, | ||
@@ -40,2 +41,3 @@ "devDependencies": { | ||
"connect": "^3.4.0", | ||
"eslint": "^2.11.1", | ||
"gulp": "^3.9.0", | ||
@@ -47,2 +49,3 @@ "gulp-eslint": "^2.0.0", | ||
"gulp-webdriver": "^2.0.1", | ||
"intersection-observer": "^0.1.1", | ||
"lodash": "^4.8.2", | ||
@@ -57,4 +60,4 @@ "mocha": "^2.3.4", | ||
"wdio-sauce-service": "^0.2.2", | ||
"webdriverio": "^4.0.5" | ||
"webdriverio": "^4.1.1" | ||
} | ||
} |
421
README.md
# Autotrack [![Build Status](https://travis-ci.org/googleanalytics/autotrack.svg?branch=master)](https://travis-ci.org/googleanalytics/autotrack) | ||
- [Overview](#overview) | ||
- [Usage](#usage) | ||
- [Plugins](#plugins) | ||
- [Installation and usage](#installation-and-usage) | ||
- [Loading autotrack via npm](#loading-autotrack-via-npm) | ||
- [Passing configuration options](#passing-configuration-options) | ||
- [Loading autotrack via npm](#loading-autotrack-via-npm) | ||
- [Using individual plugins](#using-individual-plugins) | ||
- [Plugins](#plugins) | ||
- [Configuration Options](#configuration-options) | ||
- [Advanced Usage](#advanced-usage) | ||
- [Advanced configuration](#advanced-configuration) | ||
- [Custom builds](#custom-builds) | ||
@@ -20,8 +18,10 @@ - [Using autotrack with multiple trackers](#using-autotrack-with-multiple-trackers) | ||
Since most website owners care about most of the same types of user interactions, web developers end up writing the same code over and over again for every new site they build. | ||
Since most website owners care about a lot of the same types of user interactions, web developers end up writing the same code over and over again for every new site they build. | ||
Autotrack was created to solve this problem. It provides default tracking for the interactions most people care about, and it provides several convenience features (e.g. declarative event tracking) to make it easier than ever to understand how people are using your site. | ||
The `autotrack.js` library is small (3K gzipped), and includes the following plugins. By default all plugins are bundled together, but they can be included and configured separately as well: | ||
## Plugins | ||
The `autotrack.js` library is small (6K gzipped), and includes the following plugins. By default all plugins are bundled together, but they can be included and configured separately as well. This table includes a brief description of each plugin; you can click on the plugin name to see the full documentation and usage instructions: | ||
<table> | ||
@@ -33,30 +33,42 @@ <tr> | ||
<tr> | ||
<td><a href="#eventtracker"><code>eventTracker</code></a></td> | ||
<td>Declarative event tracking</td> | ||
<td><a href="/docs/plugins/clean-url-tracker.md"><code>cleanUrlTracker</code></a></td> | ||
<td>Ensures consistency in the URL paths that get reported to Google Analytics; avoiding the problem where separate rows in your pages reports actually point to the same page.</td> | ||
</tr> | ||
<tr> | ||
<td><a href="#mediaquerytracker"><code>mediaQueryTracker</code></a></td> | ||
<td>Media query and breakpoint tracking</td> | ||
<td><a href="/docs/plugins/event-tracker.md"><code>eventTracker</code></a></td> | ||
<td>Enables declarative event tracking, via HTML attributes in the markup.</td> | ||
</tr> | ||
<tr> | ||
<td><a href="#outboundformtracker"><code>outboundFormTracker</code></a></td> | ||
<td>Automatic outbound form tracking</td> | ||
<td><a href="/docs/plugins/impression-tracker.md"><code>impressionTracker</code></a></td> | ||
<td>Allows you to track when elements are visible within the viewport.</td> | ||
</tr> | ||
<tr> | ||
<td><a href="#outboundlinktracker"><code>outboundLinkTracker</code></a></td> | ||
<td>Automatic outbound link tracking</td> | ||
<td><a href="/docs/plugins/media-query-tracker.md"><code>mediaQueryTracker</code></a></td> | ||
<td>Enables tracking media query matching and media query changes.</td> | ||
</tr> | ||
<tr> | ||
<td><a href="#socialtracker"><code>socialTracker</code></a></td> | ||
<td>Automatic and enhanced declarative social tracking</td> | ||
<td><a href="/docs/plugins/outbound-form-tracker.md"><code>outboundFormTracker</code></a></td> | ||
<td>Automatically tracks form submits to external domains.</td> | ||
</tr> | ||
<tr> | ||
<td><a href="#urlchangetracker"><code>urlChangeTracker</code></a></td> | ||
<td>Automatic URL change tracking for single page applications</td> | ||
<td><a href="/docs/plugins/outbound-link-tracker.md"><code>outboundLinkTracker</code></a></td> | ||
<td>Automatically tracks link clicks to external domains.</td> | ||
</tr> | ||
<tr> | ||
<td><a href="/docs/plugins/page-visibility-tracker.md"><code>pageVisibilityTracker</code></a></td> | ||
<td>Tracks page visibility state changes, which enables much more accurate session, session duration, and pageview metrics.</td> | ||
</tr> | ||
<tr> | ||
<td><a href="/docs/plugins/social-widget-tracker.md"><code>socialWidgetTracker</code></a></td> | ||
<td>Automatically tracks user interactions with the official Facebook and Twitter widgets.</td> | ||
</tr> | ||
<tr> | ||
<td><a href="/docs/plugins/url-change-tracker.md"><code>urlChangeTracker</code></a></td> | ||
<td>Automatically tracks URL changes for single page applications.</td> | ||
</tr> | ||
</table> | ||
**Disclaimer:** autotrack is maintained by the Google Analytics developer relations team and is primarily intended for a developer audience. It is not an official Google Analytics product and does not qualify for Google Analytics premium support. Developers who choose to use this library are responsible for ensuring that their implementation meets the requirements of the [Google Analytics Terms of Service](https://www.google.com/analytics/terms/us.html) and the legal obligations of their respective country. | ||
**Disclaimer:** autotrack is maintained by members of the Google Analytics developer platform team and is primarily intended for a developer audience. It is not an official Google Analytics product and does not qualify for Google Analytics 360 support. Developers who choose to use this library are responsible for ensuring that their implementation meets the requirements of the [Google Analytics Terms of Service](https://www.google.com/analytics/terms/us.html) and the legal obligations of their respective country. | ||
## Usage | ||
## Installation and usage | ||
@@ -66,5 +78,5 @@ To add autotrack to your site, you have to do two things: | ||
1. Load the `autotrack.js` script file on your page. | ||
2. Update the [tracking snippet](https://developers.google.com/analytics/devguides/collection/analyticsjs/tracking-snippet-reference) to [require](https://developers.google.com/analytics/devguides/collection/analyticsjs/using-plugins) the `autotrack` plugin. | ||
2. Update your [tracking snippet](https://developers.google.com/analytics/devguides/collection/analyticsjs/tracking-snippet-reference) to [require](https://developers.google.com/analytics/devguides/collection/analyticsjs/using-plugins) the various autotrack plugins you want to use. | ||
If your site already includes the default JavaScript tracking snippet, you can replace it with the following modified snippet (note the added `require` command as well as the additional `autotrack.js` script): | ||
If your site already includes the default JavaScript tracking snippet, you can modify it too look something like this: | ||
@@ -75,3 +87,9 @@ ```html | ||
ga('create', 'UA-XXXXX-Y', 'auto'); | ||
ga('require', 'autotrack'); | ||
// Replace the following lines with the plugins you want to use. | ||
ga('require', 'eventTracker'); | ||
ga('require', 'outboundLinkTracker'); | ||
ga('require', 'urlChangeTracker'); | ||
// ... | ||
ga('send', 'pageview'); | ||
@@ -83,16 +101,10 @@ </script> | ||
The [analytics.js plugin system](https://developers.google.com/analytics/devguides/collection/analyticsjs/using-plugins) is designed to support asynchronously loaded scripts, so it doesn't matter if `autotrack.js` is loaded before or after `analytics.js`. It also doesn't matter if the `autotrack.js` library is loaded individually or bundled with the rest of your JavaScript code. | ||
Of course, you'll have to make the following modifications to customize autotrack to your needs: | ||
### Passing configuration options | ||
- Replace `UA-XXXXX-Y` with your [tracking ID](https://support.google.com/analytics/answer/1032385) | ||
- Replace the sample list of plugin `require` statements with the plugins you want to use. | ||
- Replace `path/to/autotrack.js` with the actual location of the `autotrack.js` file hosted on your server. | ||
The default behavior of autotrack can be customized via [configuration options](#configuration-options). You can pass configuration options to autotrack via the `require` command using the optional third parameter. | ||
**Note:** the [analytics.js plugin system](https://developers.google.com/analytics/devguides/collection/analyticsjs/using-plugins) is designed to support asynchronously loaded scripts, so it doesn't matter if `autotrack.js` is loaded before or after `analytics.js`. It also doesn't matter if the `autotrack.js` library is loaded individually or bundled with the rest of your JavaScript code. | ||
For example, you could override the default [`attributePrefix`](#attributeprefix) option as follows: | ||
```js | ||
ga('require', 'autotrack', { | ||
attributePrefix: 'data-ga-' | ||
}); | ||
``` | ||
### Loading autotrack via npm | ||
@@ -111,304 +123,42 @@ | ||
Note that the above code will include the autotrack plugins in the generated JavaScript file, but it won't register the plugin for use on an `analytics.js` tracker object. Adding the `require` command to the tracking snippet is still necessary: | ||
The above code will include all autotrack plugins in your generated source file. If you only want to include a specific set of plugins, you can require them individually: | ||
```js | ||
// In the analytics.js tracking snippet | ||
ga('create', 'UA-XXXXX-Y', 'auto'); | ||
ga('require', 'autotrack'); | ||
ga('send', 'pageview'); | ||
// In your JavaScript code | ||
require('autotrack/lib/plugins/clean-url-tracker'); | ||
require('autotrack/lib/plugins/outbound-link-tracker'); | ||
require('autotrack/lib/plugins/url-change-tracker'); | ||
// ... | ||
``` | ||
### Using individual plugins | ||
The above examples show how to include the plugin source code in your final, generated JavaScript file, which accomplishes the first step of the two-step installation process. | ||
The `autotrack.js` source file includes all the plugins described below, but in some cases you might not want to use all of them. | ||
You still have to update your tracking snippet and require the plugins you want to use: | ||
When you require the `autotrack` plugin, it runs the `require` command for each of the bundled plugins and passes them a copy of the configuration object it received (if any). To only use select plugins, you can require them individually instead of requiring the `autotrack` plugin. | ||
For example, to only use the `eventTracker` and `outboundLinkTracker` plugins, you can modify the snippet as follows: | ||
```js | ||
// In the analytics.js tracking snippet | ||
ga('create', 'UA-XXXXX-Y', 'auto'); | ||
ga('require', 'eventTracker'); | ||
// Replace the following lines with the plugins you want to use. | ||
ga('require', 'cleanUrlTracker'); | ||
ga('require', 'outboundLinkTracker'); | ||
ga('require', 'urlChangeTracker'); | ||
// ... | ||
ga('send', 'pageview'); | ||
``` | ||
Individual plugins accept the same set of configuration options as autotrack. Options not relevant to a particular plugin are ignored. To use configuration options when requiring individual plugins, the simplest way is usually to pass each plugin the same object. | ||
**Note:** be careful not to confuse the node module [`require`](https://nodejs.org/api/modules.html) statement with the `analytics.js` [`require`](https://developers.google.com/analytics/devguides/collection/analyticsjs/command-queue-reference#require) command. When loading autotrack with an npm module loader, both requires must be used. | ||
```js | ||
var opts = { /* configuration options */ }; | ||
### Passing configuration options | ||
ga('require', 'eventTracker', opts); | ||
ga('require', 'outboundLinkTracker', opts); | ||
``` | ||
All autotrack plugins accept a configuration object as the third parameter to the `require` command. | ||
When only requiring select plugins, it's important to realize that the `autotrack.js` source file still includes the code for all plugins. To build a custom version of the script with only the desired plugins, see the [custom builds](#custom-builds) section below. | ||
Some of the plugins (e.g. `outboundLinkTracker`, `socialWidgetTracker`, `urlChangeTracker`) have a default behavior that works for most people without specifying any configuration options. Other plugins (e.g. `cleanUrlTracker`, `impressionTracker`, `mediaQueryTracker`) require certain configuration options to be set in order to work. | ||
## Plugins | ||
See the individual plugin documentation to reference what options each plugin accepts (and what the default value is, if any). | ||
### `eventTracker` | ||
## Advanced configuration | ||
The `eventTracker` plugin adds declarative event tracking for click events on any element with the `data-event-category` and `data-event-action` attributes. The attributes `data-event-label` and `data-event-value` are also supported (attribute names are customizable). | ||
#### Options | ||
* [`attributePrefix`](#attributeprefix) | ||
#### Example | ||
The following element would send an event hit to Google Analytics with the category "video" and the action "play": | ||
```html | ||
<button data-event-category="video" data-event-action="play">Play</button> | ||
``` | ||
### `mediaQueryTracker` | ||
The `mediaQueryTracker` plugin allows you to track what media query is active as well as how often the matching media query changes. | ||
You can tell the `mediaQueryTracker` plugin what media query data to look for via the [`mediaQueryDefinitions`](#mediaquerydefinitions) configuration option. | ||
**Important: unlike the other autotrack plugins, to use the `mediaQueryTracker` plugin you have to first make a few changes to your property settings in Google Analytics. Here's what needs to be done:** | ||
1. Log in to Google Analytics, choose the [account and property](https://support.google.com/analytics/answer/1009618) you're sending data too, and [create a custom dimension](https://support.google.com/analytics/answer/2709829) for each set of media queries you want to track (e.g. Breakpoints, Resolution/DPI, Device Orientation) | ||
2. Give each dimension a name (e.g. Breakpoints), select a scope of [hit](https://support.google.com/analytics/answer/2709828#example-hit), and make sure the "active" checkbox is checked. | ||
3. In the [`mediaQueryDefinitions`](#mediaquerydefinitions) config object, set the `name` and `dimensionIndex` values to be the same as the name and index shown in Google Analytics. | ||
Refer to the [`mediaQueryDefinitions`](#mediaquerydefinitions) configuration option documentation for an example definition that will track breakpoint, device resolution, and device orientation data. | ||
#### Options | ||
* [`mediaQueryDefinitions`](#mediaquerydefinitions) | ||
### `outboundFormTracker` | ||
The `outboundFormTracker` plugin automatically detects when forms are submitted to sites on different domains and sends an event hit. The event category is "Outbound Form", the event action is "submit", and the event label is the value of the form's `action` attribute. | ||
By default a form is considered outbound if its action is not a relative path and does not contain the current `location.hostname` value. Note that this means forms pointing to different subdomains within the same higher-level domain are (by default) still considered outbound. This logic can be customized via the [`shouldTrackOutboundForm`](#shouldtrackoutboundform) configuration option. | ||
#### Options | ||
* [`shouldTrackOutboundForm`](#shouldtrackoutboundform) | ||
### `outboundLinkTracker` | ||
The `outboundLinkTracker` plugin automatically detects when links are clicked with `href` attributes pointing to sites on different domains and sends an event hit. The event category is "Outbound Link", the event action is "click", and the event label is the value of the link's `href` attribute. | ||
By default a link is considered outbound if its `hostname` property is not equal to `location.hostname`. Note that this means links pointing to different subdomains within the same higher-level domain are (by default) considered outbound. This logic can be customized via the [`shouldTrackOutboundLink`](#shouldtrackoutboundlink) configuration option. | ||
#### Options | ||
* [`shouldTrackOutboundLink`](#shouldtrackoutboundlink) | ||
### `socialTracker` | ||
The `socialTracker` plugin adds declarative social interaction tracking for click events on any element with the `data-social-network`, `data-social-action`, and `data-social-target` attributes, similar to the `eventTracking` plugin. | ||
It also automatically adds social tracking for the official Twitter tweet/follow buttons and the Facebook like button. In other words, if you include official Twitter or Facebook buttons on your page and you're using autotrack (or even just the `socialTracker` plugin), user interactions with those buttons will be automatically tracked. | ||
The following table outlines the social fields captured: | ||
<table> | ||
<tr> | ||
<th align="left">Widget</th> | ||
<th align="left">Social Network</th> | ||
<th align="left">Social Action</th> | ||
<th align="left">Social Target</th> | ||
</tr> | ||
<tr> | ||
<td>Like button</td> | ||
<td><code>Facebook</code></td> | ||
<td><code>like</code> or <code>unlike</code></td> | ||
<td>The URL of the current page.</td> | ||
</tr> | ||
<tr> | ||
<td>Tweet button</td> | ||
<td><code>Twitter</code></td> | ||
<td><code>tweet</code></td> | ||
<td>The widget's <code>data-url</code> attribute or the URL of the current page.</td> | ||
</tr> | ||
<tr> | ||
<td>Follow button</td> | ||
<td><code>Twitter</code></td> | ||
<td><code>follow</code></td> | ||
<td>The widget's <code>data-screen-name</code> attribute.</td> | ||
</tr> | ||
</table> | ||
### `urlChangeTracker` | ||
The `urlChangeTracker` plugin detects changes to the URL via the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) and automatically updates the tracker and sends additional pageviews. This allows [single page applications](https://en.wikipedia.org/wiki/Single-page_application) to be tracked like traditional sites without any extra configuration. | ||
Note, this plugin does not support tracking hash changes as most Google Analytics implementations do not capture the hash portion of the URL when tracking pageviews. Also, developers of single page applications should make sure their framework isn't already tracking URL changes to avoid collecting duplicate data. | ||
#### Options | ||
* [`shouldTrackUrlChange`](#shouldtrackurlchange) | ||
## Configuration options | ||
The following options can be passed to the `autotrack` plugin or individual sub-plugins: | ||
### `attributePrefix` | ||
**Type**: `string` | ||
**Default**: `'data-'` | ||
The attribute prefix for declarative event and social tracking. The value used after the prefix is a kebab-case version of the field name, for example: the field `eventCategory` with the prefix `'data-ga-'` would be `data-ga-event-category`. | ||
### `mediaQueryDefinitions` | ||
**Type**: `Object|Array|null` | ||
**Default**: `null` | ||
A media query definitions object or a list of media query definition objects. A media query definitions object contains the following properties: | ||
- `name`: a unique name that will be used as the `eventCategory` value for media query change events. | ||
- `dimensionIndex`: the index of the custom dimension [created in Google Analytics](https://support.google.com/analytics/answer/2709829). | ||
- `items`: An array of objects with the following properties: | ||
- `name`: The value that will be set on the custom dimension. | ||
- `media`: The media query value to test for a match. | ||
The following array is an example of three media query object definitions: | ||
```js | ||
ga('require', 'autotrack', { | ||
mediaQueryDefinitions: [ | ||
{ | ||
name: 'Breakpoint', | ||
dimensionIndex: 1, | ||
items: [ | ||
{name: 'sm', media: 'all'}, | ||
{name: 'md', media: '(min-width: 30em)'}, | ||
{name: 'lg', media: '(min-width: 48em)'} | ||
] | ||
}, | ||
{ | ||
name: 'Resolution', | ||
dimensionIndex: 2, | ||
items: [ | ||
{name: '1x', media: 'all'}, | ||
{name: '1.5x', media: '(min-resolution: 144dpi)'}, | ||
{name: '2x', media: '(min-resolution: 192dpi)'} | ||
] | ||
}, | ||
{ | ||
name: 'Orientation', | ||
dimensionIndex: 3, | ||
items: [ | ||
{name: 'landscape', media: '(orientation: landscape)'}, | ||
{name: 'portrait', media: '(orientation: portrait)'} | ||
] | ||
} | ||
] | ||
}); | ||
``` | ||
If multiple `media` values match at the same time, the one specified later in the `items` array will take precedence. For example, in the "Breakpoint" example above, the item `sm` is set to `all`, so it will always match unless `md` or `lg` matches. | ||
### `mediaQueryChangeTemplate` | ||
**Type**: `Function` | ||
**Default**: | ||
```js | ||
function(newValue, oldValue) { | ||
return oldValue + ' => ' + newValue; | ||
} | ||
``` | ||
A function used to format the `eventLabel` of media query change events. For example, if the matched media changes from `lg` to `md`, by default the result will be `lg => md`. | ||
### `mediaQueryChangeTimeout` | ||
**Type**: `number` | ||
**Default**: `1000` | ||
The debounce timeout, i.e., the amount of time to wait before sending the change hit. If multiple change events occur within the timeout period, only the last one is sent. | ||
### `shouldTrackOutboundForm` | ||
**Type**: `Function` | ||
**Default**: | ||
```js | ||
function(form) { | ||
var action = form.getAttribute('action'); | ||
return action && | ||
action.indexOf('http') === 0 && | ||
action.indexOf(location.hostname) < 0; | ||
}; | ||
``` | ||
A function used to determine if a form submit should be tracked as an "Outbound Form". The function is invoked with the `<form>` element as its only argument, and, if it returns truthy, the form submit will be tracked. | ||
The default `shouldTrackOutboundForm` option will consider a form submission from `blog.example.com` to `store.example.com` an outbound form submit. To customize this logic and exclude forms pointing to any `*.example.com` subdomain, you could override the option as follows: | ||
```js | ||
ga('require', 'autotrack', { | ||
shouldTrackOutboundForm: function(form) { | ||
var action = form.getAttribute('action'); | ||
// Checks that the action is set and starts with "http" to exclude relative | ||
// paths, then checks that it does not contain the string "example.com". | ||
return action && | ||
action.indexOf('http') === 0 && | ||
action.indexOf('example.com') < 0; | ||
} | ||
} | ||
``` | ||
### `shouldTrackOutboundLink` | ||
**Type**: `Function` | ||
**Default**: | ||
```js | ||
function(link) { | ||
return link.hostname != location.hostname && | ||
link.protocol.indexOf('http') === 0; | ||
}; | ||
``` | ||
A function used to determine if a link click should be tracked as an "Outbound Link". The function is invoked with the `<a>` element as its only argument, and, if it returns truthy, the link click will be tracked. | ||
The default `shouldTrackOutboundLink` option will consider a link click from `blog.example.com` to `store.example.com` an outbound link click. To customize this logic and exclude links pointing to any `*.example.com` subdomain, you could override the option as follows: | ||
```js | ||
ga('require', 'autotrack', { | ||
shouldTrackOutboundLink: function(link) { | ||
// Checks that the link's hostname does not contain "example.com". | ||
return link.hostname.indexOf('example.com') < 0 && | ||
link.protocol.indexOf('http') === 0; | ||
} | ||
} | ||
``` | ||
The default `shouldTrackOutboundLink` option also only tracks links with the `http:` or `https:` protocols. You can remove this check if you'd like to track protocols like `tel:` or `mailto:` as outbound links. | ||
### `shouldTrackUrlChange` | ||
**Type**: `Function` | ||
**Default**: | ||
```js | ||
function(newPath, oldPath) { | ||
return newPath && oldPath; | ||
} | ||
``` | ||
A function used to determine if a URL change should be tracked. By default, all changes other than hash changes are captured. | ||
The function is invoked with the string values `newPath` and `oldPath` which represent the pathname and search portion of the URL (not the hash portion). | ||
## Advanced Usage | ||
### Custom builds | ||
@@ -424,23 +174,25 @@ | ||
When making a custom build, be sure to update the tracking snippet to only require plugins included in your build. Requiring a plugin that's not included in the build will prevent any subsequent `analytics.js` commands from running. | ||
When making a custom build, be sure to update the tracking snippet to only require plugins included in your build. Requiring a plugin that's not included in the build will create an unmet dependency, which will prevent subsequent commands from running. | ||
If you're already using a module loader like [Browserify](http://browserify.org/), [Webpack](https://webpack.github.io/), or [SystemJS](https://github.com/systemjs/systemjs) to build your JavaScript, you can skip the above step and just require the plugins you want directly in your source files: | ||
If you're already using a module loader like [Browserify](http://browserify.org/), [Webpack](https://webpack.github.io/), or [SystemJS](https://github.com/systemjs/systemjs) to build your JavaScript, you can skip the above step and just require the plugins as described in the [loading autotrack via npm](#loading-autotrack-via-npm) section. | ||
```js | ||
// In your JavaScript code | ||
require('autotrack/lib/plugins/event-tracker'); | ||
require('autotrack/lib/plugins/outbound-link-tracker'); | ||
``` | ||
Check out the [autotrack source code](https://github.com/philipwalton/autotrack/blob/master/lib/plugins/autotrack.js) to get a better idea how this works. | ||
### Using autotrack with multiple trackers | ||
All autotrack plugins support multiple trackers and work by specifying the tracker name in the `require` command. The following example creates two trackers and requires `autotrack` on both. | ||
All autotrack plugins support multiple trackers and work by specifying the tracker name in the `require` command. The following example creates two trackers and requires various autotrack plugins on each. | ||
```js | ||
// Creates two trackers, one named `tracker1` and one named `tracker2`. | ||
ga('create', 'UA-XXXXX-Y', 'auto', 'tracker1'); | ||
ga('create', 'UA-XXXXX-Z', 'auto', 'tracker2'); | ||
ga('tracker1.require', 'autotrack'); | ||
ga('tracker2.require', 'autotrack'); | ||
// Requires plugins on tracker1. | ||
ga('tracker1.require', 'eventTracker'); | ||
ga('tracker1.require', 'socialWidgetTracker'); | ||
// Requires plugins on tracker2. | ||
ga('tracker2.require', 'eventTracker'); | ||
ga('tracker2.require', 'outboundLinkTracker'); | ||
ga('tracker2.require', 'pageVisibilityTracker'); | ||
// Sends the initial pageview for each tracker. | ||
ga('tracker1.send', 'pageview'); | ||
@@ -490,7 +242,8 @@ ga('tracker2.send', 'pageview'); | ||
* [Japanese](https://github.com/nebosuker/autotrack) | ||
* [Chinese](https://github.com/stevezhuang/autotrack/blob/master/README.zh.md) | ||
If you discover issues with a particular translation, please file them with the appropriate repository. To submit your own translation, follow these steps: | ||
1. Fork this repository | ||
2. Remove all files other than `README.md`. | ||
1. Fork this repository. | ||
2. Remove all files other than `README.md` and those in the `docs` folder. | ||
3. Submit a pull request to this repository that adds a link to your translations to the above list. |
@@ -23,5 +23,5 @@ /** | ||
run: function(command, arg1, arg2) { | ||
run: function() { | ||
var ga = window[window.GoogleAnalyticsObject || 'ga']; | ||
ga(command, arg1, arg2); | ||
ga.apply(null, arguments); | ||
}, | ||
@@ -43,2 +43,3 @@ | ||
hitType: model.get('hitType'), | ||
location: model.get('location'), | ||
page: model.get('page'), | ||
@@ -55,3 +56,8 @@ title: model.get('title'), | ||
dimension2: model.get('dimension2'), | ||
devId: model.get('&did') | ||
metric1: model.get('metric1'), | ||
metric2: model.get('metric2'), | ||
nonInteraction: model.get('nonInteraction'), | ||
devId: model.get('&did'), | ||
'&_av': model.get('&_av'), | ||
'&_au': model.get('&_au') | ||
}); | ||
@@ -63,6 +69,5 @@ }); | ||
return function() { | ||
return browser.execute(this.getHitData).then(function(hitData) { | ||
return expected.every(function(item) { | ||
return get(hitData.value, item[0]) === item[1]; | ||
}); | ||
var hitData = browser.execute(this.getHitData); | ||
return expected.every(function(item) { | ||
return get(hitData.value, item[0]) === item[1]; | ||
}); | ||
@@ -83,6 +88,5 @@ }.bind(this); | ||
return function() { | ||
return browser.execute(this.getTrackerData).then(function(trackerData) { | ||
return expected.every(function(item) { | ||
return get(trackerData.value, item[0]) === item[1]; | ||
}); | ||
var trackerData = browser.execute(this.getTrackerData); | ||
return expected.every(function(item) { | ||
return get(trackerData.value, item[0]) === item[1]; | ||
}); | ||
@@ -89,0 +93,0 @@ }.bind(this); |
@@ -20,101 +20,51 @@ /** | ||
var ga = require('./analytics'); | ||
var constants = require('../lib/constants'); | ||
var utilities = require('./utilities'); | ||
var browserCaps; | ||
describe('autotrack', function() { | ||
before(function() { | ||
browserCaps = browser.session().value; | ||
browser.url('/test/autotrack.html'); | ||
}); | ||
afterEach(function() { | ||
browser | ||
.execute(ga.clearHitData) | ||
.execute(ga.run, 'eventTracker:remove') | ||
.execute(ga.run, 'mediaQueryTracker:remove') | ||
.execute(ga.run, 'outboundFormTracker:remove') | ||
.execute(ga.run, 'outboundLinkTracker:remove') | ||
.execute(ga.run, 'socialTracker:remove') | ||
.execute(ga.run, 'urlChangeTracker:remove') | ||
.execute(utilities.untrackConsoleErrors) | ||
.execute(ga.run, 'remove'); | ||
}); | ||
it('should provide all plugins', function() { | ||
var gaplugins = browser | ||
.url('/test/autotrack.html') | ||
.execute(ga.getProvidedPlugins) | ||
.value; | ||
assert(gaplugins.Autotrack); | ||
assert(gaplugins.EventTracker); | ||
assert(gaplugins.MediaQueryTracker); | ||
assert(gaplugins.OutboundFormTracker); | ||
assert(gaplugins.OutboundLinkTracker); | ||
assert(gaplugins.SocialTracker); | ||
assert(gaplugins.UrlChangeTracker); | ||
}); | ||
it('should provide plugins even if sourced before the tracking snippet', | ||
it('should log a deprecation error when requiring autotrack directly', | ||
function() { | ||
var gaplugins = browser | ||
.url('/test/autotrack-reorder.html') | ||
.execute(ga.run, 'create', 'UA-XXXXX-Y', 'auto') | ||
.execute(ga.trackHitData) | ||
.execute(ga.run, 'require', 'autotrack') | ||
.execute(ga.getProvidedPlugins) | ||
.value; | ||
if (notSupportedInBrowser()) return; | ||
assert(gaplugins.Autotrack); | ||
assert(gaplugins.EventTracker); | ||
assert(gaplugins.MediaQueryTracker); | ||
assert(gaplugins.OutboundFormTracker); | ||
assert(gaplugins.OutboundLinkTracker); | ||
assert(gaplugins.SocialTracker); | ||
assert(gaplugins.UrlChangeTracker); | ||
var hitData = browser | ||
.execute(ga.run, 'send', 'pageview') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
}); | ||
it('should work with renaming the global object', function() { | ||
var gaplugins = browser | ||
.url('/test/autotrack-rename.html') | ||
var consoleErrors = browser | ||
.execute(utilities.trackConsoleErrors) | ||
.execute(ga.run, 'create', 'UA-XXXXX-Y', 'auto') | ||
.execute(ga.trackHitData) | ||
.execute(ga.run, 'require', 'autotrack') | ||
.execute(ga.getProvidedPlugins) | ||
.execute(utilities.getConsoleErrors) | ||
.value; | ||
assert(gaplugins.Autotrack); | ||
assert(gaplugins.EventTracker); | ||
assert(gaplugins.MediaQueryTracker); | ||
assert(gaplugins.OutboundFormTracker); | ||
assert(gaplugins.OutboundLinkTracker); | ||
assert(gaplugins.SocialTracker); | ||
assert(gaplugins.UrlChangeTracker); | ||
var hitData = browser | ||
.execute(ga.run, 'send', 'pageview') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert(consoleErrors.length, 1); | ||
assert(consoleErrors[0][0].indexOf('https://goo.gl/sZ2WrW') > -1); | ||
}); | ||
}); | ||
it('should include the &did param with all hits', function() { | ||
browser | ||
.url('/test/autotrack.html') | ||
.execute(ga.run, 'create', 'UA-XXXXX-Y', 'auto') | ||
.execute(ga.trackHitData) | ||
.execute(ga.run, 'require', 'autotrack') | ||
.execute(ga.run, 'send', 'pageview') | ||
.waitUntil(ga.hitDataMatches([['[0].devId', constants.DEV_ID]])); | ||
}); | ||
}); | ||
/** | ||
* @return {boolean} True if the current browser doesn't support all features | ||
* required for these tests. | ||
*/ | ||
function notSupportedInBrowser() { | ||
// IE9 doesn't support `console.error`, so it's not tested. | ||
return browserCaps.browserName == 'MicrosoftEdge' || | ||
(browserCaps.browserName == 'internet explorer' && | ||
browserCaps.version == '9'); | ||
} |
@@ -20,2 +20,3 @@ /** | ||
var ga = require('./analytics'); | ||
var utilities = require('./utilities'); | ||
var constants = require('../lib/constants'); | ||
@@ -38,2 +39,3 @@ | ||
browser | ||
.execute(utilities.unstopSubmitEvents) | ||
.execute(ga.clearHitData) | ||
@@ -48,6 +50,7 @@ .execute(ga.run, 'eventTracker:remove') | ||
.execute(ga.run, 'require', 'eventTracker') | ||
.click('#event-button') | ||
.click('#click-test') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventCategory, 'foo'); | ||
@@ -57,56 +60,137 @@ assert.equal(hitData[0].eventAction, 'bar'); | ||
assert.equal(hitData[0].eventValue, '42'); | ||
assert.equal(hitData[0].dimension1, 'baz'); | ||
assert.equal(hitData[0].nonInteraction, true); | ||
}); | ||
it('should support only specifying some of the event fields', function() { | ||
it('should support customizing the attribute prefix', function() { | ||
var hitData = browser | ||
.execute(ga.run, 'require', 'eventTracker') | ||
.click('#event-button-some-fields') | ||
.execute(ga.run, 'require', 'eventTracker', { | ||
attributePrefix: 'data-ga-' | ||
}) | ||
.click('#custom-prefix') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventCategory, 'foo'); | ||
assert.equal(hitData[0].eventAction, 'bar'); | ||
assert.equal(hitData[0].eventLabel, 'qux'); | ||
assert.equal(hitData[0].eventValue, undefined); | ||
}); | ||
it('should not capture clicks without the category and action fields', | ||
function() { | ||
it('should support non-event hit types', function() { | ||
var hitData = browser | ||
.execute(ga.run, 'require', 'eventTracker') | ||
.click('#event-button-missing-fields') | ||
.click('#social-hit-type') | ||
.click('#pageview-hit-type') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 0); | ||
assert.equal(hitData.length, 2); | ||
assert.equal(hitData[0].hitType, 'social'); | ||
assert.equal(hitData[0].socialNetwork, 'Facebook'); | ||
assert.equal(hitData[0].socialAction, 'like'); | ||
assert.equal(hitData[0].socialTarget, 'me'); | ||
assert.equal(hitData[1].hitType, 'pageview'); | ||
assert.equal(hitData[1].page, '/foobar.html'); | ||
}); | ||
it('should support customizing the attribute prefix', function() { | ||
it('should support customizing what events to listen for', function() { | ||
var hitData = browser | ||
.execute(ga.run, 'require', 'eventTracker', {attributePrefix: ''}) | ||
.click('#event-button-custom-prefix') | ||
.execute(utilities.stopSubmitEvents) | ||
.execute(ga.run, 'require', 'eventTracker', { | ||
events: ['submit'] | ||
}) | ||
.click('#click-test') | ||
.click('#submit-test') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData[0].eventCategory, 'foo'); | ||
assert.equal(hitData[0].eventAction, 'bar'); | ||
assert.equal(hitData[0].eventLabel, 'qux'); | ||
assert.equal(hitData[0].eventValue, 42); | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventCategory, 'Forms'); | ||
assert.equal(hitData[0].eventAction, 'submit'); | ||
}); | ||
it('should include the &did param with all hits', function() { | ||
it('should support specifying a fields object for all hits', function() { | ||
browser | ||
var hitData = browser | ||
.execute(utilities.stopSubmitEvents) | ||
.execute(ga.run, 'require', 'eventTracker', { | ||
fieldsObj: { | ||
nonInteraction: true, | ||
dimension1: 'foo', | ||
dimension2: 'bar' | ||
} | ||
}) | ||
.click('#social-hit-type') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].nonInteraction, true); | ||
assert.equal(hitData[0].dimension1, 'foo'); | ||
assert.equal(hitData[0].dimension2, 'bar'); | ||
}); | ||
it('should support specifying a hit filter', function() { | ||
var hitData = browser | ||
.execute(utilities.stopSubmitEvents) | ||
.execute(requireEventTrackerWithHitFilter) | ||
.click('#click-test') | ||
.click('#pageview-hit-type') | ||
.click('#social-hit-type') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].nonInteraction, true); | ||
assert.equal(hitData[0].dimension1, 'foo'); | ||
assert.equal(hitData[0].dimension2, 'bar'); | ||
}); | ||
it('includes usage params with all hits', function() { | ||
var hitData = browser | ||
.execute(ga.run, 'require', 'eventTracker') | ||
.execute(ga.run, 'send', 'pageview') | ||
.waitUntil(ga.hitDataMatches([['[0].devId', constants.DEV_ID]])); | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].devId, constants.DEV_ID); | ||
assert.equal(hitData[0][constants.VERSION_PARAM], constants.VERSION); | ||
// '2' = '000000010' in hex | ||
assert.equal(hitData[0][constants.USAGE_PARAM], '2'); | ||
}); | ||
}); | ||
/** | ||
* Since function objects can't be passed via parameters from server to | ||
* client, this one-off function must be used to set the value for | ||
* `hitFilter`. | ||
*/ | ||
function requireEventTrackerWithHitFilter() { | ||
ga('require', 'eventTracker', { | ||
hitFilter: function(model, element) { | ||
if (element.id != 'social-hit-type') { | ||
throw 'Aborting non-social hits'; | ||
} | ||
else { | ||
model.set('nonInteraction', true); | ||
model.set('dimension1', 'foo'); | ||
model.set('dimension2', 'bar'); | ||
} | ||
} | ||
}); | ||
} |
@@ -27,4 +27,4 @@ /** | ||
var autotrackOpts = { | ||
mediaQueryDefinitions: [ | ||
var opts = { | ||
definitions: [ | ||
{ | ||
@@ -83,3 +83,3 @@ name: 'Width', | ||
browser | ||
.execute(ga.run, 'require', 'mediaQueryTracker', autotrackOpts) | ||
.execute(ga.run, 'require', 'mediaQueryTracker', opts) | ||
.waitUntil(ga.trackerDataMatches([ | ||
@@ -97,3 +97,3 @@ ['dimension1', 'lg'], | ||
browser | ||
.execute(ga.run, 'require', 'mediaQueryTracker', autotrackOpts) | ||
.execute(ga.run, 'require', 'mediaQueryTracker', opts) | ||
.setViewportSize({width: 400, height: 400}, false) | ||
@@ -122,3 +122,3 @@ .waitUntil(ga.trackerDataMatches([ | ||
browser | ||
.execute(ga.run, 'require', 'mediaQueryTracker', autotrackOpts) | ||
.execute(ga.run, 'require', 'mediaQueryTracker', opts) | ||
.setViewportSize({width: 400, height: 400}, false); | ||
@@ -146,3 +146,3 @@ | ||
.execute(ga.run, 'require', 'mediaQueryTracker', | ||
Object.assign({}, autotrackOpts, {mediaQueryChangeTimeout: 0})) | ||
Object.assign({}, opts, {changeTimeout: 0})) | ||
.setViewportSize({width: 400, height: 400}, false); | ||
@@ -167,3 +167,3 @@ | ||
.setViewportSize({width: 800, height: 600}, false) | ||
.execute(ga.run, 'require', 'mediaQueryTracker', autotrackOpts) | ||
.execute(ga.run, 'require', 'mediaQueryTracker', opts) | ||
.setViewportSize({width: 400, height: 400}, false); | ||
@@ -192,3 +192,3 @@ | ||
browser | ||
.execute(requireMediaQueryTrackerWithChangeTemplate) | ||
.execute(requireMediaQueryTracker_changeTemplate) | ||
.setViewportSize({width: 400, height: 400}, false) | ||
@@ -201,9 +201,58 @@ .waitUntil(ga.hitDataMatches([ | ||
it('should support customizing any field via the fieldsObj', function() { | ||
it('should include the &did param with all hits', function() { | ||
if (notSupportedInBrowser()) return; | ||
browser | ||
.execute(ga.run, 'require', 'mediaQueryTracker', | ||
Object.assign({}, opts, { | ||
changeTimeout: 0, | ||
fieldsObj: { | ||
nonInteraction: true | ||
} | ||
})) | ||
.setViewportSize({width: 400, height: 400}, false) | ||
.waitUntil(ga.hitDataMatches([ | ||
['[0].eventCategory', 'Width'], | ||
['[0].eventAction', 'change'], | ||
['[0].eventLabel', 'lg => sm'], | ||
['[0].nonInteraction', true], | ||
['[1].eventCategory', 'Height'], | ||
['[1].eventAction', 'change'], | ||
['[1].eventLabel', 'md => sm'], | ||
['[1].nonInteraction', true] | ||
])); | ||
}); | ||
it('should support specifying a hit filter', function() { | ||
if (notSupportedInBrowser()) return; | ||
browser | ||
.execute(requireMediaQueryTracker_hitFilter) | ||
.setViewportSize({width: 400, height: 400}, false) | ||
.waitUntil(ga.hitDataMatches([ | ||
['[0].eventCategory', 'Height'], | ||
['[0].eventAction', 'change'], | ||
['[0].eventLabel', 'md => sm'], | ||
['[0].nonInteraction', true] | ||
])); | ||
}); | ||
it('includes usage params with all hits', function() { | ||
var hitData = browser | ||
.execute(ga.run, 'require', 'mediaQueryTracker') | ||
.execute(ga.run, 'send', 'pageview') | ||
.waitUntil(ga.hitDataMatches([['[0].devId', constants.DEV_ID]])); | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].devId, constants.DEV_ID); | ||
assert.equal(hitData[0][constants.VERSION_PARAM], constants.VERSION); | ||
// '8' = '000001000' in hex | ||
assert.equal(hitData[0][constants.USAGE_PARAM], '8'); | ||
}); | ||
@@ -215,9 +264,25 @@ | ||
/** | ||
* @return {boolean} True if the current browser doesn't support all features | ||
* required for these tests. | ||
*/ | ||
function notSupportedInBrowser() { | ||
// TODO(philipwalton): Some capabilities aren't implemented, so we can't test | ||
// against Edge right now. Wait for build 10532 to support setViewportSize | ||
// https://dev.windows.com/en-us/microsoft-edge/platform/status/webdriver/details/ | ||
// IE9 doesn't support matchMedia, so it's not tested. | ||
return browserCaps.browserName == 'MicrosoftEdge' || | ||
(browserCaps.browserName == 'internet explorer' && | ||
browserCaps.version == '9'); | ||
} | ||
/** | ||
* Since function objects can't be passed via parameters from server to | ||
* client, this one-off function must be used to set the value for | ||
* `mediaQueryChangeTemplate`. | ||
* `changeTemplate`. | ||
*/ | ||
function requireMediaQueryTrackerWithChangeTemplate() { | ||
function requireMediaQueryTracker_changeTemplate() { | ||
ga('require', 'mediaQueryTracker', { | ||
mediaQueryDefinitions: [ | ||
definitions: [ | ||
{ | ||
@@ -242,3 +307,3 @@ name: 'Width', | ||
], | ||
mediaQueryChangeTemplate: function(oldValue, newValue) { | ||
changeTemplate: function(oldValue, newValue) { | ||
return oldValue + ':' + newValue; | ||
@@ -251,14 +316,39 @@ } | ||
/** | ||
* @return {boolean} True if the current browser doesn't support all features | ||
* required for these tests. | ||
* Since function objects can't be passed via parameters from server to | ||
* client, this one-off function must be used to set the value for | ||
* `hitFilter`. | ||
*/ | ||
function notSupportedInBrowser() { | ||
// TODO(philipwalton): Some capabilities aren't implemented, so we can't test | ||
// against Edge right now. Wait for build 10532 to support setViewportSize | ||
// https://dev.windows.com/en-us/microsoft-edge/platform/status/webdriver/details/ | ||
function requireMediaQueryTracker_hitFilter() { | ||
ga('require', 'mediaQueryTracker', { | ||
definitions: [ | ||
{ | ||
name: 'Width', | ||
dimensionIndex: 1, | ||
items: [ | ||
{name: 'sm', media: 'all'}, | ||
{name: 'md', media: '(min-width: 480px)'}, | ||
{name: 'lg', media: '(min-width: 640px)'} | ||
] | ||
}, | ||
{ | ||
name: 'Height', | ||
dimensionIndex: 2, | ||
items: [ | ||
{name: 'sm', media: 'all'}, | ||
{name: 'md', media: '(min-height: 480px)'}, | ||
{name: 'lg', media: '(min-height: 640px)'} | ||
] | ||
} | ||
], | ||
hitFilter: function(model) { | ||
var category = model.get('eventCategory'); | ||
if (category == 'Width') { | ||
throw 'Exclude width changes'; | ||
} | ||
else { | ||
model.set('nonInteraction', true); | ||
} | ||
} | ||
}); | ||
} | ||
// IE9 doesn't support matchMedia, so it's not tested. | ||
return browserCaps.browserName == 'MicrosoftEdge' || | ||
(browserCaps.browserName == 'internet explorer' && | ||
browserCaps.version == '9'); | ||
} |
@@ -34,12 +34,14 @@ /** | ||
var hitData = browser | ||
.execute(utilities.stopFormSubmitEvents) | ||
.execute(utilities.stopSubmitEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(ga.run, 'require', 'outboundFormTracker') | ||
.click('#submit-1') | ||
.click('#outbound-submit') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventCategory, 'Outbound Form'); | ||
assert.equal(hitData[0].eventAction, 'submit'); | ||
assert.equal(hitData[0].eventLabel, 'https://google-analytics.com/collect'); | ||
assert.equal(hitData[0].eventLabel, | ||
'https://www.google-analytics.com/collect'); | ||
}); | ||
@@ -51,6 +53,6 @@ | ||
var hitData = browser | ||
.execute(utilities.stopFormSubmitEvents) | ||
.execute(utilities.stopSubmitEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(ga.run, 'require', 'outboundFormTracker') | ||
.click('#submit-2') | ||
.click('#local-submit') | ||
.execute(ga.getHitData) | ||
@@ -66,6 +68,6 @@ .value; | ||
var hitData = browser | ||
.execute(utilities.stopFormSubmitEvents) | ||
.execute(utilities.stopSubmitEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(ga.run, 'require', 'outboundFormTracker') | ||
.click('#submit-3') | ||
.click('#action-less-submit') | ||
.execute(ga.getHitData) | ||
@@ -78,19 +80,2 @@ .value; | ||
it('should allow customizing what is considered an outbound form', | ||
function() { | ||
var testData = browser | ||
.execute(utilities.stopFormSubmitEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(requireOutboundFormTrackerWithConditional) | ||
.click('#submit-1') | ||
.click('#submit-2') | ||
.click('#submit-3') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert(!testData.length); | ||
}); | ||
it('should navigate to the proper outbound location on submit', function() { | ||
@@ -101,4 +86,5 @@ | ||
.execute(ga.run, 'require', 'outboundFormTracker') | ||
.click('#submit-1') | ||
.waitUntil(utilities.urlMatches('https://google-analytics.com/collect')); | ||
.click('#outbound-submit') | ||
.waitUntil(utilities.urlMatches( | ||
'https://www.google-analytics.com/collect')); | ||
@@ -115,3 +101,3 @@ // Restores the page state. | ||
.execute(ga.run, 'require', 'outboundFormTracker') | ||
.click('#submit-2') | ||
.click('#local-submit') | ||
.waitUntil(utilities.urlMatches('/test/blank.html')); | ||
@@ -131,3 +117,3 @@ | ||
.execute(ga.run, 'require', 'outboundFormTracker') | ||
.click('#submit-1') | ||
.click('#outbound-submit') | ||
.execute(ga.getHitData) | ||
@@ -137,5 +123,7 @@ .value; | ||
// Tests that the hit is sent. | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventCategory, 'Outbound Form'); | ||
assert.equal(hitData[0].eventAction, 'submit'); | ||
assert.equal(hitData[0].eventLabel, 'https://google-analytics.com/collect'); | ||
assert.equal(hitData[0].eventLabel, | ||
'https://www.google-analytics.com/collect'); | ||
@@ -148,4 +136,5 @@ // Tests that navigation actually happens | ||
.execute(ga.run, 'require', 'outboundFormTracker') | ||
.click('#submit-1') | ||
.waitUntil(utilities.urlMatches('https://google-analytics.com/collect')); | ||
.click('#outbound-submit') | ||
.waitUntil(utilities.urlMatches( | ||
'https://www.google-analytics.com/collect')); | ||
@@ -159,8 +148,156 @@ // Restores the page state. | ||
it('should include the &did param with all hits', function() { | ||
it('should support customizing the selector used to detect form submits', | ||
function() { | ||
browser | ||
var hitData = browser | ||
.execute(utilities.stopSubmitEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(ga.run, 'require', 'outboundFormTracker', { | ||
formSelector: '.form' | ||
}) | ||
.click('#outbound-submit-with-class') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventCategory, 'Outbound Form'); | ||
assert.equal(hitData[0].eventAction, 'submit'); | ||
assert.equal(hitData[0].eventLabel, 'https://example.com/'); | ||
}); | ||
it('should support customizing what is considered an outbound form', | ||
function() { | ||
var hitData = browser | ||
.execute(utilities.stopSubmitEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(requireOutboundFormTracker_shouldTrackOutboundForm) | ||
.click('#outbound-submit') | ||
.click('#outbound-submit-with-class') | ||
.click('#local-submit') | ||
.click('#action-less-submit') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventCategory, 'Outbound Form'); | ||
assert.equal(hitData[0].eventAction, 'submit'); | ||
assert.equal(hitData[0].eventLabel, 'https://example.com/'); | ||
}); | ||
it('should support customizing any field via the fieldsObj', function() { | ||
var hitData = browser | ||
.execute(utilities.stopSubmitEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(ga.run, 'require', 'outboundFormTracker', { | ||
fieldsObj: { | ||
eventCategory: 'External Form', | ||
eventAction: 'send', | ||
nonInteraction: true | ||
} | ||
}) | ||
.click('#outbound-submit') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventCategory, 'External Form'); | ||
assert.equal(hitData[0].eventAction, 'send'); | ||
assert.equal(hitData[0].eventLabel, | ||
'https://www.google-analytics.com/collect'); | ||
assert.equal(hitData[0].nonInteraction, true); | ||
}); | ||
it('supports setting attributes declaratively', function() { | ||
var hitData = browser | ||
.execute(utilities.stopSubmitEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(ga.run, 'require', 'outboundFormTracker') | ||
.click('#declarative-attributes-submit') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventCategory, 'External Form'); | ||
assert.equal(hitData[0].eventAction, 'submit'); | ||
assert.equal(hitData[0].dimension1, true); | ||
}); | ||
it('supports customizing the attribute prefix', function() { | ||
var hitData = browser | ||
.execute(utilities.stopSubmitEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(ga.run, 'require', 'outboundFormTracker', { | ||
attributePrefix: 'data-ga-' | ||
}) | ||
.click('#declarative-attributes-prefix-submit') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventAction, 'submit'); | ||
assert.equal(hitData[0].eventLabel, 'www.google-analytics.com'); | ||
assert.equal(hitData[0].nonInteraction, true); | ||
}); | ||
it('should support specifying a hit filter', function() { | ||
var hitData = browser | ||
.execute(utilities.stopSubmitEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(requireOutboundFormTracker_hitFilter) | ||
.click('#outbound-submit') | ||
.click('#outbound-submit-with-class') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventCategory, 'Outbound Form'); | ||
assert.equal(hitData[0].eventAction, 'submit'); | ||
assert.equal(hitData[0].eventLabel, 'https://example.com/'); | ||
assert.equal(hitData[0].nonInteraction, true); | ||
}); | ||
it('should support forms in shadow DOM and event retargetting', function() { | ||
if (notSupportedInBrowser()) return; | ||
var hitData = browser | ||
.execute(utilities.stopSubmitEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(ga.run, 'require', 'outboundFormTracker') | ||
.execute(simulateSubmitFromInsideShadowDom) | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventCategory, 'Outbound Form'); | ||
assert.equal(hitData[0].eventAction, 'submit'); | ||
assert.equal(hitData[0].eventLabel, 'https://example.com/'); | ||
}); | ||
it('includes usage params with all hits', function() { | ||
var hitData = browser | ||
.execute(ga.run, 'require', 'outboundFormTracker') | ||
.execute(ga.run, 'send', 'pageview') | ||
.waitUntil(ga.hitDataMatches([['[0].devId', constants.DEV_ID]])); | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].devId, constants.DEV_ID); | ||
assert.equal(hitData[0][constants.VERSION_PARAM], constants.VERSION); | ||
// '10' = '000010000' in hex | ||
assert.equal(hitData[0][constants.USAGE_PARAM], '10'); | ||
}); | ||
@@ -194,3 +331,3 @@ | ||
browser | ||
.execute(utilities.unstopFormSubmitEvents) | ||
.execute(utilities.unstopSubmitEvents) | ||
.execute(ga.clearHitData) | ||
@@ -203,2 +340,13 @@ .execute(ga.run, 'outboundFormTracker:remove') | ||
/** | ||
* @return {boolean} True if the current browser doesn't support all features | ||
* required for these tests. | ||
*/ | ||
function notSupportedInBrowser() { | ||
return browser.execute(function() { | ||
return !Element.prototype.attachShadow; | ||
}).value; | ||
} | ||
/** | ||
* Since function objects can't be passed via parameters from server to | ||
@@ -208,11 +356,41 @@ * client, this one-off function must be used to set the value for | ||
*/ | ||
function requireOutboundFormTrackerWithConditional() { | ||
function requireOutboundFormTracker_shouldTrackOutboundForm() { | ||
ga('require', 'outboundFormTracker', { | ||
shouldTrackOutboundForm: function(form) { | ||
var action = form.getAttribute('action'); | ||
return action && | ||
action.indexOf('http') === 0 && | ||
action.indexOf('google-analytics.com') < 0; | ||
shouldTrackOutboundForm: function(form, parseUrl) { | ||
return parseUrl(form.action).hostname == 'example.com'; | ||
} | ||
}); | ||
} | ||
/** | ||
* Since function objects can't be passed via parameters from server to | ||
* client, this one-off function must be used to set the value for | ||
* `hitFilter`. | ||
*/ | ||
function requireOutboundFormTracker_hitFilter() { | ||
ga('require', 'outboundFormTracker', { | ||
hitFilter: function(model, form) { | ||
if (form.action.indexOf('www.google-analytics.com') > -1) { | ||
throw 'Exclude hits to www.google-analytics.com'; | ||
} | ||
else { | ||
model.set('nonInteraction', true); | ||
} | ||
} | ||
}); | ||
} | ||
/** | ||
* Webdriver does not currently support selecting elements inside a shadow | ||
* tree, so we have to fake it. | ||
*/ | ||
function simulateSubmitFromInsideShadowDom() { | ||
var shadowHost = document.getElementById('shadow-host'); | ||
var form = shadowHost.shadowRoot.querySelector('form'); | ||
var event = document.createEvent('Event'); | ||
event.initEvent('submit', true, true); | ||
form.dispatchEvent(event); | ||
} |
@@ -24,2 +24,5 @@ /** | ||
var browserCaps; | ||
describe('outboundLinkTracker', function() { | ||
@@ -35,3 +38,3 @@ | ||
var hitData = browser | ||
.execute(utilities.stopLinkClickEvents) | ||
.execute(utilities.stopClickEvents) | ||
.execute(utilities.stubBeacon) | ||
@@ -46,3 +49,4 @@ .execute(ga.run, 'require', 'outboundLinkTracker') | ||
assert.equal(hitData[0].eventAction, 'click'); | ||
assert.equal(hitData[0].eventLabel, 'https://google-analytics.com/collect'); | ||
assert.equal(hitData[0].eventLabel, | ||
'https://www.google-analytics.com/collect'); | ||
}); | ||
@@ -54,3 +58,3 @@ | ||
var hitData = browser | ||
.execute(utilities.stopLinkClickEvents) | ||
.execute(utilities.stopClickEvents) | ||
.execute(utilities.stubBeacon) | ||
@@ -69,3 +73,3 @@ .execute(ga.run, 'require', 'outboundLinkTracker') | ||
var hitData = browser | ||
.execute(utilities.stopLinkClickEvents) | ||
.execute(utilities.stopClickEvents) | ||
.execute(utilities.stubBeacon) | ||
@@ -82,17 +86,2 @@ .execute(ga.run, 'require', 'outboundLinkTracker') | ||
it('should allow customizing what is considered an outbound link', | ||
function() { | ||
var hitData = browser | ||
.execute(utilities.stopLinkClickEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(requireOutboundLinkTrackerWithConditional) | ||
.click('#outbound-link') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 0); | ||
}); | ||
it('should navigate to the proper location on outbound clicks', function() { | ||
@@ -104,3 +93,4 @@ | ||
.click('#outbound-link') | ||
.waitUntil(utilities.urlMatches('https://google-analytics.com/collect')); | ||
.waitUntil(utilities.urlMatches( | ||
'https://www.google-analytics.com/collect')); | ||
@@ -130,3 +120,3 @@ // Restores the page state. | ||
.execute(utilities.stubNoBeacon) | ||
.execute(utilities.stopLinkClickEvents) | ||
.execute(utilities.stopClickEvents) | ||
.execute(ga.run, 'require', 'outboundLinkTracker') | ||
@@ -140,8 +130,181 @@ .click('#outbound-link') | ||
it('should include the &did param with all hits', function() { | ||
it('supports events other than click', function() { | ||
browser | ||
if (!browserSupportsRightClick()) return; | ||
var hitData = browser | ||
.execute(utilities.stopClickEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(ga.run, 'require', 'outboundLinkTracker', { | ||
events: ['mousedown', 'contextmenu'] | ||
}) | ||
.rightClick('#outbound-link') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 2); | ||
assert.equal(hitData[0].eventCategory, 'Outbound Link'); | ||
assert.equal(hitData[0].eventAction, 'mousedown'); | ||
assert.equal(hitData[0].eventLabel, | ||
'https://www.google-analytics.com/collect'); | ||
assert.equal(hitData[1].eventCategory, 'Outbound Link'); | ||
assert.equal(hitData[1].eventAction, 'contextmenu'); | ||
assert.equal(hitData[1].eventLabel, | ||
'https://www.google-analytics.com/collect'); | ||
}); | ||
it('should support customizing the selector used to detect link clicks', | ||
function() { | ||
var hitData = browser | ||
.execute(utilities.stopClickEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(ga.run, 'require', 'outboundLinkTracker', { | ||
linkSelector: '.link' | ||
}) | ||
.click('#outbound-link') | ||
.click('#outbound-link-with-class') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventCategory, 'Outbound Link'); | ||
assert.equal(hitData[0].eventAction, 'click'); | ||
assert.equal(hitData[0].eventLabel, 'https://example.com/'); | ||
}); | ||
it('should support customizing what is considered an outbound link', | ||
function() { | ||
var hitData = browser | ||
.execute(utilities.stopClickEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(requireOutboundLinkTracker_shouldTrackOutboundLink) | ||
.click('#outbound-link') | ||
.click('#outbound-link-with-class') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventCategory, 'Outbound Link'); | ||
assert.equal(hitData[0].eventAction, 'click'); | ||
assert.equal(hitData[0].eventLabel, 'https://example.com/'); | ||
}); | ||
it('should support customizing any field via the fieldsObj', function() { | ||
var hitData = browser | ||
.execute(utilities.stopClickEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(ga.run, 'require', 'outboundLinkTracker', { | ||
fieldsObj: { | ||
eventCategory: 'External Link', | ||
eventAction: 'tap', | ||
nonInteraction: true | ||
} | ||
}) | ||
.click('#outbound-link') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventCategory, 'External Link'); | ||
assert.equal(hitData[0].eventAction, 'tap'); | ||
assert.equal(hitData[0].eventLabel, | ||
'https://www.google-analytics.com/collect'); | ||
assert.equal(hitData[0].nonInteraction, true); | ||
}); | ||
it('supports setting attributes declaratively', function() { | ||
var hitData = browser | ||
.execute(utilities.stopClickEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(ga.run, 'require', 'outboundLinkTracker') | ||
.click('#declarative-attributes') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventCategory, 'External Link'); | ||
assert.equal(hitData[0].eventAction, 'click'); | ||
assert.equal(hitData[0].dimension1, true); | ||
}); | ||
it('supports customizing the attribute prefix', function() { | ||
var hitData = browser | ||
.execute(utilities.stopClickEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(ga.run, 'require', 'outboundLinkTracker', { | ||
attributePrefix: 'data-ga-' | ||
}) | ||
.click('#declarative-attributes-prefix') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventAction, 'click'); | ||
assert.equal(hitData[0].eventLabel, 'www.google-analytics.com'); | ||
assert.equal(hitData[0].nonInteraction, true); | ||
}); | ||
it('should support specifying a hit filter', function() { | ||
var hitData = browser | ||
.execute(utilities.stopClickEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(requireOutboundLinkTracker_hitFilter) | ||
.click('#outbound-link') | ||
.click('#outbound-link-with-class') | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventCategory, 'Outbound Link'); | ||
assert.equal(hitData[0].eventAction, 'click'); | ||
assert.equal(hitData[0].eventLabel, 'https://example.com/'); | ||
assert.equal(hitData[0].nonInteraction, true); | ||
}); | ||
it('should support links in shadow DOM and event retargetting', function() { | ||
if (!browserSupportsShadowDom()) return; | ||
var hitData = browser | ||
.execute(utilities.stopClickEvents) | ||
.execute(utilities.stubBeacon) | ||
.execute(ga.run, 'require', 'outboundLinkTracker') | ||
.execute(simulateClickFromInsideShadowDom) | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].eventCategory, 'Outbound Link'); | ||
assert.equal(hitData[0].eventAction, 'click'); | ||
assert.equal(hitData[0].eventLabel, 'https://example.com/'); | ||
}); | ||
it('includes usage params with all hits', function() { | ||
var hitData = browser | ||
.execute(ga.run, 'require', 'outboundLinkTracker') | ||
.execute(ga.run, 'send', 'pageview') | ||
.waitUntil(ga.hitDataMatches([['[0].devId', constants.DEV_ID]])); | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].devId, constants.DEV_ID); | ||
assert.equal(hitData[0][constants.VERSION_PARAM], constants.VERSION); | ||
// '20' = '000100000' in hex | ||
assert.equal(hitData[0][constants.USAGE_PARAM], '20'); | ||
}); | ||
@@ -156,2 +319,3 @@ | ||
function setupPage() { | ||
browserCaps = browser.session().value; | ||
browser.url('/test/outbound-link-tracker.html'); | ||
@@ -176,3 +340,3 @@ } | ||
browser | ||
.execute(utilities.unstopLinkClickEvents) | ||
.execute(utilities.unstopClickEvents) | ||
.execute(ga.clearHitData) | ||
@@ -185,2 +349,21 @@ .execute(ga.run, 'outboundLinkTracker:remove') | ||
/** | ||
* @return {boolean} True if the current browser supports Shadow DOM. | ||
*/ | ||
function browserSupportsShadowDom() { | ||
return browser.execute(function() { | ||
return Element.prototype.attachShadow; | ||
}).value; | ||
} | ||
/** | ||
* @return {boolean} True if the browser driver supports the rightClick method. | ||
*/ | ||
function browserSupportsRightClick() { | ||
// https://github.com/webdriverio/webdriverio/issues/1419 | ||
return browserCaps.browserName != 'safari'; | ||
} | ||
/** | ||
* Since function objects can't be passed via parameters from server to | ||
@@ -190,8 +373,41 @@ * client, this one-off function must be used to set the value for | ||
*/ | ||
function requireOutboundLinkTrackerWithConditional() { | ||
function requireOutboundLinkTracker_shouldTrackOutboundLink() { | ||
ga('require', 'outboundLinkTracker', { | ||
shouldTrackOutboundLink: function(link) { | ||
return link.hostname != 'google-analytics.com'; | ||
shouldTrackOutboundLink: function(link, parseUrl) { | ||
return parseUrl(link.href).hostname == 'example.com'; | ||
} | ||
}); | ||
} | ||
/** | ||
* Since function objects can't be passed via parameters from server to | ||
* client, this one-off function must be used to set the value for | ||
* `hitFilter`. | ||
*/ | ||
function requireOutboundLinkTracker_hitFilter() { | ||
ga('require', 'outboundLinkTracker', { | ||
hitFilter: function(model, link) { | ||
if (link.href.indexOf('www.google-analytics.com') > -1) { | ||
throw 'Exclude hits to www.google-analytics.com'; | ||
} | ||
else { | ||
model.set('nonInteraction', true); | ||
} | ||
} | ||
}); | ||
} | ||
/** | ||
* Webdriver does not currently support selecting elements inside a shadow | ||
* tree, so we have to fake it. | ||
*/ | ||
function simulateClickFromInsideShadowDom() { | ||
var shadowHost = document.getElementById('shadow-host'); | ||
var link = shadowHost.shadowRoot.querySelector('a'); | ||
var event = document.createEvent('Event'); | ||
event.initEvent('click', true, true); | ||
link.dispatchEvent(event); | ||
} |
@@ -100,2 +100,3 @@ /** | ||
assert.equal(hitData.length, 6); | ||
assert.equal(hitData[0].page, '/test/foo.html'); | ||
@@ -176,3 +177,3 @@ assert.equal(hitData[0].title, 'Foo'); | ||
browser.execute(requireUrlChangeTrackerTrackerWithConditional); | ||
browser.execute(requireUrlChangeTrackerTracker_shouldTrackUrlChange); | ||
@@ -199,8 +200,63 @@ var fooUrl = browser | ||
it('should include the &did param with all hits', function() { | ||
it('should support customizing any field via the fieldsObj', function() { | ||
browser | ||
if (notSupportedInBrowser()) return; | ||
browser.execute(ga.run, 'require', 'urlChangeTracker', { | ||
fieldsObj: { | ||
dimension1: 'urlChangeTracker' | ||
} | ||
}); | ||
browser.click('#foo'); | ||
browser.back(); | ||
var hitData = browser | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 2); | ||
assert.equal(hitData[0].page, '/test/foo.html'); | ||
assert.equal(hitData[0].title, 'Foo'); | ||
assert.equal(hitData[0].dimension1, 'urlChangeTracker'); | ||
assert.equal(hitData[1].page, '/test/url-change-tracker.html'); | ||
assert.equal(hitData[1].title, 'Home'); | ||
assert.equal(hitData[1].dimension1, 'urlChangeTracker'); | ||
}); | ||
it('should support specifying a hit filter', function() { | ||
if (notSupportedInBrowser()) return; | ||
browser.execute(requireUrlChangeTrackerTracker_hitFilter); | ||
browser.click('#foo'); | ||
browser.back(); | ||
var hitData = browser | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].page, '/test/url-change-tracker.html'); | ||
assert.equal(hitData[0].title, 'Home'); | ||
assert.equal(hitData[0].dimension1, 'urlChangeTracker'); | ||
}); | ||
it('includes usage params with all hits', function() { | ||
var hitData = browser | ||
.execute(ga.run, 'require', 'urlChangeTracker') | ||
.execute(ga.run, 'send', 'pageview') | ||
.waitUntil(ga.hitDataMatches([['[0].devId', constants.DEV_ID]])); | ||
.execute(ga.getHitData) | ||
.value; | ||
assert.equal(hitData.length, 1); | ||
assert.equal(hitData[0].devId, constants.DEV_ID); | ||
assert.equal(hitData[0][constants.VERSION_PARAM], constants.VERSION); | ||
// '100' = '100000000' in hex | ||
assert.equal(hitData[0][constants.USAGE_PARAM], '100'); | ||
}); | ||
@@ -212,2 +268,13 @@ | ||
/** | ||
* @return {boolean} True if the current browser doesn't support all features | ||
* required for these tests. | ||
*/ | ||
function notSupportedInBrowser() { | ||
// IE9 doesn't support the HTML5 History API. | ||
return browserCaps.browserName == 'internet explorer' && | ||
browserCaps.version == '9'; | ||
} | ||
/** | ||
* Since function objects can't be passed via parameters from server to | ||
@@ -217,3 +284,3 @@ * client, this one-off function must be used to set the value for | ||
*/ | ||
function requireUrlChangeTrackerTrackerWithConditional() { | ||
function requireUrlChangeTrackerTracker_shouldTrackUrlChange() { | ||
ga('require', 'urlChangeTracker', { | ||
@@ -228,9 +295,18 @@ shouldTrackUrlChange: function() { | ||
/** | ||
* @return {boolean} True if the current browser doesn't support all features | ||
* required for these tests. | ||
* Since function objects can't be passed via parameters from server to | ||
* client, this one-off function must be used to set the value for | ||
* `hitFilter`. | ||
*/ | ||
function notSupportedInBrowser() { | ||
// IE9 doesn't support the HTML5 History API. | ||
return browserCaps.browserName == 'internet explorer' && | ||
browserCaps.version == '9'; | ||
function requireUrlChangeTrackerTracker_hitFilter() { | ||
ga('require', 'urlChangeTracker', { | ||
hitFilter: function(model) { | ||
var title = model.get('title'); | ||
if (title == 'Foo') { | ||
throw 'Exclude Foo pages'; | ||
} | ||
else { | ||
model.set('dimension1', 'urlChangeTracker'); | ||
} | ||
} | ||
}); | ||
} |
@@ -28,6 +28,5 @@ /** | ||
return function() { | ||
return browser.url().then(function(result) { | ||
var actualUrl = result.value; | ||
return actualUrl.indexOf(expectedUrl) > -1; | ||
}); | ||
var result = browser.url(); | ||
var actualUrl = result.value; | ||
return actualUrl.indexOf(expectedUrl) > -1; | ||
}; | ||
@@ -41,3 +40,3 @@ }, | ||
*/ | ||
stopFormSubmitEvents: function() { | ||
stopSubmitEvents: function() { | ||
window.__stopFormSubmits__ = function(event) { | ||
@@ -54,3 +53,3 @@ event.preventDefault(); | ||
*/ | ||
unstopFormSubmitEvents: function() { | ||
unstopSubmitEvents: function() { | ||
document.removeEventListener('submit', window.__stopFormSubmits__); | ||
@@ -74,8 +73,8 @@ }, | ||
*/ | ||
stopLinkClickEvents: function() { | ||
window.__stopLinkClicks__ = function(event) { | ||
stopClickEvents: function() { | ||
window.__stopClicks__ = function(event) { | ||
event.preventDefault(); | ||
}; | ||
document.addEventListener('click', window.__stopLinkClicks__); | ||
document.addEventListener('click', window.__stopClicks__); | ||
}, | ||
@@ -87,4 +86,4 @@ | ||
*/ | ||
unstopLinkClickEvents: function() { | ||
document.removeEventListener('click', window.__stopLinkClicks__); | ||
unstopClickEvents: function() { | ||
document.removeEventListener('click', window.__stopClicks__); | ||
}, | ||
@@ -110,4 +109,38 @@ | ||
navigator.sendBeacon = undefined; | ||
}, | ||
/** | ||
* Wraps `console.error` and tracks calls to it. | ||
*/ | ||
trackConsoleErrors: function() { | ||
if (!window.console) return; | ||
window.__consoleErrors__ = []; | ||
window.__originalConsoleError__ = window.console.error; | ||
window.console.error = function() { | ||
window.__consoleErrors__.push(arguments); | ||
window.__originalConsoleError__.apply(window.console, arguments); | ||
}; | ||
}, | ||
/** | ||
* Restores the original `console.error`. | ||
*/ | ||
untrackConsoleErrors: function() { | ||
if (!window.console) return; | ||
delete window.__consoleErrors__; | ||
window.console.error = window.__originalConsoleError__ || | ||
window.console.error; | ||
}, | ||
/** | ||
* Returns all console error call arguments since tracking started. | ||
* @return {Array} The list of console error call arguments. | ||
*/ | ||
getConsoleErrors: function() { | ||
return window.__consoleErrors__; | ||
} | ||
}; |
@@ -34,3 +34,4 @@ /** | ||
exclude: [ | ||
// 'path/to/excluded/files' | ||
'./test/analytics.js', | ||
'./test/utilities.js' | ||
], | ||
@@ -54,3 +55,3 @@ // | ||
// | ||
maxInstances: 5, | ||
maxInstances: 2, | ||
// | ||
@@ -74,3 +75,3 @@ // If you have trouble getting all important capabilities together, check out the | ||
// Level of logging verbosity: silent | verbose | command | data | result | error | ||
logLevel: 'silent', | ||
logLevel: 'error', | ||
// | ||
@@ -88,7 +89,7 @@ // Enables colors for log output. | ||
// Default timeout for all waitFor* commands. | ||
waitforTimeout: 100000, | ||
waitforTimeout: 60000, | ||
// | ||
// Default timeout in milliseconds for request | ||
// if Selenium Grid doesn't send response | ||
connectionRetryTimeout: 100000, | ||
connectionRetryTimeout: 60000, | ||
// | ||
@@ -141,3 +142,3 @@ // Default request retries count | ||
ui: 'bdd', | ||
timeout: 100000 | ||
timeout: 60000 | ||
}, | ||
@@ -215,3 +216,3 @@ // | ||
{browserName: 'chrome'}, | ||
{browserName: 'firefox'} | ||
// {browserName: 'firefox'} | ||
]; | ||
@@ -227,3 +228,4 @@ | ||
browserName: 'firefox', | ||
platform: 'OS X 10.11' | ||
platform: 'OS X 10.11', | ||
version: '46' // TODO(philipwalton): 47 has issues, use 46 until fixed. | ||
}, | ||
@@ -230,0 +232,0 @@ { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
499281
62
4840
1
3
20
242
2
+ Addeddom-utils@^0.2.2
+ Addedobject-assign@^4.0.1
+ Addeddom-utils@0.2.2(transitive)
+ Addedobject-assign@4.1.1(transitive)
- Removeddelegate@^3.0.0
- Removeddelegate@3.2.0(transitive)