/**
* ========================================================================
* A/B Testing Standard Snippet Template
* ========================================================================
* This is the standard template for all A/B testing variations.
* Use this structure to ensure consistency across all tests.
*/
(function() {
try {
/* Main variables */
var debug = 0; // Set to 1 to enable console logging for debugging
var variation_name = ""; // Name of your test variation (e.g., "homepage-hero-test-v1")
/* All Pure helper functions */
function waitForElement(selector, trigger, delayInterval = 50, delayTimeout = 15000) {
var interval = setInterval(function() {
if (document && document.querySelector(selector) && document.querySelectorAll(selector).length > 0) {
clearInterval(interval);
trigger(document.querySelector(selector));
}
}, delayInterval);
setTimeout(function() {
clearInterval(interval);
}, delayTimeout);
}
/* Variation functions - Write your test logic here */
/* Variation Init - This is where your test starts */
function init() {
/* Start your code here */
console.log("Test initialized. Please check if functionality is triggering correctly.");
}
/* Initialize variation */
waitForElement("body", init);
} catch (e) {
if (debug) console.log(e, "error in Test " + variation_name);
}
})();
/**
* ========================================================================
* LIBRARY FUNCTIONS FOR A/B TESTING
* ========================================================================
* All functions below are pure functions (no side effects, same input = same output)
* that can be used in your A/B tests. Copy only what you need.
*/
// ========================================================================
// 1. DOM MANIPULATION & OBSERVATION
// ========================================================================
/**
* #1.1 Wait For Element
*
* USE WHEN: You need to wait for an element to appear in the DOM before executing code.
* COMMON USE CASE: Single Page Applications (SPAs), dynamically loaded content, lazy-loaded images
*
* EXAMPLE:
* waitForElement('.product-title', (element) => {
* element.textContent = 'New Title';
* });
*
* @param {string} selector - CSS selector for the element to wait for
* @param {Function} trigger - Callback function to execute when element is found
* @param {number} [delayInterval=50] - How often to check for element (in milliseconds)
* @param {number} [delayTimeout=15000] - How long to wait before giving up (in milliseconds)
*/
function waitForElement(selector, trigger, delayInterval = 50, delayTimeout = 15000) {
var interval = setInterval(function() {
if (document && document.querySelector(selector) && document.querySelectorAll(selector).length > 0) {
clearInterval(interval);
trigger(document.querySelector(selector));
}
}, delayInterval);
setTimeout(function() {
clearInterval(interval);
}, delayTimeout);
}
/**
* #1.2 Observe Selector (Advanced DOM Observer)
*
* USE WHEN: You need to watch for multiple elements that might be added/removed dynamically
* DIFFERENCE FROM waitForElement: This continues watching and can handle multiple elements
* COMMON USE CASE: Product listing pages, infinite scroll, search results
* DEPENDS ON: debounce() function (include it in your code)
*
* EXAMPLE:
* const stopObserving = observeSelector('.product-card', (element) => {
* console.log('New product card found:', element);
* });
* // Later, if needed: stopObserving();
*
* @param {string} selector - CSS selector to observe
* @param {Function} callback - Function to call for each matching element found
* @param {Object} [options={}] - Configuration options
* @param {Document} [options.document=window.document] - The document to observe
* @param {boolean} [options.once=false] - If true, stops observing after first match
* @returns {Function} - A function to stop observing when called
*/
function observeSelector(selector, callback, options = {}) {
const document = options.document || window.document;
const processed = new Map();
if (options.timeout || options.onTimeout) {
throw `observeSelector options \`timeout\` and \`onTimeout\` are not yet implemented.`;
}
let obs;
let isDone = false;
const done = () => {
if (obs) obs.disconnect();
isDone = true;
};
const processElement = el => {
if (!processed.has(el)) {
processed.set(el, true);
callback(el);
if (options.once) {
done();
return true;
}
}
return false;
};
const lookForSelector = () => {
const elParent = document.documentElement;
if (elParent.matches(selector) || elParent.querySelector(selector)) {
const elements = elParent.querySelectorAll(selector);
elements.forEach(el => processElement(el));
}
};
const debouncedLookForSelector = debounce(() => {
lookForSelector();
}, 100);
// Initial check for the selector on page load
lookForSelector();
if (!isDone) {
obs = new MutationObserver(() => {
debouncedLookForSelector();
});
obs.observe(document, {
attributes: false,
childList: true,
subtree: true,
});
}
return done;
}
/**
* #1.3 Add Class to Element
*
* USE WHEN: You need to add a CSS class to an element
* COMMON USE CASE: Applying custom styling, toggling visibility
*
* EXAMPLE:
* addClass('.product-card', 'highlighted');
*
* @param {string} selector - CSS selector for the element
* @param {string} [cls=""] - Class name to add
*/
function addClass(selector, cls = "") {
var el = document.querySelector(selector);
if (el) {
el.classList.add(cls);
}
}
/**
* #1.4 Remove Class from Element
*
* USE WHEN: You need to remove a CSS class from an element
* COMMON USE CASE: Removing default styling, hiding elements
*
* EXAMPLE:
* removeClass('.modal', 'hidden');
*
* @param {string} selector - CSS selector for the element
* @param {string} [cls=""] - Class name to remove
*/
function removeClass(selector, cls = "") {
var el = document.querySelector(selector);
if (el && el.classList.contains(cls)) {
el.classList.remove(cls);
}
}
/**
* #1.5 Change Content of Element
*
* USE WHEN: You need to replace the HTML content of an element
* COMMON USE CASE: Updating headlines, button text, descriptions
* WARNING: Use with caution - replaces all inner HTML
*
* EXAMPLE:
* changeContent('.hero-title', '
New Headline
');
*
* @param {string} selector - CSS selector for the element
* @param {string} [content=""] - HTML content to set
*/
function changeContent(selector, content = "") {
var el = document.querySelector(selector);
if (el) {
el.innerHTML = content;
}
}
// ========================================================================
// 2. EVENT HANDLING
// ========================================================================
/**
* #2.1 Live Event Listener (Event Delegation)
*
* USE WHEN: You need to attach events to elements that don't exist yet or are dynamically added
* DIFFERENCE FROM addEventListener: Works on future elements matching the selector
* COMMON USE CASE: Click handlers for dynamically loaded buttons, product cards
*
* EXAMPLE:
* live('.add-to-cart-btn', 'click', function(e) {
* console.log('Add to cart clicked', this);
* });
*
* @param {string} selector - CSS selector for target elements
* @param {string} event - Event type (e.g., 'click', 'input', 'change')
* @param {Function} callback - Function to call when event fires
* @param {Document|Element} [context=document] - Parent element to attach listener to
*/
function live(selector, event, callback, context = document) {
const addEvent = (el, type, handler) => {
el.addEventListener(type, handler);
};
const liveHandler = e => {
const el = e.target && e.target.closest ? e.target.closest(selector) : null;
if (el && el !== context) {
callback.call(el, e);
}
};
addEvent(context, event, liveHandler);
}
/**
* #2.2 Debounce Function
*
* USE WHEN: You need to limit how often a function is called
* COMMON USE CASE: Search input handlers, window resize events, scroll handlers
* WHY: Prevents performance issues by reducing function calls
*
* EXAMPLE:
* const handleSearch = debounce((value) => {
* console.log('Searching for:', value);
* }, 300);
*
* input.addEventListener('input', (e) => handleSearch(e.target.value));
*
* @param {Function} func - The function to debounce
* @param {number} [delay=20] - Wait time in milliseconds before calling function
* @returns {Function} - Debounced version of the function
*/
function debounce(func, delay = 20) {
let timeout;
return function(...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), delay);
};
}
/**
* #2.3 SPA Location Change Listener
*
* USE WHEN: Testing on Single Page Applications (React, Vue, Angular) where URL changes don't reload page
* COMMON USE CASE: Running test logic when user navigates between pages in an SPA
*
* EXAMPLE:
* listener(() => {
* console.log('Page changed to:', window.location.pathname);
* // Re-run your test logic here
* });
*
* @param {Function} trigger - Callback function to execute on location change
*/
function listener(trigger) {
window.addEventListener("locationchange", function() {
trigger();
});
history.pushState = (f =>
function pushState() {
var ret = f.apply(this, arguments);
window.dispatchEvent(new Event("pushstate"));
window.dispatchEvent(new Event("locationchange"));
return ret;
})(history.pushState);
history.replaceState = (f =>
function replaceState() {
var ret = f.apply(this, arguments);
window.dispatchEvent(new Event("replacestate"));
window.dispatchEvent(new Event("locationchange"));
return ret;
})(history.replaceState);
window.addEventListener("popstate", () => {
window.dispatchEvent(new Event("locationchange"));
});
}
// ========================================================================
// 3. COOKIE MANAGEMENT
// ========================================================================
/**
* #3.1 Set Cookie
*
* USE WHEN: You need to store data in browser cookies
* COMMON USE CASE: Saving user preferences, tracking test exposure, storing variation info
*
* EXAMPLE:
* setCookie('test_variant', 'variant_b', 30); // Expires in 30 days
*
* @param {string} name - Cookie name
* @param {string} value - Cookie value
* @param {number} days - Number of days until cookie expires
*/
function setCookie(name, value, days) {
var expires = "";
if (days) {
var date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + (value || "") + expires + "; path=/";
}
/**
* #3.2 Get Cookie
*
* USE WHEN: You need to read a cookie value
* COMMON USE CASE: Checking if user has seen a test before, reading saved preferences
*
* EXAMPLE:
* const variant = getCookie('test_variant');
* if (variant === 'variant_b') {
* // Show variant B
* }
*
* @param {string} name - Cookie name to retrieve
* @returns {string|null} - Cookie value or null if not found
*/
function getCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(";");
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == " ") c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
/**
* #3.3 Remove Cookie
*
* USE WHEN: You need to delete a cookie
* COMMON USE CASE: Clearing test data, removing user preferences
*
* EXAMPLE:
* removeCookie('test_variant');
*
* @param {string} cookieName - Name of cookie to remove
*/
function removeCookie(cookieName) {
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
}
// ========================================================================
// 4. UTILITY FUNCTIONS
// ========================================================================
/**
* #4.1 Generate Unique ID (UUID)
*
* USE WHEN: You need a unique identifier for tracking, session IDs, or element IDs
* COMMON USE CASE: Creating unique IDs for dynamically inserted elements, tracking events
*
* EXAMPLE:
* const uniqueId = uuid();
* element.setAttribute('data-test-id', uniqueId);
*
* @returns {string} - A unique identifier string
*/
function uuid() {
var timestamp = new Date().getTime();
var randomNumber = Math.floor(Math.random() * 100000);
var uuid = `${timestamp}-${randomNumber}-`;
var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split("");
for (var i = 0; i < 8; i++) {
uuid += characters[Math.floor(Math.random() * characters.length)];
}
return uuid;
}
// ========================================================================
// 5. ASYNC/FETCH UTILITIES
// ========================================================================
/**
* #5.1 Cache Fetch (with Session Storage)
*
* USE WHEN: You need to fetch data from an API and cache it to avoid repeated requests
* COMMON USE CASE: Fetching product data, pricing, availability
* WHY: Improves performance and reduces server load
*
* EXAMPLE:
* cache('https://api.example.com/products', 'products-cache', true)
* .then(data => console.log('Products:', data))
* .catch(err => console.error('Error:', err));
*
* @param {string} url - URL to fetch from
* @param {string} [cacheKey=""] - Unique key for caching
* @param {boolean} [isJson=true] - Whether to parse response as JSON
* @param {Object} [options={}] - Fetch options
* @param {number} [ttl=3600] - Cache time-to-live in seconds (default 1 hour)
* @returns {Promise} - Promise resolving to the fetched data
*/
async function cache(url, cacheKey = "", isJson = true, options = {}, ttl = 3600) {
const now = Date.now();
const cacheKeyCombined = `${url}-${cacheKey}`;
const cached = JSON.parse(sessionStorage.getItem(cacheKeyCombined) || "{}");
try {
// Check if valid cache exists
if (cached.data && now - cached.timestamp < ttl * 1000) {
return cached.data;
}
// Fetch from the network if no valid cache is found
const fetchOptions = { ...options, cache: options?.cache || "default" };
const response = await fetch(url, fetchOptions);
// Ensure the response is valid
if (!response.ok) {
throw new Error(`Failed to fetch data. HTTP Status: ${response.status}`);
}
// Parse the response based on isJson flag
const data = await response[isJson ? "json" : "text"]();
// Save the response to the cache
sessionStorage.setItem(cacheKeyCombined, JSON.stringify({ data, timestamp: now }));
return data;
} catch (error) {
throw error;
}
}
// ========================================================================
// 6. SHOPIFY CART MONITORING (E-commerce Specific)
// ========================================================================
/**
* #6.1 Shopify Cart Change Observer
*
* USE WHEN: Testing on Shopify stores and need to detect cart changes
* COMMON USE CASE: Triggering cart upsells, showing notifications when items are added
*
* EXAMPLE:
* new PerformanceObserver(function(e) {
* !e.getEntriesByType("resource").some(({ name }) =>
* /\/cart\/(change|update|add|clear)/i.test(name)
* ) || showCartNotification();
* }).observe({ entryTypes: ["resource"] });
*
* Replace YOUR_FUNCTION with your callback function
*/
new PerformanceObserver(function(e) {
!e.getEntriesByType("resource").some(({ name }) => /\/cart\/(change|update|add|clear)/i.test(name)) || YOUR_FUNCTION();
}).observe({ entryTypes: ["resource"] });
// ========================================================================
// 7. QUICK REFERENCE EXAMPLES
// ========================================================================
/**
* COMMON A/B TESTING PATTERNS:
*
* 1. CHANGE BUTTON TEXT:
* waitForElement('.checkout-button', (btn) => {
* btn.textContent = 'Complete Purchase';
* });
*
* 2. ADD URGENCY MESSAGE:
* waitForElement('.product-price', (priceEl) => {
* priceEl.insertAdjacentHTML('afterend',
* '
Only 3 left in stock!
'
* );
* });
*
* 3. TRACK BUTTON CLICKS:
* live('.add-to-cart', 'click', function() {
* console.log('Add to cart clicked');
* // Track event in your analytics tool
* });
*
* 4. SHOW ELEMENT ONLY FOR RETURNING USERS:
* if (getCookie('returning_user') === 'yes') {
* waitForElement('.special-offer', (el) => {
* el.style.display = 'block';
* });
* }
*
* 5. DEBOUNCED SEARCH:
* const searchHandler = debounce((value) => {
* // Perform search API call
* console.log('Searching for:', value);
* }, 500);
*
* document.querySelector('.search-input').addEventListener('input', (e) => {
* searchHandler(e.target.value);
* });
*/
// ========================================================================
// BEST PRACTICES FOR JUNIOR DEVELOPERS
// ========================================================================
/**
* 1. ALWAYS WRAP YOUR CODE IN TRY-CATCH:
* Prevents one error from breaking the entire test
*
* 2. USE waitForElement BEFORE DOM MANIPULATION:
* Elements might not exist when your code runs
*
* 3. TEST IN MULTIPLE BROWSERS:
* Chrome, Firefox, Safari, and mobile browsers
*
* 4. AVOID BLOCKING THE MAIN THREAD:
* Use debounce for expensive operations
*
* 5. CLEAN UP AFTER YOURSELF:
* Remove observers when no longer needed using the returned cleanup function
*
* 6. USE MEANINGFUL VARIABLE NAMES:
* Instead of 'btn', use 'checkoutButton'
*
* 7. COMMENT YOUR CODE:
* Explain WHY, not just WHAT
*
* 8. TEST THOROUGHLY:
* Check edge cases, slow connections, and different device sizes
*/
// ================================================================== The End ==================================================================
// =============================================================================================================================================