diff --git a/api-docs/docs/browser-tracker/browser-tracker.api.md b/api-docs/docs/browser-tracker/browser-tracker.api.md index 53f8939d5..2a40e49a3 100644 --- a/api-docs/docs/browser-tracker/browser-tracker.api.md +++ b/api-docs/docs/browser-tracker/browser-tracker.api.md @@ -541,6 +541,7 @@ export type TrackerConfiguration = { plugins?: Array; onSessionUpdateCallback?: (updatedSession: ClientSession) => void; preservePageViewIdForUrl?: PreservePageViewIdForUrl; + synchronousCookieWrite?: boolean; } & EmitterConfigurationBase & LocalStorageEventStoreConfigurationBase; // @public diff --git a/api-docs/docs/browser-tracker/markdown/browser-tracker.trackerconfiguration.md b/api-docs/docs/browser-tracker/markdown/browser-tracker.trackerconfiguration.md index c84696320..04b2fafda 100644 --- a/api-docs/docs/browser-tracker/markdown/browser-tracker.trackerconfiguration.md +++ b/api-docs/docs/browser-tracker/markdown/browser-tracker.trackerconfiguration.md @@ -30,6 +30,7 @@ type TrackerConfiguration = { plugins?: Array; onSessionUpdateCallback?: (updatedSession: ClientSession) => void; preservePageViewIdForUrl?: PreservePageViewIdForUrl; + synchronousCookieWrite?: boolean; } & EmitterConfigurationBase & LocalStorageEventStoreConfigurationBase; ``` diff --git a/common/changes/@snowplow/browser-tracker-core/issue-cookie_optimization_2024-09-03-12-13.json b/common/changes/@snowplow/browser-tracker-core/issue-cookie_optimization_2024-09-03-12-13.json new file mode 100644 index 000000000..b56ad4f64 --- /dev/null +++ b/common/changes/@snowplow/browser-tracker-core/issue-cookie_optimization_2024-09-03-12-13.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@snowplow/browser-tracker-core", + "comment": "Make cookie writes async by default to improve tracker performance (#1340)", + "type": "none" + } + ], + "packageName": "@snowplow/browser-tracker-core" +} \ No newline at end of file diff --git a/common/changes/@snowplow/browser-tracker/issue-cookie_optimization_2024-09-03-12-13.json b/common/changes/@snowplow/browser-tracker/issue-cookie_optimization_2024-09-03-12-13.json new file mode 100644 index 000000000..8e2bf89d1 --- /dev/null +++ b/common/changes/@snowplow/browser-tracker/issue-cookie_optimization_2024-09-03-12-13.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@snowplow/browser-tracker", + "comment": "Make cookie writes async by default to improve tracker performance (#1340)", + "type": "none" + } + ], + "packageName": "@snowplow/browser-tracker" +} \ No newline at end of file diff --git a/common/changes/@snowplow/javascript-tracker/issue-cookie_optimization_2024-09-03-12-13.json b/common/changes/@snowplow/javascript-tracker/issue-cookie_optimization_2024-09-03-12-13.json new file mode 100644 index 000000000..c8a4588c9 --- /dev/null +++ b/common/changes/@snowplow/javascript-tracker/issue-cookie_optimization_2024-09-03-12-13.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@snowplow/javascript-tracker", + "comment": "Make cookie writes async by default to improve tracker performance (#1340)", + "type": "none" + } + ], + "packageName": "@snowplow/javascript-tracker" +} \ No newline at end of file diff --git a/libraries/browser-tracker-core/src/helpers/index.ts b/libraries/browser-tracker-core/src/helpers/index.ts index 6c93b2b07..b21da64df 100755 --- a/libraries/browser-tracker-core/src/helpers/index.ts +++ b/libraries/browser-tracker-core/src/helpers/index.ts @@ -254,10 +254,10 @@ export function findRootDomain(sameSite: string, secure: boolean) { cookie(cookieName, cookieValue, 0, '/', currentDomain, sameSite, secure); if (cookie(cookieName) === cookieValue) { // Clean up created cookie(s) - deleteCookie(cookieName, currentDomain, sameSite, secure); + deleteCookie(cookieName, '/', currentDomain, sameSite, secure); const cookieNames = getCookiesWithPrefix(cookiePrefix); for (let i = 0; i < cookieNames.length; i++) { - deleteCookie(cookieNames[i], currentDomain, sameSite, secure); + deleteCookie(cookieNames[i], '/', currentDomain, sameSite, secure); } return currentDomain; @@ -290,8 +290,8 @@ export function isValueInArray(val: T, array: T[]) { * @param cookieName - The name of the cookie to delete * @param domainName - The domain the cookie is in */ -export function deleteCookie(cookieName: string, domainName?: string, sameSite?: string, secure?: boolean) { - cookie(cookieName, '', -1, '/', domainName, sameSite, secure); +export function deleteCookie(cookieName: string, path?: string, domainName?: string, sameSite?: string, secure?: boolean) { + cookie(cookieName, '', -1, path, domainName, sameSite, secure); } /** diff --git a/libraries/browser-tracker-core/src/snowplow.ts b/libraries/browser-tracker-core/src/snowplow.ts index 955574b21..3161f50e8 100644 --- a/libraries/browser-tracker-core/src/snowplow.ts +++ b/libraries/browser-tracker-core/src/snowplow.ts @@ -32,6 +32,7 @@ import { LOG } from '@snowplow/tracker-core'; import { SharedState } from './state'; import { Tracker } from './tracker'; import { BrowserTracker, TrackerConfiguration } from './tracker/types'; +import { asyncCookieStorage } from './tracker/cookie_storage'; const namedTrackers: Record = {}; @@ -151,3 +152,12 @@ function getTrackersFromCollection( } return trackers; } + +/** + * Write all pending cookies to the browser. + * Useful if you track events just before the page is unloaded. + * This call is not necessary if `synchronousCookieWrite` is set to `true`. + */ +export function flushPendingCookies() { + asyncCookieStorage.flush(); +} diff --git a/libraries/browser-tracker-core/src/tracker/cookie_storage.ts b/libraries/browser-tracker-core/src/tracker/cookie_storage.ts new file mode 100644 index 000000000..b7b2dfd07 --- /dev/null +++ b/libraries/browser-tracker-core/src/tracker/cookie_storage.ts @@ -0,0 +1,207 @@ +import { cookie, deleteCookie } from '../helpers'; + + +/** + * Cookie storage interface for reading and writing cookies. + */ +export interface CookieStorage { + /** + * Get the value of a cookie + * + * @param name - The name of the cookie + * @returns The cookie value + */ + getCookie(name: string): string; + + /** + * Set a cookie + * + * @param name - The cookie name (required) + * @param value - The cookie value + * @param ttl - The cookie Time To Live (seconds) + * @param path - The cookies path + * @param domain - The cookies domain + * @param samesite - The cookies samesite attribute + * @param secure - Boolean to specify if cookie should be secure + * @returns true if the cookie was set, false otherwise + */ + setCookie( + name: string, + value?: string, + ttl?: number, + path?: string, + domain?: string, + samesite?: string, + secure?: boolean + ): boolean; + + /** + * Delete a cookie + * + * @param name - The cookie name + * @param domainName - The cookie domain name + * @param sameSite - The cookie same site attribute + * @param secure - Boolean to specify if cookie should be secure + */ + deleteCookie(name: string, path?: string, domainName?: string, sameSite?: string, secure?: boolean): void; +} + +export interface AsyncCookieStorage extends CookieStorage { + /** + * Clear the cookie storage cache (does not delete any cookies) + */ + clearCache(): void; + + /** + * Write all pending cookies. + */ + flush(): void; +} + +interface Cookie { + getValue: () => string; + setValue: (value?: string, ttl?: number, path?: string, domain?: string, samesite?: string, secure?: boolean) => boolean; + deleteValue: (path?: string, domainName?: string, sameSite?: string, secure?: boolean) => void; + flush: () => void; +} + +function newCookie(name: string): Cookie { + let flushTimer: ReturnType | undefined; + let lastSetValueArgs: Parameters | undefined; + let cacheExpireAt: Date | undefined; + let flushed = true; + const flushTimeout = 10; // milliseconds + const maxCacheTtl = 0.05; // seconds + + function getValue(): string { + // Note: we can't cache the cookie value as we don't know the expiration date + if (lastSetValueArgs && (!cacheExpireAt || cacheExpireAt > new Date())) { + return lastSetValueArgs[0] ?? cookie(name); + } + return cookie(name); + } + + function setValue(value?: string, ttl?: number, path?: string, domain?: string, samesite?: string, secure?: boolean): boolean { + lastSetValueArgs = [value, ttl, path, domain, samesite, secure]; + flushed = false; + + // throttle setting the cookie + if (flushTimer === undefined) { + flushTimer = setTimeout(() => { + flushTimer = undefined; + flush(); + }, flushTimeout); + } + + cacheExpireAt = new Date(Date.now() + Math.min(maxCacheTtl, ttl ?? maxCacheTtl) * 1000); + return true; + } + + function deleteValue(path?: string, domainName?: string, sameSite?: string, secure?: boolean): void { + lastSetValueArgs = undefined; + flushed = true; + + // cancel setting the cookie + if (flushTimer !== undefined) { + clearTimeout(flushTimer); + flushTimer = undefined; + } + + deleteCookie(name, path, domainName, sameSite, secure); + } + + function flush(): void { + if (flushTimer !== undefined) { + clearTimeout(flushTimer); + flushTimer = undefined; + } + + if (flushed) { + return; + } + flushed = true; + + if (lastSetValueArgs !== undefined) { + const [value, ttl, path, domain, samesite, secure] = lastSetValueArgs; + cookie(name, value, ttl, path, domain, samesite, secure); + } + } + + return { + getValue, + setValue, + deleteValue, + flush, + }; +} + +/** + * Create a new async cookie storage + * + * @returns A new cookie storage + */ +export function newCookieStorage(): AsyncCookieStorage { + let cache: Record = {}; + + function getOrInitCookie(name: string): Cookie { + if (!cache[name]) { + cache[name] = newCookie(name); + } + return cache[name]; + } + + function getCookie(name: string): string { + return getOrInitCookie(name).getValue(); + } + + function setCookie( + name: string, + value?: string, + ttl?: number, + path?: string, + domain?: string, + samesite?: string, + secure?: boolean + ): boolean { + return getOrInitCookie(name).setValue(value, ttl, path, domain, samesite, secure); + } + + function deleteCookie(name: string, path?: string, domainName?: string, sameSite?: string, secure?: boolean): void { + getOrInitCookie(name).deleteValue(path, domainName, sameSite, secure); + } + + function clearCache(): void { + cache = {}; + } + + function flush(): void { + for (const cookie of Object.values(cache)) { + cookie.flush(); + } + } + + return { + getCookie, + setCookie, + deleteCookie, + clearCache, + flush, + }; +} + +/** + * Cookie storage instance with asynchronous cookie writes + */ +export const asyncCookieStorage = newCookieStorage(); + +/** + * Cookie storage instance with synchronous cookie writes + */ +export const syncCookieStorage: CookieStorage = { + getCookie: cookie, + setCookie: (name, value, ttl, path, domain, samesite, secure) => { + cookie(name, value, ttl, path, domain, samesite, secure); + return document.cookie.indexOf(`${name}=`) !== -1; + }, + deleteCookie +}; diff --git a/libraries/browser-tracker-core/src/tracker/index.ts b/libraries/browser-tracker-core/src/tracker/index.ts index e4b93a322..8787d5146 100755 --- a/libraries/browser-tracker-core/src/tracker/index.ts +++ b/libraries/browser-tracker-core/src/tracker/index.ts @@ -16,11 +16,9 @@ import { getReferrer, addEventListener, getHostName, - cookie, attemptGetLocalStorage, attemptWriteLocalStorage, attemptDeleteLocalStorage, - deleteCookie, fixupTitle, fromQuerystring, isInteger, @@ -69,6 +67,7 @@ import { } from './id_cookie'; import { CLIENT_SESSION_SCHEMA, WEB_PAGE_SCHEMA, BROWSER_CONTEXT_SCHEMA } from './schemata'; import { getBrowserProperties } from '../helpers/browser_props'; +import { asyncCookieStorage, syncCookieStorage } from './cookie_storage'; declare global { interface Navigator { @@ -172,6 +171,9 @@ export function Tracker( }; }; + // Create a new cookie storage instance with synchronous cookie write if configured + const cookieStorage = trackerConfiguration.synchronousCookieWrite ? syncCookieStorage : asyncCookieStorage; + // Get all injected plugins browserPlugins.push(getBrowserDataPlugin()); /* When including the Web Page context, we add the relevant internal plugins */ @@ -479,7 +481,7 @@ export function Tracker( if (configStateStorageStrategy == 'localStorage') { return attemptGetLocalStorage(fullName); } else if (configStateStorageStrategy == 'cookie' || configStateStorageStrategy == 'cookieAndLocalStorage') { - return cookie(fullName); + return cookieStorage.getCookie(fullName); } return undefined; } @@ -606,8 +608,7 @@ export function Tracker( if (configStateStorageStrategy == 'localStorage') { return attemptWriteLocalStorage(name, value, timeout); } else if (configStateStorageStrategy == 'cookie' || configStateStorageStrategy == 'cookieAndLocalStorage') { - cookie(name, value, timeout, configCookiePath, configCookieDomain, configCookieSameSite, configCookieSecure); - return document.cookie.indexOf(`${name}=`) !== -1 ? true : false; + return cookieStorage.setCookie(name, value, timeout, configCookiePath, configCookieDomain, configCookieSameSite, configCookieSecure); } return false; } @@ -620,8 +621,8 @@ export function Tracker( const sesname = getSnowplowCookieName('ses'); attemptDeleteLocalStorage(idname); attemptDeleteLocalStorage(sesname); - deleteCookie(idname, configCookieDomain, configCookieSameSite, configCookieSecure); - deleteCookie(sesname, configCookieDomain, configCookieSameSite, configCookieSecure); + cookieStorage.deleteCookie(idname, configCookiePath, configCookieDomain, configCookieSameSite, configCookieSecure); + cookieStorage.deleteCookie(sesname, configCookiePath, configCookieDomain, configCookieSameSite, configCookieSecure); if (!configuration?.preserveSession) { memorizedSessionId = uuid(); memorizedVisitCount = 1; @@ -832,7 +833,7 @@ export function Tracker( const isFirstEventInSession = eventIndexFromIdCookie(idCookie) === 0; if (configOptOutCookie) { - toOptoutByCookie = !!cookie(configOptOutCookie); + toOptoutByCookie = !!cookieStorage.getCookie(configOptOutCookie); } else { toOptoutByCookie = false; } @@ -1299,7 +1300,7 @@ export function Tracker( }, setUserIdFromCookie: function (cookieName: string) { - businessUserId = cookie(cookieName); + businessUserId = cookieStorage.getCookie(cookieName); }, setCollectorUrl: function (collectorUrl: string) { diff --git a/libraries/browser-tracker-core/src/tracker/types.ts b/libraries/browser-tracker-core/src/tracker/types.ts index 4d34b9a72..5bbfae2ba 100755 --- a/libraries/browser-tracker-core/src/tracker/types.ts +++ b/libraries/browser-tracker-core/src/tracker/types.ts @@ -190,6 +190,15 @@ export type TrackerConfiguration = { * Defaults to `false`. */ preservePageViewIdForUrl?: PreservePageViewIdForUrl; + + /** + * Whether to write the cookies synchronously. + * This can be useful for testing purposes to ensure that the cookies are written before the test continues. + * It also has the benefit of making sure that the cookie is correctly set before session information is used in events. + * The downside is that it is slower and blocks the main thread. + * @defaultValue false + */ + synchronousCookieWrite?: boolean; } & EmitterConfigurationBase & LocalStorageEventStoreConfigurationBase; diff --git a/libraries/browser-tracker-core/test/helpers/index.ts b/libraries/browser-tracker-core/test/helpers/index.ts index 04dc78825..cff0d8bfb 100644 --- a/libraries/browser-tracker-core/test/helpers/index.ts +++ b/libraries/browser-tracker-core/test/helpers/index.ts @@ -77,5 +77,6 @@ export function createTestSessionIdCookie(params?: CreateTestSessionIdCookie) { export function createTracker(configuration?: TrackerConfiguration, sharedState?: SharedState) { let id = 'sp-' + Math.random(); + configuration = { ...configuration, synchronousCookieWrite: true }; return addTracker(id, id, '', '', sharedState ?? new SharedState(), configuration); } diff --git a/libraries/browser-tracker-core/test/tracker/cookie_storage.test.ts b/libraries/browser-tracker-core/test/tracker/cookie_storage.test.ts new file mode 100644 index 000000000..ce48e9bee --- /dev/null +++ b/libraries/browser-tracker-core/test/tracker/cookie_storage.test.ts @@ -0,0 +1,69 @@ +import { asyncCookieStorage, newCookieStorage, syncCookieStorage } from "../../src/tracker/cookie_storage"; + +test("cookieStorage sets, gets, and deletes value", () => { + const cookieStorage = newCookieStorage(); + cookieStorage.setCookie("test", "value"); + expect(cookieStorage.getCookie("test")).toBe("value"); + + cookieStorage.deleteCookie("test"); + expect(cookieStorage.getCookie("test")).toBeFalsy(); +}); + +test("cookieStorage sets value with ttl and clears cache after ttl", (done) => { + const cookieStorage = newCookieStorage(); + const ttl = 1; + cookieStorage.setCookie("test", "value", ttl); + + expect(cookieStorage.getCookie("test")).toBe("value"); + + setTimeout(() => { + expect(cookieStorage.getCookie("test")).toBeFalsy(); + done(); + }, ttl * 1000 + 100); +}); + +test("cookieStorage sets value with path, domain, samesite, and secure", () => { + const cookieStorage = newCookieStorage(); + const path = "/"; + const domain = "example.com"; + const samesite = "Strict"; + const secure = true; + + cookieStorage.setCookie("test", "value", undefined, path, domain, samesite, secure); + expect(cookieStorage.getCookie("test")).toBe("value"); +}); + +test("cookieStorage sets value with synchronous cookie write", () => { + const cookieStorage = syncCookieStorage; + cookieStorage.setCookie("test", "value"); + expect(cookieStorage.getCookie("test")).toBe("value"); +}); + +test("asyncCookieStorage flushes pending cookies", () => { + let cookieJar = ''; + + jest.spyOn(document, 'cookie', 'set').mockImplementation((cookieValue) => { + cookieJar = cookieValue; + }); + + asyncCookieStorage.setCookie("test", "value"); + expect(cookieJar).toBe(""); + asyncCookieStorage.flush(); + expect(cookieJar).toBe("test=value"); +}); + +test('writes the latest cookie value', (done) => { + let cookieJar = ''; + + jest.spyOn(document, 'cookie', 'set').mockImplementation((cookieValue) => { + cookieJar = cookieValue; + }); + + for (let i = 0; i < 100; i++) { + asyncCookieStorage.setCookie("test", `value${i}`); + } + setTimeout(() => { + expect(cookieJar).toBe('test=value99'); + done(); + }, 100); +}); diff --git a/plugins/browser-plugin-form-tracking/src/helpers.ts b/plugins/browser-plugin-form-tracking/src/helpers.ts index 8364f1780..c6af6113b 100644 --- a/plugins/browser-plugin-form-tracking/src/helpers.ts +++ b/plugins/browser-plugin-form-tracking/src/helpers.ts @@ -1,5 +1,6 @@ import { addEventListener, + flushPendingCookies, getCssClasses, getFilterByClass, getFilterByName, @@ -423,6 +424,7 @@ function getFormSubmissionListener( }), resolveDynamicContext(context, target, elementsData) ); + flushPendingCookies(); } }; } diff --git a/plugins/browser-plugin-link-click-tracking/src/index.ts b/plugins/browser-plugin-link-click-tracking/src/index.ts index 873122a16..076c681a4 100644 --- a/plugins/browser-plugin-link-click-tracking/src/index.ts +++ b/plugins/browser-plugin-link-click-tracking/src/index.ts @@ -34,6 +34,7 @@ import { getCssClasses, getFilterByClass, getHostName, + flushPendingCookies, type BrowserPlugin, type BrowserTracker, type FilterCriterion, @@ -183,6 +184,8 @@ export function trackLinkClick( } if (payload) t.core.track(payload, event.context, event.timestamp); }); + + flushPendingCookies(); } /** diff --git a/plugins/browser-plugin-media/test/api.test.ts b/plugins/browser-plugin-media/test/api.test.ts index 68803bb12..1c9910256 100644 --- a/plugins/browser-plugin-media/test/api.test.ts +++ b/plugins/browser-plugin-media/test/api.test.ts @@ -44,11 +44,12 @@ describe('Media Tracking API', () => { addTracker(`sp${idx++}`, `sp${idx++}`, 'js-3.9.0', '', new SharedState(), { stateStorageStrategy: 'cookie', encodeBase64: false, + synchronousCookieWrite: true, plugins: [ SnowplowMediaPlugin(), { beforeTrack: (pb: PayloadBuilder) => { - const { ue_pr, co, tna } = pb.getPayload(); + const { ue_pr, co, tna } = pb.build(); if (tna == `sp${idx - 1}`) { eventQueue.push({ event: JSON.parse(ue_pr as string).data, context: JSON.parse(co as string).data }); } diff --git a/trackers/javascript-tracker/test/pages/track_performance.html b/trackers/javascript-tracker/test/pages/track_performance.html new file mode 100644 index 000000000..50d89af3f --- /dev/null +++ b/trackers/javascript-tracker/test/pages/track_performance.html @@ -0,0 +1,98 @@ + + + + + Track performance test page + + + +

Page for sending requests to Snowplow Micro

+
+ + + + + + + diff --git a/trackers/javascript-tracker/test/performance/track_performance.test.ts b/trackers/javascript-tracker/test/performance/track_performance.test.ts new file mode 100755 index 000000000..5e83ac356 --- /dev/null +++ b/trackers/javascript-tracker/test/performance/track_performance.test.ts @@ -0,0 +1,61 @@ +import { Capabilities } from "@wdio/types"; + +const loadUrlAndWait = async (url: string) => { + await browser.url(url); + await browser.pause(5000); + await browser.waitUntil(async () => (await $('#init').getText()) === 'true', { + timeout: 20000, + timeoutMsg: 'expected init after 20s', + }); +}; + +const shouldSkipBrowser = (browser: any) => { + const capabilities = browser.capabilities as Capabilities.DesiredCapabilities; + const browserName = capabilities.browserName?.toLowerCase(); + return browserName === 'firefox' || browserName === 'safari'; +}; + +describe('Performance of tracking', () => { + if (shouldSkipBrowser(browser)) { + fit('Skip browser', () => { }); + return; + } + + let noneMeasure: PerformanceMeasure | undefined; + let cookieMeasure: PerformanceMeasure | undefined; + let cookieAndLocalStorageMeasure: PerformanceMeasure | undefined; + let cookieAndLocalStorageSyncMeasure: PerformanceMeasure | undefined; + + beforeAll(async () => { + await loadUrlAndWait('/track_performance.html?stateStorageStrategy=none'); + noneMeasure = await browser.execute(() => performance.measure('none', 'start', 'end')); + + await loadUrlAndWait('/track_performance.html?stateStorageStrategy=cookie'); + cookieMeasure = await browser.execute(() => performance.measure('cookie', 'start', 'end')); + + await loadUrlAndWait('/track_performance.html?stateStorageStrategy=cookieAndLocalStorage'); + cookieAndLocalStorageMeasure = await browser.execute(() => performance.measure('cookieAndLocalStorage', 'start', 'end')); + + await loadUrlAndWait('/track_performance.html?stateStorageStrategy=cookieAndLocalStorage&synchronousCookieWrite=true'); + cookieAndLocalStorageSyncMeasure = await browser.execute(() => performance.measure('cookieAndLocalStorageSync', 'start', 'end')); + + console.log('state storage strategy: none', noneMeasure?.duration); + console.log('state storage strategy: cookie', cookieMeasure?.duration); + console.log('state storage strategy: cookieAndLocalStorage', cookieAndLocalStorageMeasure?.duration); + console.log('state storage strategy: cookieAndLocalStorageSync', cookieAndLocalStorageSyncMeasure?.duration); + }); + + it('should have a performance log', () => { + expect(noneMeasure).toBeDefined(); + expect(cookieMeasure).toBeDefined(); + expect(cookieAndLocalStorageMeasure).toBeDefined(); + expect(cookieAndLocalStorageSyncMeasure).toBeDefined(); + }); + + it('should have a performance log with a duration', () => { + expect(noneMeasure?.duration).toBeLessThan(1000); + expect(cookieMeasure?.duration).toBeLessThan((noneMeasure?.duration ?? 0) * 2); + expect(cookieAndLocalStorageMeasure?.duration).toBeLessThan((cookieMeasure?.duration ?? 0) * 2); + expect(cookieAndLocalStorageSyncMeasure?.duration).toBeLessThan((cookieAndLocalStorageMeasure?.duration ?? 0) * 10); + }); +}); diff --git a/trackers/javascript-tracker/test/wdio.default.conf.ts b/trackers/javascript-tracker/test/wdio.default.conf.ts index 4c20f4dfa..af5a1a8ee 100644 --- a/trackers/javascript-tracker/test/wdio.default.conf.ts +++ b/trackers/javascript-tracker/test/wdio.default.conf.ts @@ -13,6 +13,7 @@ export const config: Omit = { getFullPath('test/functional/*.test.ts'), getFullPath('test/integration/*.test.ts'), getFullPath('test/media/media.test.ts'), + getFullPath('test/performance/*.test.ts'), // YouTube and Vimeo tests are disabled since they block SauceLabs on CI ]], logLevel: 'warn',