{"id":123355,"date":"2025-03-10T02:49:15","date_gmt":"2025-03-10T02:49:15","guid":{"rendered":"https:\/\/peraltafinancing.com\/analytics\/automated-tests-for-google-tag-managers-datalayer\/"},"modified":"2025-03-10T02:49:15","modified_gmt":"2025-03-10T02:49:15","slug":"automated-tests-for-google-tag-managers-datalayer","status":"publish","type":"post","link":"https:\/\/fivemor.com\/?p=123355","title":{"rendered":"Automated Tests For Google Tag Manager&#8217;s dataLayer"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div>\n<p>One of the biggest fears I have as a <a href=\"https:\/\/tagmanager.google.com\/\">Google Tag Manager<\/a> user is a broken release of the website (or app) on which I have deployed GTM. Far too often, lack of proper communication practices within an organization lead to a release being pushed out without thoroughly testing how this release impacts any existing tracking solutions.<\/p>\n<div style=\"aspect-ratio: 980 \/ 362;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/01\/dataLayer-undefined.jpg\" title=\"run automated tests against dataLayer\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"362\" width=\"980\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/01\/dataLayer-undefined.jpg#ZgotmplZ\" alt=\"run automated tests against dataLayer\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Luckily there are ways to mitigate this. The most significant and impactful precautions you can take are all about <a href=\"https:\/\/www.simoahava.com\/analytics\/from-project-to-process\/\">process<\/a>:<\/p>\n<ul>\n<li>\n<p>Be active in the daily development team work.<\/p>\n<\/li>\n<li>\n<p>Introduce \u201canalytics\u201d as an item in the <a href=\"https:\/\/www.agilealliance.org\/glossary\/definition-of-done\/\">definition of done<\/a> of each release.<\/p>\n<\/li>\n<li>\n<p>Educate the developers on what <a href=\"https:\/\/www.simoahava.com\/analytics\/data-layer\/\">Data Layer is<\/a> and how a proper GTM deployment hinges on a stable Data Layer.<\/p>\n<\/li>\n<\/ul>\n<p>In addition to these, there are technical measures you can employ to further eliminate the risk of things breaking down when a new release is pushed into the wild.<\/p>\n<div style=\"aspect-ratio: 1102 \/ 628;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/01\/datalayer-assert-example.jpg\" title=\"Data Layer assertion\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"628\" width=\"1102\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/01\/datalayer-assert-example.jpg#ZgotmplZ\" alt=\"Data Layer assertion\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>Unit tests<\/strong> should be used to test the code itself, verifying that a given input always produces an expected output. When testing <code>dataLayer<\/code>, this is especially important for any objects you write into the page template, as they are mostly code-driven and do not depend on user input.<\/p>\n<p>However, if you only ran unit tests it would be difficult to cover the variety of things that can happen in the web browser. That\u2019s where <strong>functional tests<\/strong> come into play, and those will be the focus of this article.<\/p>\n<p>With functional tests, you are actually running tests on a \u201creal\u201d implementation of the website. Typically this means having a version of the site running on localhost or a staging environment, and then using a \u201cvirtual\u201d browser, typically deployed via a framework such as <a href=\"http:\/\/www.seleniumhq.org\/\">Selenium<\/a>, to perform the actual test steps.<\/p>\n<p>To save you time and trouble, I have written an <strong>open-source<\/strong>, self-contained functional test suite for Google Tag Manager\u2019s <code>dataLayer<\/code> called, imaginatively, <strong>gtm-datalayer-test<\/strong>.<\/p>\n<div style=\"aspect-ratio: 2878 \/ 1548;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/01\/gtm-datalayer-test.jpg\" title=\"gtm-datalayer-test repo\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1548\" width=\"2878\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/01\/gtm-datalayer-test.jpg#ZgotmplZ\" alt=\"gtm-datalayer-test repo\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><a href=\"https:\/\/github.com\/sahava\/gtm-datalayer-test\/\">Visit the GitHub repo for gtm-datalayer-test<\/a>.<\/p>\n<p>The rest of this guide covers how the solution works.<\/p>\n<p>                <span class=\"simmer\"><br \/>\n  <span class=\"close\">X<\/span><\/p>\n<p>\n    <span class=\"fa fa-md fa-bell\"\/><br \/>\n    <strong>The Simmer Newsletter<\/strong>\n  <\/p>\n<p>\n    Subscribe to the <a href=\"https:\/\/www.simoahava.com\/newsletter\/\">Simmer newsletter<\/a> to get the latest news and content from Simo Ahava into your email inbox!\n  <\/p>\n<p>  <\/span><\/p>\n<h2 id=\"credit\">Credit<\/h2>\n<p>First credit where credit is due. The solution proposed in this article isn\u2019t ground-breaking in any way, shape or form. Using JSON to control processes is very common in the development world, and there are many test frameworks that directly support JSON validation for tests.<\/p>\n<p>A direct inspiration to this article is <a href=\"https:\/\/webanalyticsfordevelopers.com\/2017\/01\/03\/wanna-test-mac-edition\/\">Jan Exner<\/a>\u2019s work with a TDD setup for Adobe Analytics and DTM. You should take a look at the <a href=\"https:\/\/github.com\/janexner\/site-infrastructure-tests\">related GitHub repo<\/a>, where you\u2019ll find that it has many similarities to gtm-datalayer-test.<\/p>\n<p>As often happens, <a href=\"https:\/\/www.thyngster.com\/\">David Vallejo\u2019s<\/a> been an important brainstorming partner for building the test suite. I hope he will be a regular contributor to the project!<\/p>\n<h2 id=\"elevator-pitch\">Elevator pitch<\/h2>\n<p>The elevator pitch for this solution is this:<\/p>\n<blockquote>\n<p>gtm-datalayer-test is a functional testing solution for the global <code>window.dataLayer<\/code> queue used by Google Tag Manager. Tests are defined, executed, and reported using a special JSON configuration file, which validates against the latest draft of the <a href=\"http:\/\/json-schema.org\/\">JSON Schema<\/a> standard. The solution can be used to manually or automatically test if <code>window.dataLayer<\/code> has an expected structure. This is crucial especially when new releases of the website are pushed live.<\/p>\n<\/blockquote>\n<h2 id=\"1-get-it-up-and-running\">1. Get it up and running<\/h2>\n<p>First, let\u2019s get things up and running. Before you start, you will need the following things:<\/p>\n<ol>\n<li>\n<p>NodeJS and NPM installed (<a href=\"https:\/\/nodejs.org\/en\/download\/\">here<\/a>)<\/p>\n<\/li>\n<li>\n<p>Latest version of Java (<a href=\"https:\/\/www.java.com\/en\/download\/help\/index_installing.xml\">here<\/a>)<\/p>\n<\/li>\n<\/ol>\n<p>Once you have these installed, you should be able to run the following commands in your terminal and get a valid result for each:<\/p>\n<ul>\n<li>\n<p><code>java -version<\/code><\/p>\n<\/li>\n<li>\n<p><code>npm -v<\/code><\/p>\n<\/li>\n<li>\n<p><code>node -v<\/code><\/p>\n<\/li>\n<\/ul>\n<div style=\"aspect-ratio: 856 \/ 390;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/01\/commands.jpg\" title=\"Commands to run in terminal\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"390\" width=\"856\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/01\/commands.jpg#ZgotmplZ\" alt=\"Commands to run in terminal\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Once you\u2019ve installed these successfully, you can get <strong>gtm-datalayer-test<\/strong> up and running by executing the following commands. Run the first command (<code>git clone<\/code>) in a directory where you want the <strong>gtm-datalayer-test<\/strong> project directory to be established.<\/p>\n<ol>\n<li>\n<p><code>git clone https:\/\/github.com\/sahava\/gtm-datalayer-test.git<\/code><\/p>\n<\/li>\n<li>\n<p><code>cd gtm-datalayer-test<\/code><\/p>\n<\/li>\n<li>\n<p><code>npm install<\/code><\/p>\n<\/li>\n<li>\n<p><code>npm test<\/code><\/p>\n<\/li>\n<\/ol>\n<p>When running the final command, you should see something like this:<\/p>\n<div style=\"aspect-ratio: 1298 \/ 1130;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/01\/npm-test.jpg\" title=\"after the npm test command\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1130\" width=\"1298\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/01\/npm-test.jpg#ZgotmplZ\" alt=\"after the npm test command\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>With <code>git clone ...<\/code>, you pulled the source code from <a href=\"https:\/\/github.com\/sahava\/gtm-datalayer-test\/\">my GitHub repo<\/a> into a directory named <strong>gtm-datalayer-test<\/strong>, which is where you then navigated to in the second step.<\/p>\n<p>Next, by running <code>npm install<\/code>, <strong>npm<\/strong> (package manager for Node) runs through all the dependencies listed in the <strong>package.json<\/strong> file, and installs them locally to the project. This is very important, as without these dependencies the code itself is useless.<\/p>\n<p>Finally, by running <code>npm test<\/code>, you are executing an <em>npm script<\/em> specifically written for this project. It first starts a simple web server, after which it runs the automated <code>dataLayer<\/code> tests against a mock index.html file on that server. Finally, it outputs the results of the test into the console.<\/p>\n<p>The following chapters explore each of these steps in more detail.<\/p>\n<h2 id=\"2-technology-stack\">2. Technology stack<\/h2>\n<p>Node.js and npm give you a wealth of pre-built modules to work with when building your web application. I mean, you <em>could<\/em> write everything from scratch, but you\u2019ll end up spending more time, energy, and hair trying to do the stuff that existing npm packages already perform much more efficiently.<\/p>\n<p>This project uses the following technologies \/ dependencies \/ packages:<\/p>\n<p>You don\u2019t have to know what these modules do, but by looking at the list, one thing should jump out: I\u2019m gunning for a self-contained solution here. I\u2019m sacrificing customization for ease of use, but even in doing so the modularity of <strong>gtm-datalayer-test<\/strong> hasn\u2019t been compromised. All the packages above contribute to the framework I have built, but nothing\u2019s stopping you from extracting just some components and integrating them into your own test suite. In fact, I strongly recommend you do so if you already have a functional testing framework set up!<\/p>\n<p>As you might have gathered, the whole thing runs on JavaScript. There\u2019s not much you need to customize, but if you do wish to get your hands dirty, some knowledge of JavaScript, especially <a href=\"http:\/\/es6-features.org\/#Constants\">ECMAScript 6<\/a> and <a href=\"https:\/\/nodejs.org\/en\/\">Node.js<\/a>, is required.<\/p>\n<h2 id=\"3-how-it-works\">3. How it works<\/h2>\n<p>Each test setup comprises some moving parts. There\u2019s the <strong>wdio configuration file<\/strong> you feed to <strong>wdio<\/strong>, there\u2019s the <strong>JavaScript test spec<\/strong> itself, and then there\u2019s a <strong>configuration JSON<\/strong> you use to define your expectations for each test.<\/p>\n<h3 id=\"31-wdio-configuration-file\">3.1. wdio configuration file<\/h3>\n<p>This solution uses <a href=\"http:\/\/webdriver.io\/\">WebdriverIO<\/a> to manage much of the legwork. WebdriverIO offers a bunch of <em>bindings<\/em> we can use together with the virtual browsers operated by Selenium. Together with Selenium, WebdriverIO gives us all the tools we need to define, execute, and report on tests we want to run on any given application or web property.<\/p>\n<p>To make running WebdriverIO as smooth as possible, we use a module called <strong>wdio<\/strong> to run the tests. The great thing about <strong>wdio<\/strong> is that it\u2019s very extendable, and there are already lots of great modules and plugins we can use with it to make our setup purr.<\/p>\n<p>The <strong>wdio configuration JavaScript<\/strong> is basically what runs this whole show. The JavaScript exports a <code>config<\/code> object, which holds all the settings you want to define for the test. This includes what browser drivers you want to use, in which directories to look for spec files, what test runner to use, etc.<\/p>\n<p>You can find the example configuration, used when running <code>npm test<\/code>, in the <strong>.\/examples<\/strong> directory, with the name <strong>examples.conf.js<\/strong>. Here\u2019s what it looks like:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">const<\/span> enhancedEcommerceSchema = require(<span style=\"color:#a50\">'..\/lib\/enhancedEcommerceSchema.json'<\/span>)\nexports.config = {\n    specs: [\n        <span style=\"color:#a50\">'.\/examples\/spec\/basic_example.js'<\/span>\n    ],\n    maxInstances: <span style=\"color:#099\">10<\/span>,\n    capabilities: [{\n        maxInstances: <span style=\"color:#099\">5<\/span>,\n        browserName: <span style=\"color:#a50\">'phantomjs'<\/span>,\n        <span style=\"color:#a50\">'phantomjs.binary.path'<\/span>: <span style=\"color:#a50\">'.\/node_modules\/phantomjs-prebuilt\/bin\/phantomjs'<\/span>\n    }],\n    sync: <span style=\"color:#00a\">true<\/span>,\n    logLevel: <span style=\"color:#a50\">'silent'<\/span>,\n    coloredLogs: <span style=\"color:#00a\">true<\/span>,\n    bail: <span style=\"color:#099\">0<\/span>,\n    waitforTimeout: <span style=\"color:#099\">10000<\/span>,\n    connectionRetryTimeout: <span style=\"color:#099\">90000<\/span>,\n    connectionRetryCount: <span style=\"color:#099\">3<\/span>,\n    services: [<span style=\"color:#a50\">'selenium-standalone'<\/span>],\n    seleniumInstallArgs: {\n        version: <span style=\"color:#a50\">'3.0.1'<\/span>\n    },\n    seleniumArgs: {\n        version: <span style=\"color:#a50\">'3.0.1'<\/span>\n    },\n    framework: <span style=\"color:#a50\">'mocha'<\/span>,\n    reporters: [<span style=\"color:#a50\">'spec'<\/span>],\n    mochaOpts: {\n        ui: <span style=\"color:#a50\">'bdd'<\/span>\n    },\n    before: <span style=\"color:#00a\">function<\/span>() {\n        <span style=\"color:#00a\">const<\/span> chai = require(<span style=\"color:#a50\">'chai'<\/span>);\n        chai.use(require(<span style=\"color:#a50\">'chai-json-schema'<\/span>))\n        chai.use(require(<span style=\"color:#a50\">'chai-subset'<\/span>))\n        chai.tv4.addSchema(<span style=\"color:#a50\">'\/enhancedEcommerceSchema.json'<\/span>, enhancedEcommerceSchema)\n        global.expect = chai.expect\n        global.assert = chai.assert\n    }\n}\n<\/code><\/pre>\n<\/div>\n<p>First, be sure to check <a href=\"http:\/\/webdriver.io\/guide\/testrunner\/configurationfile.html\">here<\/a> for the full range of options you can set in the configuration file. It\u2019s a good idea to familiarize yourself with the available options, since you\u2019ll definitely need to modify some of them when extending these examples to your actual use cases.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">const<\/span> enhancedEcommerceSchema = require(<span style=\"color:#a50\">'..\/lib\/enhancedEcommerceSchema.json'<\/span>)\n<\/code><\/pre>\n<\/div>\n<p>This command loads a custom <a href=\"https:\/\/developers.google.com\/tag-manager\/enhanced-ecommerce\">Enhanced Ecommerce<\/a> JSON Schema which you can refer to when you want to test if your <code>dataLayer<\/code> has valid Enhanced Ecommerce objects within. There\u2019s more on this in a later chapter.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">specs: [\n    <span style=\"color:#a50\">'.\/examples\/spec\/basic_example.js'<\/span>\n]\n<\/code><\/pre>\n<\/div>\n<p>Use the <code>specs<\/code> keyword to list the locations with test files you want <strong>wdio<\/strong> to run through. Whenever you run <strong>wdio<\/strong> without the <code>--spec<\/code> command-line parameter, it will run through all the tests listed in this array.<\/p>\n<p>You can use wildcards, too. For example, all my tests are stored in various places within a directory named <strong>\/spec\/<\/strong>. To get <strong>wdio<\/strong> to run through all tests within, regardless of directory or file name, my <code>specs<\/code> configuration looks like this:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">specs: [\n    <span style=\"color:#a50\">'.\/spec\/**\/*.js'<\/span>\n]\n<\/code><\/pre>\n<\/div>\n<p>This means that in the director \/spec\/, look through all the files and directories for any items that have the <strong>.js<\/strong> extension, and use them as test specifications.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">capabilities: [{\n    browserName: <span style=\"color:#a50\">'phantomjs'<\/span>\n},{\n    browserName: <span style=\"color:#a50\">'chrome'<\/span>\n}]\n<\/code><\/pre>\n<\/div>\n<p>This is where you define the browser instances you want to fire with each test. Basically, any browser you list here will be launched when the test runner starts, and the tests will be run through each respective browser. Chrome, Firefox and PhantomJS have support out of the box in gtm-datalayer-test.<\/p>\n<p>By the way, <strong>PhantomJS<\/strong> is a very popular <em>headless browser<\/em>. Basically, it\u2019s a browser without a graphical user interface. It\u2019s thus a very lightweight, quick solution for running your tests on. However, with every major release you should probably run the tests through \u201creal\u201d browsers, too.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">framework: <span style=\"color:#a50\">'mocha'<\/span>,\nreporters: [<span style=\"color:#a50\">'spec'<\/span>]\n<\/code><\/pre>\n<\/div>\n<p>Here you define that you want to use <strong>Mocha<\/strong> as the test framework, and <strong>spec<\/strong> as the reporter. With <strong>wdio<\/strong>, you can also use <strong>Jasmine<\/strong> or <strong>Cucumber<\/strong> instead of Mocha. You also have a bunch of reporters to choose from, or you can build your own (quite simple, actually). For more details, see here for <a href=\"http:\/\/webdriver.io\/guide\/testrunner\/frameworks.html\">test frameworks<\/a> and here for <a href=\"http:\/\/webdriver.io\/guide\/reporters\/spec.html\">reporters<\/a>.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">before: <span style=\"color:#00a\">function<\/span>() {\n    <span style=\"color:#00a\">const<\/span> chai = require(<span style=\"color:#a50\">'chai'<\/span>);\n    chai.use(require(<span style=\"color:#a50\">'chai-json-schema'<\/span>))\n    chai.use(require(<span style=\"color:#a50\">'chai-subset'<\/span>))\n    chai.tv4.addSchema(<span style=\"color:#a50\">'\/enhancedEcommerceSchema.json'<\/span>, enhancedEcommerceSchema)\n    global.expect = chai.expect\n    global.assert = chai.assert\n}\n<\/code><\/pre>\n<\/div>\n<p>The <code>before<\/code> hook is executed before any test specs are run. In this hook, I basically tell <strong>wdio<\/strong> to use <strong>Chai<\/strong> as the <em>assertion<\/em> library of choice. <a href=\"http:\/\/chaijs.com\/\">Chai<\/a> is a very popular assertion library, and it\u2019s often used together with Mocha to provide a more expressive language for describing your tests. You can use any assertion library you want, or you can use the <a href=\"https:\/\/nodejs.org\/api\/assert.html\">default Node.js assertion library<\/a>, if you wish.<\/p>\n<p>Again, remember to read through the <a href=\"http:\/\/webdriver.io\/guide\/testrunner\/gettingstarted.html\"><strong>wdio<\/strong> documentation<\/a>. It\u2019s very important to understand what you can customize and how. I hope to add more examples to the GitHub repo in the future, but I still recommend you take a look at the documentation.<\/p>\n<h3 id=\"32-javascript-test-specification\">3.2. JavaScript test specification<\/h3>\n<p>Because this is a self-contained solution (or so I hope), you don\u2019t really need to touch the JavaScript test spec at all. The whole thing is orchestrated with the <strong>wdio<\/strong> configuration file and the JSON configuration file.<\/p>\n<p>Each JavaScript test specification executes its respective configuration JSON, running a bunch of tests against the <code>window.dataLayer<\/code> object on any page it visits.<\/p>\n<p>If you want to run multiple specifications, you will need to copy both the test specification JavaScript (e.g. <strong>basic_example.js<\/strong>) as well as the configuration JSON (e.g. <strong>basic_example.conf.json<\/strong>) to the spec directory. You can also have all your spec files in a single directory, if you wish. The <strong>wdio<\/strong> configuration file simply looks through the <strong>\/examples\/spec<\/strong> directory for any JavaScript test files.<\/p>\n<p>I\u2019m not going to walk you through the entire file, but there are some things you should know about.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#aaa;font-style:italic\">\/\/ Set the file in require() to point to this test's configuration JSON\n<\/span><span style=\"color:#aaa;font-style:italic\"\/><span style=\"color:#00a\">const<\/span> dataLayerConf = require(<span style=\"color:#a50\">'.\/basic_example.conf.json'<\/span>)\n<\/code><\/pre>\n<\/div>\n<p>If you create a new test specification, the JSON file name you point to in the <code>require()<\/code> method should refer to the configuration JSON you\u2019ve created for this test.<\/p>\n<p>The test specification runs through two main suites of tests:<\/p>\n<ol>\n<li>\n<p>\u201cGeneric\u201d tests for <code>window.dataLayer<\/code>, where certain keys should be found on every page the test visits.<\/p>\n<\/li>\n<li>\n<p>\u201cPage-specific\u201d configurations, where you test <code>window.dataLayer<\/code> on each given page for page-specific key-value pairs.<\/p>\n<\/li>\n<\/ol>\n<p>All the reports are generated automatically based on certain values in the configuration JSON. Before the test runs, however, the configuration JSON itself is validated against what I\u2019ve defined as a valid JSON Schema for this particular project (you can find this in <strong>\/lib\/validTestConfSchema.json<\/strong>). If anything is missing or incorrectly encoded, the test reporter will stop with an error.<\/p>\n<h3 id=\"33-json-configuration-file\">3.3. JSON configuration file<\/h3>\n<p>The JSON configuration is what the whole test hinges on. All the parameters you provide govern not only which pages the test loads in the virtual browser, but also what you expect to find in <code>window.dataLayer<\/code> on any given page.<\/p>\n<p>This is what a fully loaded JSON configuration might look like:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">{\n    <span style=\"color:#a50\">\"baseUrl\"<\/span> : <span style=\"color:#a50\">\"https:\/\/www.simoahava.com\"<\/span>,\n    <span style=\"color:#a50\">\"dataLayerName\"<\/span> : <span style=\"color:#a50\">\"dataLayer\"<\/span>,\n    <span style=\"color:#a50\">\"multipleContainers\"<\/span> : <span style=\"color:#00a\">true<\/span>,\n    <span style=\"color:#a50\">\"dataLayer\"<\/span> : [{\n        <span style=\"color:#a50\">\"@json\"<\/span> : <span style=\"color:#00a\">false<\/span>,\n        <span style=\"color:#a50\">\"visitorLoginState\"<\/span> : <span style=\"color:#a50\">\"logged-out\"<\/span>\n    },{\n        <span style=\"color:#a50\">\"event\"<\/span> : {\n            <span style=\"color:#a50\">\"pattern\"<\/span> : <span style=\"color:#a50\">\"^gtm.dom$\"<\/span>\n        }\n    },{\n        <span style=\"color:#a50\">\"event\"<\/span> : {\n            <span style=\"color:#a50\">\"pattern\"<\/span> : <span style=\"color:#a50\">\"^gtm.load$\"<\/span>\n        }\n    }],\n    <span style=\"color:#a50\">\"page\"<\/span> : [{\n        <span style=\"color:#a50\">\"path\"<\/span> : <span style=\"color:#a50\">\"\/gtm-tips\/10-useful-css-selectors\/\"<\/span>,\n        <span style=\"color:#a50\">\"dataLayer\"<\/span> : [{\n            <span style=\"color:#a50\">\"@expect\"<\/span> : <span style=\"color:#a50\">\"dataLayer to have basic article variables\"<\/span>,\n            <span style=\"color:#a50\">\"pageAttributes\"<\/span> : {\n                <span style=\"color:#a50\">\"enum\"<\/span> : [[<span style=\"color:#a50\">\"css-selectors\"<\/span>, <span style=\"color:#a50\">\"google-tag-manager\"<\/span>, <span style=\"color:#a50\">\"gtmtips\"<\/span>]]\n            },\n            <span style=\"color:#a50\">\"pageCategory\"<\/span> : {\n                <span style=\"color:#a50\">\"enum\"<\/span>: [[<span style=\"color:#a50\">\"gtm-tips\"<\/span>]]\n            },\n            <span style=\"color:#a50\">\"pagePostType\"<\/span> : {\n                <span style=\"color:#a50\">\"pattern\"<\/span> : <span style=\"color:#a50\">\"^post$\"<\/span>\n            },\n            <span style=\"color:#a50\">\"pagePostType2\"<\/span> : {\n                <span style=\"color:#a50\">\"pattern\"<\/span>: <span style=\"color:#a50\">\"^single-post$\"<\/span>\n            },\n            <span style=\"color:#a50\">\"postCountOnPage\"<\/span> : {\n                <span style=\"color:#a50\">\"type\"<\/span> : <span style=\"color:#a50\">\"number\"<\/span>\n            },\n            <span style=\"color:#a50\">\"postCountTotal\"<\/span> : {\n                <span style=\"color:#a50\">\"type\"<\/span> : <span style=\"color:#a50\">\"number\"<\/span>\n            }\n        },{\n            <span style=\"color:#a50\">\"@expect\"<\/span> : <span style=\"color:#a50\">\"dataLayer to have article impressions\"<\/span>,\n            <span style=\"color:#a50\">\"event\"<\/span> : {\n                <span style=\"color:#a50\">\"pattern\"<\/span>: <span style=\"color:#a50\">\"^impressionsPushed$\"<\/span>\n            },\n            <span style=\"color:#a50\">\"ecommerce\"<\/span> : {\n                <span style=\"color:#a50\">\"type\"<\/span> : <span style=\"color:#a50\">\"object\"<\/span>,\n                <span style=\"color:#a50\">\"properties\"<\/span> : {\n                    <span style=\"color:#a50\">\"impressions\"<\/span>: {\n                        <span style=\"color:#a50\">\"$ref\"<\/span> : <span style=\"color:#a50\">\"\/enhancedEcommerceSchema.json#\/definitions\/impressions\"<\/span>\n                    }\n                },\n                <span style=\"color:#a50\">\"required\"<\/span> : [<span style=\"color:#a50\">\"impressions\"<\/span>]\n            }\n        },{\n            <span style=\"color:#a50\">\"@expect\"<\/span> : <span style=\"color:#a50\">\"dataLayer to have clientId\"<\/span>,\n            <span style=\"color:#a50\">\"event\"<\/span> : {\n                <span style=\"color:#a50\">\"pattern\"<\/span> : <span style=\"color:#a50\">\"^trackerReady$\"<\/span>\n            },\n            <span style=\"color:#a50\">\"cid\"<\/span> : {\n                <span style=\"color:#a50\">\"type\"<\/span> : <span style=\"color:#a50\">\"string\"<\/span>,\n                <span style=\"color:#a50\">\"pattern\"<\/span> : <span style=\"color:#a50\">\"[0-9]+\\\\.[0-9]+\"<\/span>\n            }\n        },{\n            <span style=\"color:#a50\">\"@expect\"<\/span> : <span style=\"color:#a50\">\"dataLayer to have product detail view\"<\/span>,\n            <span style=\"color:#a50\">\"ecommerce\"<\/span> : {\n                <span style=\"color:#a50\">\"type\"<\/span> : <span style=\"color:#a50\">\"object\"<\/span>,\n                <span style=\"color:#a50\">\"properties\"<\/span> : {\n                    <span style=\"color:#a50\">\"detail\"<\/span> : {\n                        <span style=\"color:#a50\">\"$ref\"<\/span> : <span style=\"color:#a50\">\"\/enhancedEcommerceSchema.json#\/definitions\/detail\"<\/span>\n                    }\n                },\n                <span style=\"color:#a50\">\"required\"<\/span> : [<span style=\"color:#a50\">\"detail\"<\/span>]\n            }\n        }]\n    }]\n}\n<\/code><\/pre>\n<\/div>\n<p>Here are the main keys you can work with:<\/p>\n<ul>\n<li>\n<p><strong>baseUrl<\/strong> (required) &#8211; a string containing the full protocol, domain, and port of the site where all the tests are run in this specification. Note! Leave the trailing slash out (<code>https:\/\/www.simoahava.com<\/code>, NOT <code>https:\/\/www.simoahava.com\/<\/code>).<\/p>\n<\/li>\n<li>\n<p><strong>dataLayerName<\/strong> &#8211; if you\u2019re using some other name for the global <code>dataLayer<\/code> object than \u201cdataLayer\u201d, remember to specify it here. You can leave this out otherwise (the test defaults to <code>dataLayer<\/code>).<\/p>\n<\/li>\n<li>\n<p><strong>multipleContainers<\/strong> &#8211; set this to <code>true<\/code> if you have multiple containers on the page. Otherwise leave this out.<\/p>\n<\/li>\n<\/ul>\n<p>In addition to this, there\u2019s <strong>dataLayer<\/strong>, which is an array of objects you expect to find on every single page the test visits (generic configuration).<\/p>\n<p>There\u2019s also <strong>page<\/strong>, which is where you specify each page you want the test to visit, with any page-specific dataLayer configurations defined within.<\/p>\n<p>There\u2019s more on the configuration file in the following chapter.<\/p>\n<h2 id=\"4-json-configuration\">4. JSON configuration<\/h2>\n<p>Picking up where we left off in the previous chapter, let\u2019s focus on the two important configuration arrays we haven\u2019t covered yet: <code>dataLayer[]<\/code> and <code>page[]<\/code>.<\/p>\n<h3 id=\"41-generic-datalayer\">4.1. Generic dataLayer<\/h3>\n<p>The <strong>dataLayer<\/strong> key in the root of the JSON configuration is where you list objects you expect to find on every single page the test visits. These are so called \u201cgeneric\u201d keys. So, let\u2019s say you expect to find an object that contains the key-value pair <code>\"event\" : \"gtm.dom\"<\/code>, as well as an optional object where they key is <code>\"visitorLoginState\"<\/code> whose value is any string, this is what the generic object might look like:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-json\" data-lang=\"json\">{\n    <span style=\"color:#f00;background-color:#faa\">...<\/span>\n    <span style=\"color:#f00;background-color:#faa\">dataLayer<\/span> <span style=\"color:#f00;background-color:#faa\">:<\/span> <span style=\"color:#f00;background-color:#faa\">[{<\/span>\n        <span style=\"color:#1e90ff;font-weight:bold\">\"@json\"<\/span> : <span style=\"color:#00a\">false<\/span>,\n        <span style=\"color:#1e90ff;font-weight:bold\">\"event\"<\/span> : <span style=\"color:#a50\">\"gtm.dom\"<\/span>\n    }<span style=\"color:#f00;background-color:#faa\">,<\/span>{\n        <span style=\"color:#1e90ff;font-weight:bold\">\"visitorLoginState\"<\/span> : {\n            <span style=\"color:#1e90ff;font-weight:bold\">\"@rootRequired\"<\/span> : <span style=\"color:#00a\">false<\/span>,\n            <span style=\"color:#1e90ff;font-weight:bold\">\"type\"<\/span> : <span style=\"color:#a50\">\"string\"<\/span>\n        }\n    }<span style=\"color:#f00;background-color:#faa\">]<\/span>\n    <span style=\"color:#f00;background-color:#faa\">...<\/span>\n<span style=\"color:#f00;background-color:#faa\">}<\/span><\/code><\/pre>\n<\/div>\n<p>Let\u2019s see what these keys mean.<\/p>\n<p><strong>@json<\/strong> is a special configuration that can only have the <code>false<\/code> value. Any other value and the schema will not validate. If a <strong>dataLayer<\/strong> object has this key in the configuration, it means that any key-value pairs within that object will be looked for <em>verbatim<\/em> within the global <code>window.dataLayer<\/code> object. In other words, if <code>window.dataLayer<\/code> doesn\u2019t have at least one object with the <strong>exact<\/strong> key-value pair <code>\"event\" : \"gtm.dom\"<\/code>, the test will fail.<\/p>\n<p>You can thus use the <code>\"@json\" : false<\/code> setting to run simple <strong>subset checks<\/strong>. You can define complex objects, arrays, or any available data types, but they need to be found in that exact format within <code>window.dataLayer<\/code>. This <strong>subset check<\/strong> is a great way to test key-value pairs which you expect to be immutable across releases.<\/p>\n<p>If you <em>don\u2019t<\/em> provide the <strong>@json<\/strong> key, the test will be validated against <a href=\"http:\/\/json-schema.org\/\">JSON Schema<\/a> logic. JSON Schema is a (draft) standard used to describe JSON documents. The <code>window.dataLayer<\/code> object can be stringified into an imperfect JSON representation, though it\u2019s typically good enough to run JSON validation tests against.<\/p>\n<p>JSON Schema gives you a lot of tools to work with when describing the complexity of <code>window.dataLayer<\/code>. You can create tests where you expect keys to have a certain range of values, a certain data type, optional or required parameters, et cetera.<\/p>\n<p>In the example above, <code>\"visitorLoginState\"<\/code> is simply defined as an optional string. You don\u2019t require <code>window.dataLayer<\/code> to have it, but if it is found, you expect it to be a string. Thus the test would fail if <code>window.dataLayer<\/code> had an object with <code>\"visitorLoginState\" : false<\/code>.<\/p>\n<p>The special <strong>@rootRequired<\/strong> key is used only for keys in the root of the <strong>dataLayer<\/strong> object. If you add this key with value <code>false<\/code>, it means that the parameter it\u2019s attached to is <strong>optional<\/strong>. You don\u2019t require <code>window.dataLayer<\/code> to have that key in the root of any object. The default is that each key you define in the root of the <strong>dataLayer<\/strong> object is required, so using <code>\"@rootRequired\" : false<\/code> is the only way to impact this. For keys deeper in the structure you can use regular JSON Schema syntax (e.g. <code>\"required\" : [\"someProperty\", \"someOtherProperty\"]<\/code>).<\/p>\n<h3 id=\"42-page-specific-configurations\">4.2. Page-specific configurations<\/h3>\n<p>The page-specific configurations contain one extra configuration level, after which you define their own <strong>dataLayer<\/strong> objects using the methods you read about in the previous chapter.<\/p>\n<p>The first page-specific key you need to define is <strong>path<\/strong>. This should be in the root of each object in the <strong>page<\/strong> array. The value should be a proper URL path, starting with \u201c\/\u201d. For example, to visit three different pages in the test, you would configure the <strong>page<\/strong> object like this:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-json\" data-lang=\"json\">{\n    <span style=\"color:#f00;background-color:#faa\">...<\/span>\n    <span style=\"color:#1e90ff;font-weight:bold\">\"dataLayer\"<\/span> : [{\n        <span style=\"color:#f00;background-color:#faa\">....<\/span>\n    }],\n    <span style=\"color:#1e90ff;font-weight:bold\">\"page\"<\/span> : [{\n        <span style=\"color:#1e90ff;font-weight:bold\">\"path\"<\/span> : <span style=\"color:#a50\">\"\/first-page-to-test\/\"<\/span>,\n        <span style=\"color:#1e90ff;font-weight:bold\">\"dataLayer\"<\/span> : [{\n            <span style=\"color:#f00;background-color:#faa\">...<\/span>\n        }]\n    },{\n        <span style=\"color:#1e90ff;font-weight:bold\">\"path\"<\/span> : <span style=\"color:#a50\">\"\/second-page-to-test\/\"<\/span>,\n        <span style=\"color:#1e90ff;font-weight:bold\">\"dataLayer\"<\/span> : [{\n            <span style=\"color:#f00;background-color:#faa\">...<\/span>\n        }]\n    },{\n        <span style=\"color:#1e90ff;font-weight:bold\">\"path\"<\/span> : <span style=\"color:#a50\">\"\/third-page-to-test\/\"<\/span>,\n        <span style=\"color:#1e90ff;font-weight:bold\">\"dataLayer\"<\/span> : [{\n            <span style=\"color:#f00;background-color:#faa\">...<\/span>\n        }]\n    }\n<span style=\"color:#f00;background-color:#faa\">}<\/span><\/code><\/pre>\n<\/div>\n<p>For each given <strong>page<\/strong> object, you can define the <strong>dataLayer<\/strong> composition you expect to find on that particular page. The configuration of each <strong>dataLayer<\/strong> object is exactly the same as explored in the previous chapter with one important addition.<\/p>\n<p>In the root of each <strong>dataLayer<\/strong> object, you need to define the key <strong>@expect<\/strong> with a textual description of this test. In other words, use this key to describe <em>why<\/em> you are expecting this particular <strong>dataLayer<\/strong> object to be found in the global <code>window.dataLayer<\/code>.<\/p>\n<p>Whatever you type in this string will be prefixed with the verb \u201cexpect\u201d, so you can thus use a partial sentence. This is what your configuration might look like:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-json\" data-lang=\"json\">{\n    <span style=\"color:#f00;background-color:#faa\">...<\/span>\n    <span style=\"color:#1e90ff;font-weight:bold\">\"dataLayer\"<\/span> : [{\n        <span style=\"color:#f00;background-color:#faa\">....<\/span>\n    }],\n    <span style=\"color:#1e90ff;font-weight:bold\">\"page\"<\/span> : [{\n        <span style=\"color:#1e90ff;font-weight:bold\">\"path\"<\/span> : <span style=\"color:#a50\">\"\/first-page-to-test\/\"<\/span>,\n        <span style=\"color:#1e90ff;font-weight:bold\">\"dataLayer\"<\/span> : [{\n            <span style=\"color:#1e90ff;font-weight:bold\">\"@expect\"<\/span> : <span style=\"color:#a50\">\"dataLayer to have the userState key\"<\/span>,\n            <span style=\"color:#1e90ff;font-weight:bold\">\"userState\"<\/span> : {\n                <span style=\"color:#1e90ff;font-weight:bold\">\"type\"<\/span> : <span style=\"color:#a50\">\"string\"<\/span>\n            }\n        }]\n    },{\n        <span style=\"color:#1e90ff;font-weight:bold\">\"path\"<\/span> : <span style=\"color:#a50\">\"\/second-page-to-test\/\"<\/span>,\n        <span style=\"color:#1e90ff;font-weight:bold\">\"dataLayer\"<\/span> : [{\n            <span style=\"color:#1e90ff;font-weight:bold\">\"@expect\"<\/span> : <span style=\"color:#a50\">\"dataLayer to optionally contain userCount\"<\/span>,\n            <span style=\"color:#1e90ff;font-weight:bold\">\"userCount\"<\/span> : {\n                <span style=\"color:#1e90ff;font-weight:bold\">\"type\"<\/span> : <span style=\"color:#a50\">\"number\"<\/span>,\n                <span style=\"color:#1e90ff;font-weight:bold\">\"@rootRequired\"<\/span> : <span style=\"color:#00a\">false<\/span>\n            }\n        }]\n    },{\n        <span style=\"color:#1e90ff;font-weight:bold\">\"path\"<\/span> : <span style=\"color:#a50\">\"\/third-page-to-test\/\"<\/span>,\n        <span style=\"color:#1e90ff;font-weight:bold\">\"dataLayer\"<\/span> : [{\n            <span style=\"color:#1e90ff;font-weight:bold\">\"@expect\"<\/span> : <span style=\"color:#a50\">\"dataLayer to have the Enhanced Ecommerce detail object\"<\/span>,\n            <span style=\"color:#1e90ff;font-weight:bold\">\"ecommerce\"<\/span> : {\n                <span style=\"color:#1e90ff;font-weight:bold\">\"type\"<\/span> : <span style=\"color:#a50\">\"object\"<\/span>,\n                <span style=\"color:#1e90ff;font-weight:bold\">\"properties\"<\/span> : {\n                    <span style=\"color:#1e90ff;font-weight:bold\">\"detail\"<\/span> : {\n                        <span style=\"color:#1e90ff;font-weight:bold\">\"$ref\"<\/span> : <span style=\"color:#a50\">\"\/enhancedEcommerceSchema.json#\/definitions\/detail\"<\/span>\n                    }\n                },\n                <span style=\"color:#1e90ff;font-weight:bold\">\"required\"<\/span> : [<span style=\"color:#a50\">\"detail\"<\/span>]\n        }<span style=\"color:#f00;background-color:#faa\">]<\/span>\n    }\n<span style=\"color:#f00;background-color:#faa\">}<\/span><\/code><\/pre>\n<\/div>\n<p>See the last object? Interesting, eh? First, you expect the global <code>window.dataLayer<\/code> structure to contain an object named <strong>ecommerce<\/strong> with one required property: <strong>detail<\/strong>. Next, you\u2019re using the <strong>$ref<\/strong> JSON Schema keyword to link to an external definition. In fact, in the directory <strong>\/lib\/<\/strong> you can find the schema named <strong>enhancedEcommerceSchema.json<\/strong>, which I created to make it easier to describe a valid Enhanced Ecommerce object.<\/p>\n<p>You can link to the definitions within from your test configuration JSON files. Hopefully we\u2019ll have more schemas for other complex objects soon, and nothing\u2019s stopping you from creating and linking your own custom JSON for schemas you expect to use over and over again!<\/p>\n<h2 id=\"5-running-your-tests\">5. Running your tests<\/h2>\n<p>If you\u2019ve been modifying the <strong>basic_example.conf.json<\/strong>, you can use the predefined npm script <code>npm test<\/code> to run your setup. When you execute that command, the following things happen:<\/p>\n<ol>\n<li>\n<p>The script fires up an <strong>http-server<\/strong> instance on http:\/\/localhost:8080, which loads the <strong>index.html<\/strong> file from .\/examples\/.<\/p>\n<\/li>\n<li>\n<p>The script runs <code>.\/node_modules\/.bin\/wdio .\/examples\/examples.conf.js<\/code><\/p>\n<\/li>\n<li>\n<p>The test runner automatically starts an instance of the Selenium server, which then proceeds to fire up a <strong>phantomjs<\/strong> browser driver.<\/p>\n<\/li>\n<li>\n<p>The test runner starts reading your <strong>basic_example.js<\/strong> test specification, and opens the test browser on URL http:\/\/localhost:8080\/index.html.<\/p>\n<\/li>\n<li>\n<p>The test runner proceeds to run through all the tests on this URL, keeping tabs on which tests passed and which failed.<\/p>\n<\/li>\n<li>\n<p>The test runner then reports the results of the test, using green checkmarks for passed tests and red numbers for failed tests.<\/p>\n<\/li>\n<li>\n<p>If any tests failed, they are reported as AssertionErrors with each error associated with its respective test number.<\/p>\n<\/li>\n<li>\n<p>Finally, <strong>wdio<\/strong> automatically shuts down the Selenium instance, and <strong>http-server<\/strong> is killed, too.<\/p>\n<\/li>\n<\/ol>\n<div style=\"aspect-ratio: 1990 \/ 1244;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2017\/01\/failed-npm-test.jpg\" title=\"Failed npm-test\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1244\" width=\"1990\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2017\/01\/failed-npm-test.jpg#ZgotmplZ\" alt=\"Failed npm-test\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Once you\u2019ve graduated beyond the <strong>basic_example.js<\/strong> file, you might want to create your own tests, store them in their own folders, and create new <strong>wdio<\/strong> configuration files for each.<\/p>\n<p>The command for running <strong>wdio<\/strong> against your own <strong>wdio configuration files<\/strong> is this (executed in the root of your project):<\/p>\n<p><code>.\/node_modules\/.bin\/wdio <wdio configuration=\"\" file=\"\"\/><\/code><\/p>\n<p>This runs the <strong>wdio<\/strong> executable against your configuration file, which in turn passes each specification defined in the configuration for the test runner. The test runner, then, starts the browser instances you have selected, running the tests, and channeling the output to the test reporter.<\/p>\n<p>You might want to start using a task \/ workflow runner such as <a href=\"http:\/\/gruntjs.com\/\">Grunt<\/a> or <a href=\"http:\/\/gulpjs.com\/\">Gulp<\/a> to manage your test suites. It will make it easier to run arbitrary tests, as you can specify things like custom parameters, and you can chain multiple tests together, if you wish.<\/p>\n<h2 id=\"6-contribute\">6. Contribute<\/h2>\n<p>This is an <strong>open-source<\/strong> project. I don\u2019t expect you to contribute, but if you do I am very grateful indeed.<\/p>\n<p>To contribute, head on over to the <a href=\"https:\/\/github.com\/sahava\/gtm-datalayer-test\/\">GitHub repo<\/a>. You can fork the repo, make modifications, and then submit those modifications as Pull Requests. It would be best, however, if you first introduce the thing you want to change as an Issue to make sure we all agree that it\u2019s a good feature to focus on.<\/p>\n<p>Alternatively, you can add your ideas, questions, or bugs in the comments below. I\u2019m fully aware this isn\u2019t a \u201cplug-and-play\u201d solution for testing GTM, since I never intended it to be one. Thus I expect there to be issues that I haven\u2019t considered (I should write tests for this, too!), and I would be very grateful if you\u2019d let me know about any trouble you\u2019ve come across with this solution.<\/p>\n<p>Finally, the latest version of this solution AND the documentation is only maintained in <a href=\"https:\/\/github.com\/sahava\/gtm-datalayer-test\/\">the GitHub repo<\/a>. It\u2019s possible that this article is already outdated as you\u2019re reading this, so I hope you head on over to the repo to see the latest changes.<\/p>\n<\/p><\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>One of the biggest fears I have as a Google Tag Manager user is a broken release of the website (or app) on which I have deployed GTM. Far too often, lack of proper communication practices within an organization lead to a release being pushed out without thoroughly testing how this release impacts any existing [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":123356,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12033],"tags":[16382,38540,1531,11419,1791,11326],"dealstore":[],"offerexpiration":[],"class_list":["post-123355","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-analytics","tag-automated","tag-datalayer","tag-google","tag-managers","tag-tag","tag-tests"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Automated Tests For Google Tag Manager&#039;s dataLayer - Som2ny Network<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/fivemor.com\/?p=123355\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Automated Tests For Google Tag Manager&#039;s dataLayer - Som2ny Network\" \/>\n<meta property=\"og:description\" content=\"One of the biggest fears I have as a Google Tag Manager user is a broken release of the website (or app) on which I have deployed GTM. Far too often, lack of proper communication practices within an organization lead to a release being pushed out without thoroughly testing how this release impacts any existing [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fivemor.com\/?p=123355\" \/>\n<meta property=\"og:site_name\" content=\"Som2ny Network\" \/>\n<meta property=\"article:published_time\" content=\"2025-03-10T02:49:15+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/dataLayer-undefined.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"980\" \/>\n\t<meta property=\"og:image:height\" content=\"362\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"19 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/fivemor.com\/?p=123355#article\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/?p=123355\"},\"author\":{\"name\":\"admin\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\"},\"headline\":\"Automated Tests For Google Tag Manager&#8217;s dataLayer\",\"datePublished\":\"2025-03-10T02:49:15+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=123355\"},\"wordCount\":3414,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=123355#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/dataLayer-undefined.jpg\",\"keywords\":[\"Automated\",\"dataLayer\",\"Google\",\"Managers\",\"TAG\",\"tests\"],\"articleSection\":[\"Analytics\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/fivemor.com\/?p=123355#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/fivemor.com\/?p=123355\",\"url\":\"https:\/\/fivemor.com\/?p=123355\",\"name\":\"Automated Tests For Google Tag Manager's dataLayer - Som2ny Network\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=123355#primaryimage\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=123355#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/dataLayer-undefined.jpg\",\"datePublished\":\"2025-03-10T02:49:15+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/fivemor.com\/?p=123355#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/fivemor.com\/?p=123355\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/?p=123355#primaryimage\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/dataLayer-undefined.jpg\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/dataLayer-undefined.jpg\",\"width\":980,\"height\":362},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/fivemor.com\/?p=123355#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/fivemor.com\/?bp_activities=1\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Automated Tests For Google Tag Manager&#8217;s dataLayer\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/fivemor.com\/#website\",\"url\":\"https:\/\/fivemor.com\/\",\"name\":\"Som2ny Network\",\"description\":\"Daily Deals\",\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/fivemor.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/fivemor.com\/#organization\",\"name\":\"Som2ny Network\",\"url\":\"https:\/\/fivemor.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"width\":300,\"height\":86,\"caption\":\"Som2ny Network\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"caption\":\"admin\"},\"sameAs\":[\"https:\/\/fivemor.com\"],\"url\":\"https:\/\/fivemor.com\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Automated Tests For Google Tag Manager's dataLayer - Som2ny Network","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/fivemor.com\/?p=123355","og_locale":"en_US","og_type":"article","og_title":"Automated Tests For Google Tag Manager's dataLayer - Som2ny Network","og_description":"One of the biggest fears I have as a Google Tag Manager user is a broken release of the website (or app) on which I have deployed GTM. Far too often, lack of proper communication practices within an organization lead to a release being pushed out without thoroughly testing how this release impacts any existing [&hellip;]","og_url":"https:\/\/fivemor.com\/?p=123355","og_site_name":"Som2ny Network","article_published_time":"2025-03-10T02:49:15+00:00","og_image":[{"width":980,"height":362,"url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/dataLayer-undefined.jpg","type":"image\/jpeg"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"admin","Est. reading time":"19 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/fivemor.com\/?p=123355#article","isPartOf":{"@id":"https:\/\/fivemor.com\/?p=123355"},"author":{"name":"admin","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371"},"headline":"Automated Tests For Google Tag Manager&#8217;s dataLayer","datePublished":"2025-03-10T02:49:15+00:00","mainEntityOfPage":{"@id":"https:\/\/fivemor.com\/?p=123355"},"wordCount":3414,"commentCount":0,"publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"image":{"@id":"https:\/\/fivemor.com\/?p=123355#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/dataLayer-undefined.jpg","keywords":["Automated","dataLayer","Google","Managers","TAG","tests"],"articleSection":["Analytics"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/fivemor.com\/?p=123355#respond"]}]},{"@type":"WebPage","@id":"https:\/\/fivemor.com\/?p=123355","url":"https:\/\/fivemor.com\/?p=123355","name":"Automated Tests For Google Tag Manager's dataLayer - Som2ny Network","isPartOf":{"@id":"https:\/\/fivemor.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/fivemor.com\/?p=123355#primaryimage"},"image":{"@id":"https:\/\/fivemor.com\/?p=123355#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/dataLayer-undefined.jpg","datePublished":"2025-03-10T02:49:15+00:00","breadcrumb":{"@id":"https:\/\/fivemor.com\/?p=123355#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fivemor.com\/?p=123355"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/?p=123355#primaryimage","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/dataLayer-undefined.jpg","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/03\/dataLayer-undefined.jpg","width":980,"height":362},{"@type":"BreadcrumbList","@id":"https:\/\/fivemor.com\/?p=123355#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/fivemor.com\/?bp_activities=1"},{"@type":"ListItem","position":2,"name":"Automated Tests For Google Tag Manager&#8217;s dataLayer"}]},{"@type":"WebSite","@id":"https:\/\/fivemor.com\/#website","url":"https:\/\/fivemor.com\/","name":"Som2ny Network","description":"Daily Deals","publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/fivemor.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/fivemor.com\/#organization","name":"Som2ny Network","url":"https:\/\/fivemor.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","width":300,"height":86,"caption":"Som2ny Network"},"image":{"@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371","name":"admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","caption":"admin"},"sameAs":["https:\/\/fivemor.com"],"url":"https:\/\/fivemor.com\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/123355","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=123355"}],"version-history":[{"count":0,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/123355\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/media\/123356"}],"wp:attachment":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=123355"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=123355"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=123355"},{"taxonomy":"dealstore","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fdealstore&post=123355"},{"taxonomy":"offerexpiration","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fofferexpiration&post=123355"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}