{"id":88925,"date":"2025-02-15T03:27:46","date_gmt":"2025-02-15T03:27:46","guid":{"rendered":"https:\/\/peraltafinancing.com\/analytics\/simple-way-to-measure-a-b-test-flicker-impact\/"},"modified":"2025-02-15T03:27:46","modified_gmt":"2025-02-15T03:27:46","slug":"simple-way-to-measure-a-b-test-flicker-impact","status":"publish","type":"post","link":"https:\/\/fivemor.com\/?p=88925","title":{"rendered":"Simple Way To Measure A\/B Test Flicker Impact"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div>\n<p>\u201cFlickering\u201d or \u201cFlash Of Original Content\u201d (<a href=\"https:\/\/reflectivedata.com\/what-is-fooc-flicker-how-to-get-rid-of-it\/\">FOOC<\/a>) is a phenomenon where there\u2019s a (typically) slight but observable delay in the browser updating the site or element layout if the user is included in a variant group for experimentation. This manifests in the original, unmodified element being rendered in the visible portion of the page before the experiment library updates it with the variant.<\/p>\n<p>There are ways to mitigate the flicker:<\/p>\n<ol>\n<li>Add the A\/B testing library directly into the page template and don\u2019t load it via some other, asynchronously loaded dependency (e.g. <a href=\"https:\/\/tagmanager.google.com\/\">Google Tag Manager<\/a>).<\/li>\n<li>Load the A\/B testing library synchronously, and have it hide the element that is being tested until the library is loaded.<\/li>\n<li>Utilize some kind of <a href=\"https:\/\/support.google.com\/optimize\/answer\/7100284?hl=en\">anti-flicker tech<\/a>.<\/li>\n<li>Run the experiments server-side, and render content with the variant in place.<\/li>\n<\/ol>\n<div style=\"aspect-ratio: 2584 \/ 1244;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/flicker-test-hero.jpg\" title=\"Flicker test\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1244\" width=\"2584\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/flicker-test-hero.jpg#ZgotmplZ\" alt=\"Flicker test\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Typically, the only non-intrusive and consistent way to avoid the flicker is to look into server-side rendering for your experiments. For example, tools like <a href=\"https:\/\/console.conductrics.com\/docs\/demo\/api-reference\">Conductrics<\/a> offer a robust set of APIs to do all the decision-making logic in your server. Then there are tools like <a href=\"https:\/\/developers.google.com\/optimize\/devguides\/experiments\">Google Optimize<\/a> that require you to do the variant selection and assignment manually, but the tool can then handle the data collection and reporting.<\/p>\n<p>However, the reason you\u2019ve read thus far is probably because you\u2019re worried about <strong>client-side testing<\/strong>.<\/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=\"introducing-the-problem\">Introducing the problem<\/h2>\n<p>With JavaScript-based experimentation libraries, you\u2019re subject to the rules and limitations of the page render in the browser. The <strong>flicker<\/strong> happens because the page with the modified element is being rendered from the page HTML source, but the experimentation library needs to wait for an opening to allow the browser to process the experiment data.<\/p>\n<p>This is most often a problem when you\u2019re running scripts <strong>asynchronously<\/strong>. Async load means that once the browser starts to download the library, it doesn\u2019t wait for the download to complete. Instead, it proceeds with the page render. Once the download is complete, and as soon as the browser has an available slot in its single thread of execution, it will start parsing and executing the JavaScript within the library.<\/p>\n<p>By moving from asynchronous to synchronous loading, you solve part of this issue. However, it\u2019s not like synchronous loading actually fixes anything automatically. Since the library is loaded at the top of <code\/>, a synchronously loaded library doesn\u2019t have access to the elements it\u2019s designed to modify (since those elements are created in the <code\/>, which hasn\u2019t yet been generated).<\/p>\n<p>Instead, libraries like Google Optimize, when loaded synchronously, actually <strong>hide<\/strong> the element that\u2019s being tested. They inject a <strong>style declaration<\/strong> that sets the visibility of all elements matching the CSS selector of the experimentation targets to <code>hidden<\/code>. Only once the element has been actually added to the page, can Optimize then modify it and unhide it. This is fairly elegant but it might introduce a slight flicker of another kind, where the element seems to \u201cpop\u201d into place out of sequence with the rest of the render.<\/p>\n<div style=\"aspect-ratio: 1520 \/ 114;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/optimize-hides.jpg\" title=\"optimize-hides.jpg\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"114\" width=\"1520\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/optimize-hides.jpg#ZgotmplZ\" alt=\"optimize-hides.jpg\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>A similar solution is <strong>anti-flicker<\/strong> JavaScript. The purpose here is to actually <strong>hide the entire page<\/strong> until the experimentation library has loaded. This is, and has been, my biggest objection about how A\/B-testing tools are implemented. I simply can\u2019t fathom the logic behind potentially sacrificing the usability of the entire page just to get better data quality for your experimentation.<\/p>\n<blockquote>\n<p>Considering how crucial page performance and <em>perceived<\/em> page performance is these days, I steer clear of anti-flicker snippets that hide the entire page. It doesn\u2019t matter if there are mitigations in place for ad blockers and download errors. If the endpoint is unresponsive or lags, <a href=\"https:\/\/support.google.com\/optimize\/answer\/7100284?hl=en\">Google Optimize\u2019s default anti-flicker snippet<\/a> has the page wait for a maximum of <strong>4 seconds<\/strong> (this is adjustable) before revealing the content. Naturally, if the container loads before that, the page is revealed faster. But still, OUCH!<\/p>\n<\/blockquote>\n<h2 id=\"measuring-the-impact-of-flicker\">Measuring the impact of flicker<\/h2>\n<p>So, let\u2019s assume the situation is as follows:<\/p>\n<p>You\u2019ve got an experiment running that treats a home page element, which is visible above the fold if the page is loaded without a scroll threshold in place.<\/p>\n<p>You\u2019ve deployed Google Optimize using the <a href=\"https:\/\/support.google.com\/optimize\/answer\/7513085?hl=en\">new snippet<\/a>. You\u2019ve deployed the <strong>asynchronous<\/strong> snippet, and you are <strong>not<\/strong> using the anti-flicker JavaScript, so there\u2019s a visible and measurable flicker in place.<\/p>\n<div style=\"aspect-ratio: 960 \/ 100;\" class=\"figure \">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/animated-flicker.gif\" title=\"Flicker of the original (grey background) before the variant (red background)\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"100\" width=\"960\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/animated-flicker.gif#ZgotmplZ\" alt=\"Flicker of the original (grey background) before the variant (red background)\"\/><\/p>\n<p>    <\/a><\/p>\n<p>    <span class=\"caption\">Flicker of the original (grey background) before the variant (red background)<\/span><\/p>\n<\/div>\n<p>In order to <strong>measure<\/strong> the severity of this flicker, we need to collect a number of timings:<\/p>\n<ol>\n<li>Time when the original element was added to the page,<\/li>\n<li>Time when the original element became visible in the viewport,<\/li>\n<li>Time when the experimentation library was loaded,<\/li>\n<li>Time when the experiment change was applied to the page.<\/li>\n<\/ol>\n<p>The <strong>flicker<\/strong> is the time delta between (2) and (4). If the element isn\u2019t visible in the viewport, or if the experiment is applied <em>before<\/em> the base element becomes visible, the flicker is not a problem. (3) is interesting metadata about how the experimentation library itself works, and how fast it manages to apply the change after loading.<\/p>\n<h3 id=\"introduction-to-the-javascript-well-need\">Introduction to the JavaScript we\u2019ll need<\/h3>\n<p>The solution will rely on two pieces of <strong>JavaScript<\/strong> code running <strong>directly in the page template<\/strong>. You <strong>can\u2019t<\/strong> execute this code reliably through a dependency like Google Tag Manager, because Google Tag Manager in many cases loads <em>after<\/em> all steps (1)-(4) have already happened, meaning you won\u2019t get accurate measurements.<\/p>\n<p>The first bit of JavaScript is run at the very top of <code\/>, even before the Optimize snippet. This script uses the <a href=\"https:\/\/support.google.com\/optimize\/answer\/9059383?hl=en\"><code>optimize.callback<\/code><\/a> API to collect the timestamp of the experimentation library load. This is timing number (3) in the list above.<\/p>\n<p>The second JavaScript snippet is added to the <strong>top of <code\/><\/strong>, because the observers need access to <code>document.body<\/code>. Here\u2019s what it does:<\/p>\n<ul>\n<li>A <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/MutationObserver\"><strong>MutationObserver<\/strong><\/a> waits on the page and reacts to two changes: when the element is first added to the page, and when the element is updated with the variant. These are timings (1) and (4), respectively, in the list above.<\/li>\n<li>An <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Intersection_Observer_API\"><strong>IntersectionObserver<\/strong><\/a> is added to the page as soon as the original element is rendered. The purpose of the IntersectionObserver is to fire a callback as soon as the original element is visible in the viewport. This is timing (2) in the list above.<\/li>\n<\/ul>\n<p>Once the timings have been collected, they are pushed into <code>dataLayer<\/code> to be used in Google Tag Manager.<\/p>\n<h3 id=\"other-preparations\">Other preparations<\/h3>\n<p>To best measure the application of the experiment element, I have added the <strong>data attribute<\/strong> <code>data-test=\"true\"<\/code> to the variant. This makes it easier for me to locate the element using CSS selectors.<\/p>\n<p>The attribute is added via the Optimize editor, and is thus only present on the element after it\u2019s modified by Google Optimize.<\/p>\n<div style=\"aspect-ratio: 1220 \/ 140;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/data-test-true.jpg\" title=\"data-test attribute on the element\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"140\" width=\"1220\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/data-test-true.jpg#ZgotmplZ\" alt=\"data-test attribute on the element\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Finally, I\u2019m collecting all this data using Google Tag Manager, and I\u2019m sending it to <a href=\"https:\/\/www.simoahava.com\/analytics\/getting-started-with-google-analytics-app-web\/\">App + Web<\/a> because I want to collect it in <a href=\"https:\/\/www.simoahava.com\/analytics\/enable-bigquery-export-google-analytics-app-web\/\">BigQuery<\/a> for more granular analysis.<\/p>\n<blockquote>\n<p>You could just as well calculate the delta directly in the client and send it to, for example, Universal Analytics as an event. This is entirely up to you. I opted for the BigQuery approach &#8211; I justify this <a href=\"#things-to-note\">later<\/a> in the article.<\/p>\n<\/blockquote>\n<h2 id=\"installing-the-optimize-library-and-callback\">Installing the Optimize library and callback<\/h2>\n<p>To install the Optimize library, I\u2019m adding the <code><script\/><\/code> element with the <code>async<\/code> attribute to the top of <code\/>.<\/p>\n<div style=\"aspect-ratio: 1644 \/ 178;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/optimize-snippet.jpg\" title=\"Optimize snippet\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"178\" width=\"1644\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/optimize-snippet.jpg#ZgotmplZ\" alt=\"Optimize snippet\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>By adding it to the top of <code\/>, I\u2019m not eliminating the flicker (because it\u2019s still loaded <em>asynchronously<\/em>), but I am making sure that the download of the library begins as soon as the page render starts. This helps mitigate the flicker a <strong>great<\/strong> deal.<\/p>\n<p>Then, to add the Optimize callback, I\u2019m running the following script <strong>before<\/strong> the Optimize snippet at the very beginning of <code\/>.<\/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-html\" data-lang=\"html\">html&gt;\n  head <span style=\"color:#1e90ff\">lang<\/span>=<span style=\"color:#a50\">\"en-us\"<\/span>&gt;\n    meta <span style=\"color:#1e90ff\">name<\/span>=<span style=\"color:#a50\">\"generator\"<\/span> <span style=\"color:#1e90ff\">content<\/span>=<span style=\"color:#a50\">\"Hugo 0.61.0\"<\/span> \/&gt;\n    script&gt;\n      <span style=\"color:#00a\">function<\/span> gtag() { <span style=\"color:#0aa\">window<\/span>.dataLayer = <span style=\"color:#0aa\">window<\/span>.dataLayer || []; <span style=\"color:#0aa\">window<\/span>.dataLayer.push(arguments); }\n      gtag(<span style=\"color:#a50\">'event'<\/span>, <span style=\"color:#a50\">'optimize.callback'<\/span>, {\n\t    callback: <span style=\"color:#00a\">function<\/span>(e) {\n\t      <span style=\"color:#0aa\">window<\/span>.__flickerTestMilestones = <span style=\"color:#0aa\">window<\/span>.__flickerTestMilestones || {};\n          <span style=\"color:#0aa\">window<\/span>.__flickerTestMilestones.experimentLoaded = <span style=\"color:#00a\">new<\/span> <span style=\"color:#0aa\">Date<\/span>().getTime();\n\t    }\n      });\n    <span style=\"color:#1e90ff;font-weight:bold\">script<\/span>&gt;\n    ...\n  <span style=\"color:#1e90ff;font-weight:bold\">head<\/span>&gt;\n  ...\n<span style=\"color:#1e90ff;font-weight:bold\">html<\/span>&gt;<\/code><\/pre>\n<\/div>\n<p>Here, we first create the <code>gtag<\/code> queue (because that\u2019s what Optimize uses for its API control). Then, we push a callback in the shape of a gtag event. I\u2019m passing an anonymous function to the callback argument. This function references a global object which we\u2019ll use to collect the milestones. The only milestone we populate in this callback is <code>experimentLoaded<\/code>, and we attach the current timestamp to it.<\/p>\n<p>The callback is invoked as soon as the experiment library has loaded and Optimize has established to which variant the user belongs (and thus which version of the element to show to them).<\/p>\n<h2 id=\"installing-the-observer-scripts\">Installing the observer scripts<\/h2>\n<p>Here\u2019s the tricky bit. You need to install <strong>two<\/strong> observers (a <code>MutationObserver<\/code> and an <code>IntersectionObserver<\/code>). The first checks if an element has been added to the page, and the second checks if an element is in the viewport. I\u2019ll show you the code first and then explain what it does.<\/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-html\" data-lang=\"html\">body&gt;\n  script&gt;\n      (<span style=\"color:#00a\">function<\/span>() {\n        <span style=\"color:#00a\">var<\/span> ftm = <span style=\"color:#0aa\">window<\/span>.__flickerTestMilestones = <span style=\"color:#0aa\">window<\/span>.__flickerTestMilestones || {};\n        <span style=\"color:#00a\">var<\/span> testState = <span style=\"color:#a50\">'success'<\/span>;\n        <span style=\"color:#00a\">var<\/span> dpush = <span style=\"color:#00a\">function<\/span>() {\n          <span style=\"color:#00a\">if<\/span> (testState !== <span style=\"color:#a50\">'noObservers'<\/span>) {\n            <span style=\"color:#aaa;font-style:italic\">\/\/ If milestones are incomplete and it's not because of lack of support, do nothing\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>            <span style=\"color:#00a\">if<\/span> (!ftm.experimentLoaded ||\n                !ftm.baseElementAddedToPage ||\n                !ftm.testElementAddedToPage) <span style=\"color:#00a\">return<\/span>;\n\n            <span style=\"color:#aaa;font-style:italic\">\/\/ If all other milestones are in place, but baseElementVisible is not,\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>            <span style=\"color:#aaa;font-style:italic\">\/\/ send the other timings and make note that base element was not visible.\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>            <span style=\"color:#00a\">if<\/span> (!ftm.baseElementVisible) { testState = <span style=\"color:#a50\">'baseNotVisible'<\/span>; }\n          }\n\n          <span style=\"color:#aaa;font-style:italic\">\/\/ Push everything to dataLayer\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>          <span style=\"color:#0aa\">window<\/span>.dataLayer = <span style=\"color:#0aa\">window<\/span>.dataLayer || [];\n          <span style=\"color:#0aa\">window<\/span>.dataLayer.push({\n            event: <span style=\"color:#a50\">'optimize_flicker_test'<\/span>,\n            testMilestones: {\n              baseElementAddedToPage: ftm.baseElementAddedToPage,\n              baseElementVisible: ftm.baseElementVisible,\n              experimentLoaded: ftm.experimentLoaded,\n              testElementAddedToPage: ftm.testElementAddedToPage,\n              testState: testState\n            }\n          });\n          \n          <span style=\"color:#aaa;font-style:italic\">\/\/ Reset the test\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>          <span style=\"color:#0aa\">window<\/span>.__flickerTestMilestones = {};\n        };\n        \n        <span style=\"color:#aaa;font-style:italic\">\/\/ Only run if observers are supported by the browser\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>        <span style=\"color:#00a\">if<\/span> (<span style=\"color:#0aa\">window<\/span>.MutationObserver &amp;&amp; <span style=\"color:#0aa\">window<\/span>.IntersectionObserver) {\n          <span style=\"color:#00a\">var<\/span> observer = <span style=\"color:#00a\">new<\/span> MutationObserver(<span style=\"color:#00a\">function<\/span>(mutations) {\n            mutations.forEach(<span style=\"color:#00a\">function<\/span>(mutation) {\n              <span style=\"color:#00a\">var<\/span> node = !!mutation.addedNodes.length &amp;&amp; mutation.addedNodes[<span style=\"color:#099\">0<\/span>];\n              <span style=\"color:#00a\">if<\/span> (node &amp;&amp; node.matches &amp;&amp; node.matches(<span style=\"color:#a50\">'span.talks'<\/span>)) {\n                <span style=\"color:#00a\">if<\/span> (node.matches(<span style=\"color:#a50\">'[data-test]'<\/span>)) {\n                  ftm.testElementAddedToPage = <span style=\"color:#00a\">new<\/span> <span style=\"color:#0aa\">Date<\/span>().getTime();\n                  dpush();\n                } <span style=\"color:#00a\">else<\/span> {\n                  ftm.baseElementAddedToPage = <span style=\"color:#00a\">new<\/span> <span style=\"color:#0aa\">Date<\/span>().getTime();\n                  dpush();\n                  <span style=\"color:#00a\">var<\/span> intersectionObserver = <span style=\"color:#00a\">new<\/span> IntersectionObserver(<span style=\"color:#00a\">function<\/span>(entries) {\n                    <span style=\"color:#00a\">if<\/span> (entries.some(<span style=\"color:#00a\">function<\/span>(e) {\n                        <span style=\"color:#00a\">return<\/span> e.intersectionRatio &gt; <span style=\"color:#099\">0<\/span>\n                      })) {\n                      ftm.baseElementVisible = <span style=\"color:#00a\">new<\/span> <span style=\"color:#0aa\">Date<\/span>().getTime();\n                      dpush();\n                    }\n                  });\n                  intersectionObserver.observe(node);\n                }\n              }\n            });\n          });\n          observer.observe(<span style=\"color:#0aa\">document<\/span>.body, {\n            childList: <span style=\"color:#00a\">true<\/span>,\n            subtree: <span style=\"color:#00a\">true<\/span>\n          });\n        } <span style=\"color:#00a\">else<\/span> {\n          <span style=\"color:#aaa;font-style:italic\">\/\/ Make note that there was no support for observers\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>          <span style=\"color:#0aa\">window<\/span>.__flickerTestMilestones = {};\n          testState = <span style=\"color:#a50\">'noObservers'<\/span>;\n          dpush();\n        }\n      })();\n    <span style=\"color:#1e90ff;font-weight:bold\">script<\/span>&gt;\n  ...\n<span style=\"color:#1e90ff;font-weight:bold\">body<\/span>&gt;\n<span style=\"color:#1e90ff;font-weight:bold\">html<\/span>&gt;<\/code><\/pre>\n<\/div>\n<p>This script runs at the very top of <code\/> so that the observers can be primed as fast as possible.<\/p>\n<p>First thing to check is whether the browser supports both <a href=\"https:\/\/caniuse.com\/#feat=mutationobserver\"><code>MutationObserver<\/code><\/a> and <a href=\"https:\/\/caniuse.com\/#feat=intersectionobserver\"><code>IntersectionObserver<\/code><\/a>. We don\u2019t have to support all browsers for this &#8211; we just need a representative sample. If there is no support, then the <code>dataLayer.push()<\/code> includes the <code>testState<\/code> key with the value <code>noObservers<\/code>, and we can use that in our analyses.<\/p>\n<blockquote>\n<p>I did not opt for a fallback to just polling the page until the element is found. It would have made the code more complex than it already is, and it could have potentially introduced performance issues that I\u2019d rather avoid when experimenting with data.<\/p>\n<\/blockquote>\n<p>The script then inserts the <strong>MutationObserver<\/strong>. This observer pattern can be used to detect things like DOM elements being added to the page or attributes changing for individual elements.<\/p>\n<p>I\u2019m only interested in child nodes being added to the page, because the original element (by the browser engine parsing the HTML source) and the variant (by the Optimize library) are added as new elements to the page. The observer is primed 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\"><span style=\"color:#00a\">var<\/span> observer = <span style=\"color:#00a\">new<\/span> MutationObserver(callback);\nobserver.observe(<span style=\"color:#0aa\">document<\/span>.body, {\n  childList: <span style=\"color:#00a\">true<\/span>,\n  subtree: <span style=\"color:#00a\">true<\/span>\n});\n<\/code><\/pre>\n<\/div>\n<p>We\u2019re attaching the observer to <code>document.body<\/code>, and we\u2019re reacting to any changes in the child nodes, regardless of how deep they are in the subtree. If a change is detected, the callback function is executed.<\/p>\n<p>In this case, I\u2019m making sure that the observer callback only reacts to when the element I\u2019m currently testing is added to the page:<\/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\">if<\/span> (node &amp;&amp; node.matches &amp;&amp; node.matches(<span style=\"color:#a50\">'span.talks'<\/span>)) {\n<\/code><\/pre>\n<\/div>\n<p>Next, the code checks if the node that was added is the experiment element:<\/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\">if<\/span> (node.matches(<span style=\"color:#a50\">'[data-test]'<\/span>)) {\n<\/code><\/pre>\n<\/div>\n<p>If you remember, above I mentioned that I add the <code>data-test=\"true\"<\/code> attribute to the test element to make debugging easier.<\/p>\n<p>If the added element was the experiment element, I update the milestone for <code>testElementAddedToPage<\/code> with the current timestamp. This is the moment when Optimize added the modified element to the page, and will serve as the endpoint of our delta measurement.<\/p>\n<p>If the element was <strong>not<\/strong> the experiment element, it <em>has<\/em> to be the base element, so I update the milestone <code>baseElementAddedToPage<\/code> together with the timestamp.<\/p>\n<p>Since I want to know the moment the base element became <strong>visible<\/strong> in the viewport, in the callback where I process the non-experiment element, I attach an <code>IntersectionObserver<\/code> to it as well.<\/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\">var<\/span> intersectionObserver = <span style=\"color:#00a\">new<\/span> IntersectionObserver(<span style=\"color:#00a\">function<\/span>(entries) {\n  <span style=\"color:#00a\">if<\/span> (entries.some(<span style=\"color:#00a\">function<\/span>(e) {\n    <span style=\"color:#00a\">return<\/span> e.intersectionRatio &gt; <span style=\"color:#099\">0<\/span>;\n  })) { ... }\n});\nintersectionObserver.observe(node);\n<\/code><\/pre>\n<\/div>\n<p>The <code>IntersectionObserver<\/code> activates whenever the element that is being observed enters the viewport of the browser. I can then check if the element is visible even the tiniest bit (<code>intersectionRatio &gt; 0<\/code>), and then update the milestone for <code>baseElementVisible<\/code> with the timestamp.<\/p>\n<p>After every milestone, I check if at least <code>baseElementAddedToPage<\/code>, <code>experimentLoaded<\/code>, and <code>testElementAddedToPage<\/code> milestones have been updated. If they have, the milestones and the test state are pushed into <code>dataLayer<\/code>.<\/p>\n<div style=\"aspect-ratio: 854 \/ 308;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/test-milestones.jpg\" title=\"Test milestones\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"308\" width=\"854\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/test-milestones.jpg#ZgotmplZ\" alt=\"Test milestones\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>There are two reasons I\u2019m not waiting around for <code>baseElementVisible<\/code>:<\/p>\n<ul>\n<li>Sometimes the experiment loads the updated element so fast that the base element is already removed from the page when the IntersectionObserver is supposed to go off.<\/li>\n<li>Sometimes the user has scrolled beyond the fold, and the <code>baseElementVisible<\/code> simply does not fire (because the base element is not, well, visible).<\/li>\n<\/ul>\n<p>Both of these mean that the flicker is basically a non-issue, so it\u2019s OK for me to just collect a <code>null<\/code> in these cases. I update <code>testState<\/code> with <code>\"baseNotVisible\"<\/code> to make it easier to parse these in the analysis.<\/p>\n<h2 id=\"setting-up-the-google-tag-manager-assets\">Setting up the Google Tag Manager assets<\/h2>\n<p>In GTM, the <strong>trigger<\/strong> I need looks like this:<\/p>\n<div style=\"aspect-ratio: 1846 \/ 764;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/optimize-custom-event-trigger.jpg\" title=\"Optimize Custom Event Trigger\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"764\" width=\"1846\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/optimize-custom-event-trigger.jpg#ZgotmplZ\" alt=\"Optimize Custom Event Trigger\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>You\u2019ll need <em>five<\/em> Data Layer variables. Each is setup like this:<\/p>\n<div style=\"aspect-ratio: 988 \/ 584;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/experiment-time-data-layer-variable.jpg\" title=\"Experiment time data layer variable\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"584\" width=\"988\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/experiment-time-data-layer-variable.jpg#ZgotmplZ\" alt=\"Experiment time data layer variable\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The variable names you\u2019ll need are:<\/p>\n<ul>\n<li><code>testMilestones.baseElementAddedToPage<\/code><\/li>\n<li><code>testMilestones.baseElementVisible<\/code><\/li>\n<li><code>testMilestones.experimentLoaded<\/code><\/li>\n<li><code>testMilestones.testElementAddedToPage<\/code><\/li>\n<li><code>testMilestones.testState<\/code><\/li>\n<\/ul>\n<p>And this is what the <strong>App + Web<\/strong> event tag looks like:<\/p>\n<div style=\"aspect-ratio: 1790 \/ 1874;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/app-web-event-tag.jpg\" title=\"App+Web event tag\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1874\" width=\"1790\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/app-web-event-tag.jpg#ZgotmplZ\" alt=\"App+Web event tag\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>As you can see, I\u2019m also sending a \u201cpage ID\u201d. I can use this to group all the timings for any given user\/session\/page combination a bit more easily. It\u2019s not strictly necessary, but might make some analyses a bit easier.<\/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\">function<\/span>() {\n  <span style=\"color:#0aa\">window<\/span>.__flickerTestPageId = <span style=\"color:#0aa\">window<\/span>.__flickerTestPageId || {{Random GUID}};\n  <span style=\"color:#00a\">return<\/span> <span style=\"color:#0aa\">window<\/span>.__flickerTestPageId;\n}\n<\/code><\/pre>\n<\/div>\n<blockquote>\n<p>The \u201cRandom GUID\u201d variable is <a href=\"https:\/\/www.simoahava.com\/analytics\/13-useful-custom-dimensions-for-google-analytics\/#12-session-id\">another Custom JavaScript variable<\/a> that returns a random, and fairly unique, identifier.<\/p>\n<\/blockquote>\n<p>By setting a global variable, we make sure the same ID is used for all the events that are being measured.<\/p>\n<p>When I now load the home page of my site, this is what I see being sent to App + Web:<\/p>\n<div style=\"aspect-ratio: 3466 \/ 206;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/request-payload.jpg\" title=\"Request payload\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"206\" width=\"3466\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/request-payload.jpg#ZgotmplZ\" alt=\"Request payload\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h2 id=\"bigquery-output\">BigQuery output<\/h2>\n<p>In BigQuery, our event parameters are added into the <code>event_parameters<\/code> record. It\u2019s not the classiest way to pass key-value pairs, especially since values are distributed as columns with one column per (potential) type. However, it is the only supported way of exporting custom parameters for now.<\/p>\n<h3 id=\"full-table-output\">Full table output<\/h3>\n<p>Here\u2019s what those <code>optimize_flicker_test<\/code> hits look like in our event table:<\/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-SQL\" data-lang=\"SQL\"><span style=\"color:#00a\">SELECT<\/span>\n  *\n<span style=\"color:#00a\">FROM<\/span>\n  `project.dataset.events_yyyymmdd`\n<span style=\"color:#00a\">WHERE<\/span>\n  event_name = <span style=\"color:#a50\">\"optimize_flicker_test\"<\/span><\/code><\/pre>\n<\/div>\n<div style=\"aspect-ratio: 2466 \/ 930;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/sql-full-table.jpg\" title=\"SQL full table\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"930\" width=\"2466\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/sql-full-table.jpg#ZgotmplZ\" alt=\"SQL full table\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"count-of-test-states\">Count of test states<\/h3>\n<p>We can drill down and count the respective number of each test state:<\/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-SQL\" data-lang=\"SQL\"><span style=\"color:#00a\">SELECT<\/span>\n  ep.value.string_value <span style=\"color:#00a\">AS<\/span> test_state,\n  <span style=\"color:#00a\">COUNT<\/span>(*) <span style=\"color:#00a\">as<\/span> <span style=\"color:#00a\">count<\/span>\n<span style=\"color:#00a\">FROM<\/span>\n  `project.dataset.events_yyyymmdd`,\n  <span style=\"color:#00a\">UNNEST<\/span>(event_params) <span style=\"color:#00a\">as<\/span> ep\n<span style=\"color:#00a\">WHERE<\/span>\n  event_name = <span style=\"color:#a50\">\"optimize_flicker_test\"<\/span>\n  <span style=\"color:#00a\">AND<\/span> ep.<span style=\"color:#00a\">key<\/span> = <span style=\"color:#a50\">\"testState\"<\/span>\n<span style=\"color:#00a\">GROUP<\/span> <span style=\"color:#00a\">BY<\/span>\n  <span style=\"color:#099\">1<\/span>\n<span style=\"color:#00a\">ORDER<\/span> <span style=\"color:#00a\">BY<\/span>\n  <span style=\"color:#099\">2<\/span> <span style=\"color:#00a\">DESC<\/span><\/code><\/pre>\n<\/div>\n<div style=\"aspect-ratio: 1246 \/ 508;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/sql-count-of-test-states.jpg\" title=\"Count of test states\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"508\" width=\"1246\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/sql-count-of-test-states.jpg#ZgotmplZ\" alt=\"Count of test states\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"table-with-deltas\">Table with deltas<\/h3>\n<p>We can also build a query that returns all the milestones with the calculated deltas:<\/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-SQL\" data-lang=\"SQL\"><span style=\"color:#00a\">WITH<\/span> milestones <span style=\"color:#00a\">AS<\/span> (<span style=\"color:#00a\">SELECT<\/span>\n  (<span style=\"color:#00a\">SELECT<\/span> value.int_value <span style=\"color:#00a\">FROM<\/span> t.event_params <span style=\"color:#00a\">WHERE<\/span> <span style=\"color:#00a\">key<\/span> = <span style=\"color:#a50\">\"baseElementAddedToPage\"<\/span>) <span style=\"color:#00a\">as<\/span> baseElementAddedToPage,\n  (<span style=\"color:#00a\">SELECT<\/span> value.int_value <span style=\"color:#00a\">FROM<\/span> t.event_params <span style=\"color:#00a\">WHERE<\/span> <span style=\"color:#00a\">key<\/span> = <span style=\"color:#a50\">\"baseElementVisible\"<\/span>) <span style=\"color:#00a\">as<\/span> baseElementVisible,\n  (<span style=\"color:#00a\">SELECT<\/span> value.int_value <span style=\"color:#00a\">FROM<\/span> t.event_params <span style=\"color:#00a\">WHERE<\/span> <span style=\"color:#00a\">key<\/span> = <span style=\"color:#a50\">\"experimentLoaded\"<\/span>) <span style=\"color:#00a\">as<\/span> experimentLoaded,\n  (<span style=\"color:#00a\">SELECT<\/span> value.int_value <span style=\"color:#00a\">FROM<\/span> t.event_params <span style=\"color:#00a\">WHERE<\/span> <span style=\"color:#00a\">key<\/span> = <span style=\"color:#a50\">\"testElementAddedToPage\"<\/span>) <span style=\"color:#00a\">as<\/span> testElementAddedToPage\n<span style=\"color:#00a\">FROM<\/span>\n  `project.dataset.events_yyyymmdd` t\n<span style=\"color:#00a\">WHERE<\/span>\n  t.event_name = <span style=\"color:#a50\">\"optimize_flicker_test\"<\/span>)\n<span style=\"color:#00a\">SELECT<\/span>  \n  baseElementAddedToPage,\n  testElementAddedToPage-baseElementAddedToPage <span style=\"color:#00a\">AS<\/span> injection_delta,\n  baseElementVisible,\n  testElementAddedToPage-baseElementVisible <span style=\"color:#00a\">AS<\/span> flicker_delta,\n  experimentLoaded,\n  experimentLoaded-baseElementAddedToPage <span style=\"color:#00a\">AS<\/span> experiment_delta,\n  testElementAddedToPage\n<span style=\"color:#00a\">FROM<\/span> \n  milestones\n<span style=\"color:#00a\">ORDER<\/span> <span style=\"color:#00a\">BY<\/span> \n  flicker_delta <span style=\"color:#00a\">DESC<\/span><\/code><\/pre>\n<\/div>\n<div style=\"aspect-ratio: 1778 \/ 986;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/sql-calculated-milestones.jpg\" title=\"Calculated milestones\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"986\" width=\"1778\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/sql-calculated-milestones.jpg#ZgotmplZ\" alt=\"Calculated milestones\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>This is just one way to approach the data.<\/p>\n<p>Here, I\u2019m calculating <code>injection_delta<\/code> as the time it takes for the test element to be added to the page <em>after<\/em> the base element has been added. This could be used as a stand-in to measure the <em>potential<\/em> flicker.<\/p>\n<p>The <code>flicker_delta<\/code> is the elapsed time from the base element becoming visible to it being replaced with the variant.<\/p>\n<p>Finally, <code>experiment_delta<\/code> is the elapsed time from the base element being added to the page to the experiment loading. This value can be <em>negative<\/em>, meaning the experiment library loaded <em>before<\/em> the base element was added to the page. This is possible especially if the experiment library loads really fast after being, for example, cached by the browser.<\/p>\n<h3 id=\"simple-calculations\">Simple calculations<\/h3>\n<p>Once you have the milestones, you can do simple calculations:<\/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-SQL\" data-lang=\"SQL\"><span style=\"color:#00a\">WITH<\/span> milestones <span style=\"color:#00a\">AS<\/span> (<span style=\"color:#00a\">SELECT<\/span>\n  (<span style=\"color:#00a\">SELECT<\/span> value.int_value <span style=\"color:#00a\">FROM<\/span> t.event_params <span style=\"color:#00a\">WHERE<\/span> <span style=\"color:#00a\">key<\/span> = <span style=\"color:#a50\">\"baseElementAddedToPage\"<\/span>) <span style=\"color:#00a\">as<\/span> baseElementAddedToPage,\n  (<span style=\"color:#00a\">SELECT<\/span> value.int_value <span style=\"color:#00a\">FROM<\/span> t.event_params <span style=\"color:#00a\">WHERE<\/span> <span style=\"color:#00a\">key<\/span> = <span style=\"color:#a50\">\"baseElementVisible\"<\/span>) <span style=\"color:#00a\">as<\/span> baseElementVisible,\n  (<span style=\"color:#00a\">SELECT<\/span> value.int_value <span style=\"color:#00a\">FROM<\/span> t.event_params <span style=\"color:#00a\">WHERE<\/span> <span style=\"color:#00a\">key<\/span> = <span style=\"color:#a50\">\"experimentLoaded\"<\/span>) <span style=\"color:#00a\">as<\/span> experimentLoaded,\n  (<span style=\"color:#00a\">SELECT<\/span> value.int_value <span style=\"color:#00a\">FROM<\/span> t.event_params <span style=\"color:#00a\">WHERE<\/span> <span style=\"color:#00a\">key<\/span> = <span style=\"color:#a50\">\"testElementAddedToPage\"<\/span>) <span style=\"color:#00a\">as<\/span> testElementAddedToPage\n<span style=\"color:#00a\">FROM<\/span>\n  `project.dataset.event_yyyymmdd` t\n<span style=\"color:#00a\">WHERE<\/span>\n  t.event_name = <span style=\"color:#a50\">\"optimize_flicker_test\"<\/span>)\n<span style=\"color:#00a\">SELECT<\/span>\n  <span style=\"color:#00a\">AVG<\/span>(injection_delta) <span style=\"color:#00a\">as<\/span> average_injection_delta,\n  <span style=\"color:#00a\">MIN<\/span>(injection_delta) <span style=\"color:#00a\">as<\/span> minimum_injection_delta,\n  <span style=\"color:#00a\">MAX<\/span>(injection_delta) <span style=\"color:#00a\">as<\/span> maximum_injection_delta,\n  <span style=\"color:#00a\">AVG<\/span>(flicker_delta) <span style=\"color:#00a\">as<\/span> average_flicker_delta,\n  <span style=\"color:#00a\">MIN<\/span>(flicker_delta) <span style=\"color:#00a\">as<\/span> minimum_flicker_delta,\n  <span style=\"color:#00a\">MAX<\/span>(flicker_delta) <span style=\"color:#00a\">as<\/span> maximum_flicker_delta\n<span style=\"color:#00a\">FROM<\/span> (\n  <span style=\"color:#00a\">SELECT<\/span>  \n    baseElementAddedToPage,\n    testElementAddedToPage-baseElementAddedToPage <span style=\"color:#00a\">as<\/span> injection_delta,\n    baseElementVisible,\n    testElementAddedToPage-baseElementVisible <span style=\"color:#00a\">as<\/span> flicker_delta,\n    experimentLoaded,\n    experimentLoaded-baseElementAddedToPage <span style=\"color:#00a\">as<\/span> experiment_delta,\n    testElementAddedToPage\n  <span style=\"color:#00a\">FROM<\/span> \n    milestones\n)<\/code><\/pre>\n<\/div>\n<div style=\"aspect-ratio: 1856 \/ 430;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2020\/05\/sql-averages.jpg\" title=\"SQL Averages\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"430\" width=\"1856\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2020\/05\/sql-averages.jpg#ZgotmplZ\" alt=\"SQL Averages\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Nothing ground-breaking analysis-wise, but this shows what you could do with the data.<\/p>\n<h2 id=\"things-to-note\">Things to note<\/h2>\n<p>OK, so I misled you a bit with the title of this article. The purpose wasn\u2019t to show you how to measure the <strong>impact<\/strong> of the flicker, but rather how to collect the data that <em>allows<\/em> you to measure the impact of the flicker.<\/p>\n<p>With the raw data about element visibility and element injection, you can measure what the delta is and how it impacts conversions, for example. However, this analysis is left for you to calibrate against your unique data set and your unique experiments.<\/p>\n<p>You probably also wondered when reading the setup, \u201cWhy isn\u2019t this guy just calculating the delta in the client and sending <em>that<\/em> to App+Web? Or, better yet, why isn\u2019t he just sending it to Google Analytics?\u201d. Totally valid questions.<\/p>\n<p>I decided to build this experiment around <strong>raw, uncalculated data<\/strong>. Many analytics tools have happily promoted the idea that calculated metrics don\u2019t need to be reverse engineerable. This has led to confusion around things like the <a href=\"https:\/\/www.simoahava.com\/analytics\/the-schema-conspiracy\/\">sessionization schema<\/a> of Google Analytics, and the weird, quantum fluctuations that seem to govern any data set when you zoom in close enough.<\/p>\n<p>With App+Web, <em>everybody<\/em> has access to the raw data dump in BigQuery, so I want to start pushing forward the approach where the <strong>data you collect is as raw as possible<\/strong>, so that you have <strong>full freedom<\/strong> for parsing the data as you wish in BQ.<\/p>\n<p>You are, of course, free to modify the solution however you wish.<\/p>\n<h2 id=\"summary\">Summary<\/h2>\n<p>There are some <strong>caveats<\/strong> to this experiment. <a href=\"https:\/\/caniuse.com\/#feat=intersectionobserver\">Not all browsers support Mutation- and IntersectionObservers<\/a>.<\/p>\n<p>There might also be cases where the <code>baseElementVisible<\/code> simply refuses to fire in time, even if there was a flicker. The single-threadedness of JavaScript has the tendency to mess with callbacks. That\u2019s why I opted to throw the <code>baseElementVisible<\/code> baby out with the bathwater &#8211; if this metric hasn\u2019t been collected by the time all the other metrics have, I\u2019d rather not send it at all rather than send confusing values where the base element became visible <em>after<\/em> the test element was added.<\/p>\n<p>When setting up this test, just remember these key points:<\/p>\n<ol>\n<li>\n<p>The JavaScript snippets <em>must<\/em> be added in the page template. If you\u2019re firing Optimize through Google Tag Manager, you <em>could<\/em> add the Optimize callback in a Custom HTML tag, but there\u2019s still the risk of a race condition ruining everything.<\/p>\n<\/li>\n<li>\n<p>You need to update the CSS selectors in the observers to match the element you are measuring. Adding the <code>data-test=\"true\"<\/code> (or something similar) to the element in the Optimize editor makes it easier to adjust the observer pattern.<\/p>\n<\/li>\n<\/ol>\n<p>Let me know in the comments if you have additional insights to setting up the test or the subsequent analysis.<\/p>\n<\/p><\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>\u201cFlickering\u201d or \u201cFlash Of Original Content\u201d (FOOC) is a phenomenon where there\u2019s a (typically) slight but observable delay in the browser updating the site or element layout if the user is included in a variant group for experimentation. This manifests in the original, unmodified element being rendered in the visible portion of the page before [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":88926,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12033],"tags":[43328,4197,16556,2961,13597],"dealstore":[],"offerexpiration":[],"class_list":["post-88925","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-analytics","tag-flicker","tag-impact","tag-measure","tag-simple","tag-test"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Simple Way To Measure A\/B Test Flicker Impact - 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=88925\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Simple Way To Measure A\/B Test Flicker Impact - Som2ny Network\" \/>\n<meta property=\"og:description\" content=\"\u201cFlickering\u201d or \u201cFlash Of Original Content\u201d (FOOC) is a phenomenon where there\u2019s a (typically) slight but observable delay in the browser updating the site or element layout if the user is included in a variant group for experimentation. This manifests in the original, unmodified element being rendered in the visible portion of the page before [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fivemor.com\/?p=88925\" \/>\n<meta property=\"og:site_name\" content=\"Som2ny Network\" \/>\n<meta property=\"article:published_time\" content=\"2025-02-15T03:27:46+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/flicker-test-hero-scaled.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"2560\" \/>\n\t<meta property=\"og:image:height\" content=\"1232\" \/>\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=\"18 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/fivemor.com\/?p=88925#article\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/?p=88925\"},\"author\":{\"name\":\"admin\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\"},\"headline\":\"Simple Way To Measure A\/B Test Flicker Impact\",\"datePublished\":\"2025-02-15T03:27:46+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=88925\"},\"wordCount\":2690,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=88925#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/flicker-test-hero-scaled.jpg\",\"keywords\":[\"Flicker\",\"Impact\",\"Measure\",\"Simple\",\"Test\"],\"articleSection\":[\"Analytics\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/fivemor.com\/?p=88925#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/fivemor.com\/?p=88925\",\"url\":\"https:\/\/fivemor.com\/?p=88925\",\"name\":\"Simple Way To Measure A\/B Test Flicker Impact - Som2ny Network\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=88925#primaryimage\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=88925#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/flicker-test-hero-scaled.jpg\",\"datePublished\":\"2025-02-15T03:27:46+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/fivemor.com\/?p=88925#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/fivemor.com\/?p=88925\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/?p=88925#primaryimage\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/flicker-test-hero-scaled.jpg\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/flicker-test-hero-scaled.jpg\",\"width\":2560,\"height\":1232},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/fivemor.com\/?p=88925#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/fivemor.com\/?bp_activities=1\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Simple Way To Measure A\/B Test Flicker Impact\"}]},{\"@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":"Simple Way To Measure A\/B Test Flicker Impact - 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=88925","og_locale":"en_US","og_type":"article","og_title":"Simple Way To Measure A\/B Test Flicker Impact - Som2ny Network","og_description":"\u201cFlickering\u201d or \u201cFlash Of Original Content\u201d (FOOC) is a phenomenon where there\u2019s a (typically) slight but observable delay in the browser updating the site or element layout if the user is included in a variant group for experimentation. This manifests in the original, unmodified element being rendered in the visible portion of the page before [&hellip;]","og_url":"https:\/\/fivemor.com\/?p=88925","og_site_name":"Som2ny Network","article_published_time":"2025-02-15T03:27:46+00:00","og_image":[{"width":2560,"height":1232,"url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/flicker-test-hero-scaled.jpg","type":"image\/jpeg"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"admin","Est. reading time":"18 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/fivemor.com\/?p=88925#article","isPartOf":{"@id":"https:\/\/fivemor.com\/?p=88925"},"author":{"name":"admin","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371"},"headline":"Simple Way To Measure A\/B Test Flicker Impact","datePublished":"2025-02-15T03:27:46+00:00","mainEntityOfPage":{"@id":"https:\/\/fivemor.com\/?p=88925"},"wordCount":2690,"commentCount":0,"publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"image":{"@id":"https:\/\/fivemor.com\/?p=88925#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/flicker-test-hero-scaled.jpg","keywords":["Flicker","Impact","Measure","Simple","Test"],"articleSection":["Analytics"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/fivemor.com\/?p=88925#respond"]}]},{"@type":"WebPage","@id":"https:\/\/fivemor.com\/?p=88925","url":"https:\/\/fivemor.com\/?p=88925","name":"Simple Way To Measure A\/B Test Flicker Impact - Som2ny Network","isPartOf":{"@id":"https:\/\/fivemor.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/fivemor.com\/?p=88925#primaryimage"},"image":{"@id":"https:\/\/fivemor.com\/?p=88925#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/flicker-test-hero-scaled.jpg","datePublished":"2025-02-15T03:27:46+00:00","breadcrumb":{"@id":"https:\/\/fivemor.com\/?p=88925#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fivemor.com\/?p=88925"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/?p=88925#primaryimage","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/flicker-test-hero-scaled.jpg","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/flicker-test-hero-scaled.jpg","width":2560,"height":1232},{"@type":"BreadcrumbList","@id":"https:\/\/fivemor.com\/?p=88925#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/fivemor.com\/?bp_activities=1"},{"@type":"ListItem","position":2,"name":"Simple Way To Measure A\/B Test Flicker Impact"}]},{"@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\/88925","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=88925"}],"version-history":[{"count":0,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/88925\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/media\/88926"}],"wp:attachment":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=88925"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=88925"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=88925"},{"taxonomy":"dealstore","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fdealstore&post=88925"},{"taxonomy":"offerexpiration","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fofferexpiration&post=88925"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}