UNPKG

@snowplow/javascript-tracker

Version:
782 lines (732 loc) 23.4 kB
/* * Copyright (c) 2021 Snowplow Analytics Ltd, 2010 Anthon Pang * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import util from 'util'; import F from 'lodash/fp'; import { DockerWrapper, fetchResults, start, stop } from '../micro'; import { version } from '../../package.json'; const dumpLog = (log: Array<unknown>) => console.log(util.inspect(log, true, null, true)); const retrieveSchemaData = (schema: unknown) => F.compose(F.get('data'), F.find({ schema }), F.get('data')); const loadUrlAndWait = (url: string) => { browser.url(url); browser.waitUntil(() => $('#init').getText() === 'true', { timeout: 5000, timeoutMsg: 'expected init after 5s', }); browser.waitUntil(() => $('#secondInit').getText() === 'true', { timeout: 5000, timeoutMsg: 'expected init after 5s', }); }; const isMatchWithCallback = F.isMatchWith((lt, rt) => (F.isFunction(rt) ? rt(lt) : undefined)); const mobileContext = { schema: 'iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1', data: { osType: 'ubuntu', }, }; const geoContext = { schema: 'iglu:com.snowplowanalytics.snowplow/geolocation_context/jsonschema/1-1-0', data: { latitude: 40.0, longitude: 55.1, }, }; describe('Snowplow Micro integration', () => { let eventMethods = ['get', 'post', 'beacon']; let log: Array<unknown> = []; let docker: DockerWrapper; const logContains = (ev: unknown) => F.some(F.isMatch(ev as object), log); const logContainsFn = (ev: unknown) => F.some(isMatchWithCallback(ev as object), log); beforeAll(() => { browser.call(() => { return start().then((container) => { docker = container; }); }); browser.url('/index.html'); browser.setCookies({ name: 'container', value: docker.url }); loadUrlAndWait('/integration.html?eventMethod=get'); $('#bottomRight').scrollIntoView(); browser.pause(10000); // Time for requests to get written loadUrlAndWait('/integration.html?eventMethod=post'); $('#bottomRight').scrollIntoView(); browser.pause(6000); // Time for requests to get written loadUrlAndWait('/integration.html?eventMethod=beacon'); $('#bottomRight').scrollIntoView(); browser.pause(6000); // Time for requests to get written browser.call(() => fetchResults(docker.url).then((result) => { log = result; }) ); }); afterAll(() => { browser.call(() => { return stop(docker.container); }); }); eventMethods.forEach((method) => { it(`${method}: contains correct tracker version`, () => { expect( logContains({ rawEvent: { parameters: { tv: `js-${version}`, }, }, event: { app_id: `sp-${method}`, v_tracker: `js-${version}`, }, }) ).toBe(true); }); it(`${method}: contains page view`, () => { expect( logContains({ event: { event: 'page_view', platform: 'mob', app_id: `sp-${method}`, user_id: 'Malcolm', page_title: 'Integration test page', }, }) ).toBe(true); }); it(`${method}: contains page view with custom context in log`, () => { const expected = { event: { event: 'page_view', platform: 'mob', app_id: `sp-${method}`, user_id: 'Malcolm', page_title: 'My Title', contexts: { data: [ { schema: 'iglu:org.schema/WebPage/jsonschema/1-0-0', data: { keywords: ['tester'] }, }, ], }, }, }; expect(logContains(expected)).toBe(true); }); it(`${method}: contains nonnonexistent event types in log`, () => { expect( logContains({ event: 'ad', app_id: `sp-${method}`, }) ).toBe(false); }); it(`${method}: contains at least one ping in the expected interval`, () => { const gtZero = F.compose(F.negate(F.gt(0)), F.toNumber); expect( logContainsFn({ event: { event_name: 'page_ping', app_id: `sp-${method}`, pp_xoffset_max: gtZero, pp_yoffset_max: gtZero, }, }) ).toBe(true); }); it(`${method}: contains a structured event`, () => { expect( logContains({ event: { app_id: `sp-${method}`, event: 'struct', se_category: 'Mixes', se_action: 'Play', se_label: 'MRC/fabric-0503-mix', se_value: 0.0, }, }) ).toBe(true); }); it(`${method}: contains an self describing event with true timestamp`, () => { expect( logContains({ event: { app_id: `sp-${method}`, event: 'unstruct', unstruct_event: { data: { data: { bannerId: 'ASO01043' }, }, }, true_tstamp: '1970-01-18T02:23:21.868Z', }, }) ).toBe(true); }); it(`${method}: contains a transaction event`, () => { expect( logContains({ event: { app_id: `sp-${method}`, event: 'transaction', tr_orderid: 'order-123', tr_affiliation: 'acme', tr_total: 8000, tr_tax: 100, tr_shipping: 50, tr_city: 'pheonix', tr_state: 'arizona', tr_country: 'USA', tr_currency: 'JPY', }, }) ).toBe(true); }); it(`${method}: contains a transaction item event`, () => { expect( logContains({ event: { app_id: `sp-${method}`, event: 'transaction_item', ti_orderid: 'order-123', ti_sku: '1001', ti_name: 'Blue t-shirt', ti_category: 'clothing', ti_price: 2000, ti_quantity: 2, ti_currency: 'JPY', }, }) ).toBe(true); }); it(`${method}: contains an unhandled exception event`, () => { expect( logContains({ event: { app_id: `sp-${method}`, unstruct_event: { data: { schema: 'iglu:com.snowplowanalytics.snowplow/application_error/jsonschema/1-0-1', }, }, contexts: { data: [ { schema: 'iglu:org.schema/WebPage/jsonschema/1-0-0', data: { keywords: ['tester'] }, }, ], }, }, }) ).toBe(true); }); it(`${method}: shows that pageViewId is regenerated for each trackPageView`, () => { const pageViews = F.filter( (ev) => F.get('event.event', ev) === 'page_view' && F.get('event.name_tracker', ev) === 'sp', log ); const getWebPageId = F.compose( F.get('id'), retrieveSchemaData('iglu:com.snowplowanalytics.snowplow/web_page/jsonschema/1-0-0'), F.get('event.contexts') ); expect(F.size(F.groupBy(getWebPageId, pageViews))).toBeGreaterThanOrEqual(2); }); it(`${method}: contains a GDPR context`, () => { expect( logContains({ event: { app_id: `sp-${method}`, contexts: { data: [ { schema: 'iglu:com.snowplowanalytics.snowplow/gdpr/jsonschema/1-0-0', data: { basisForProcessing: 'consent', documentId: 'someId', documentVersion: '0.1.0', documentDescription: 'this document is a test', }, }, ], }, }, }) ).toBe(true); }); it(`${method}: has a GDPR context attached to all events`, () => { const withoutGdprContext = F.compose( F.negate(F.includes('iglu:com.snowplowanalytics.snowplow/gdpr/jsonschema/1-0-0')), F.get('contexts') ); const fromCfTracker = F.compose(F.eq('cf'), F.get('event.name_tracker')); const numberWithoutGdpr = F.size(F.filter((ev) => withoutGdprContext(ev) && fromCfTracker(ev), log)); expect(numberWithoutGdpr).toBe(0); }); it(`${method}: has global contexts attached to structured events`, () => { expect( logContains({ event: { app_id: `sp-${method}`, event: 'struct', contexts: { data: [geoContext, mobileContext], }, }, }) ).toBe(true); }); it(`${method}: contains an self describing event with global context from accept ruleset`, () => { expect( logContains({ event: { app_id: `sp-${method}`, event: 'unstruct', unstruct_event: { data: { schema: 'iglu:com.snowplowanalytics.snowplow/ad_impression/jsonschema/1-0-0', data: { bannerId: 'ASO01042', }, }, }, contexts: { data: [geoContext, mobileContext], }, }, }) ).toBe(true); }); it(`${method}: contains an self describing event missing global context from reject ruleset`, () => { expect( logContains({ event: { app_id: `sp-${method}`, event: 'unstruct', unstruct_event: { data: { schema: 'iglu:com.snowplowanalytics.snowplow/ad_impression/jsonschema/1-0-0', data: { bannerId: 'ASO01041', }, }, }, contexts: { data: [], }, }, }) ).toBe(true); }); it(`${method}: contains page view event with non-base64 encoded context payload`, () => { expect( logContains({ rawEvent: { parameters: { co: '{"schema":"iglu:com.snowplowanalytics.snowplow/contexts/jsonschema/1-0-0","data":[{"schema":"iglu:org.schema/WebPage/jsonschema/1-0-0","data":{"keywords":["tester"]}}]}', }, }, event: { event: 'page_view', platform: 'web', app_id: `no-b64-${method}`, page_title: 'My Title', contexts: { data: [ { schema: 'iglu:org.schema/WebPage/jsonschema/1-0-0', data: { keywords: ['tester'] }, }, ], }, }, }) ).toBe(true); }); it(`${method}: contains social interaction event`, () => { expect( logContains({ event: { event: 'unstruct', platform: 'mob', app_id: `sp-${method}`, user_id: 'Malcolm', unstruct_event: { data: { schema: 'iglu:com.snowplowanalytics.snowplow/social_interaction/jsonschema/1-0-0', data: { action: 'retweet', network: 'twitter', target: '1234' }, }, }, }, }) ).toBe(true); }); it(`${method}: contains ad impression event`, () => { expect( logContains({ event: { event: 'unstruct', platform: 'mob', app_id: `sp-${method}`, user_id: 'Malcolm', unstruct_event: { data: { schema: 'iglu:com.snowplowanalytics.snowplow/ad_impression/jsonschema/1-0-0', data: { impressionId: '67965967893', costModel: 'cpm', cost: 5.5, targetUrl: 'http://www.example.com', bannerId: '23', zoneId: '7', advertiserId: '201', campaignId: '12', }, }, }, }, }) ).toBe(true); }); it(`${method}: contains ad click event`, () => { expect( logContains({ event: { event: 'unstruct', platform: 'mob', app_id: `sp-${method}`, user_id: 'Malcolm', unstruct_event: { data: { schema: 'iglu:com.snowplowanalytics.snowplow/ad_click/jsonschema/1-0-0', data: { impressionId: '67965967893', clickId: '12243253', costModel: 'cpm', cost: 2.5, targetUrl: 'http://www.example.com', bannerId: '23', zoneId: '7', advertiserId: '201', campaignId: '12', }, }, }, }, }) ).toBe(true); }); it(`${method}: contains ad conversion event`, () => { expect( logContains({ event: { event: 'unstruct', platform: 'mob', app_id: `sp-${method}`, user_id: 'Malcolm', unstruct_event: { data: { schema: 'iglu:com.snowplowanalytics.snowplow/ad_conversion/jsonschema/1-0-0', data: { conversionId: '743560297', costModel: 'cpa', cost: 10, category: 'ecommerce', action: 'purchase', property: '', initialValue: 99, advertiserId: '201', campaignId: '12', }, }, }, }, }) ).toBe(true); }); it(`${method}: contains add to cart event`, () => { expect( logContains({ event: { event: 'unstruct', platform: 'mob', app_id: `sp-${method}`, user_id: 'Malcolm', unstruct_event: { data: { schema: 'iglu:com.snowplowanalytics.snowplow/add_to_cart/jsonschema/1-0-0', data: { sku: '000345', name: 'blue tie', category: 'clothing', unitPrice: 3.49, quantity: 2, currency: 'GBP', }, }, }, }, }) ).toBe(true); }); it(`${method}: contains remove from cart event`, () => { expect( logContains({ event: { event: 'unstruct', platform: 'mob', app_id: `sp-${method}`, user_id: 'Malcolm', unstruct_event: { data: { schema: 'iglu:com.snowplowanalytics.snowplow/remove_from_cart/jsonschema/1-0-0', data: { sku: '000345', name: 'blue tie', category: 'clothing', unitPrice: 3.49, quantity: 1, currency: 'GBP', }, }, }, }, }) ).toBe(true); }); it(`${method}: contains site search event`, () => { expect( logContains({ event: { event: 'unstruct', platform: 'mob', app_id: `sp-${method}`, user_id: 'Malcolm', unstruct_event: { data: { schema: 'iglu:com.snowplowanalytics.snowplow/site_search/jsonschema/1-0-0', data: { terms: ['unified', 'log'], filters: { category: 'books', 'sub-category': 'non-fiction' }, totalResults: 14, pageResults: 8, }, }, }, }, }) ).toBe(true); }); it(`${method}: contains timing event`, () => { expect( logContains({ event: { event: 'unstruct', platform: 'mob', app_id: `sp-${method}`, user_id: 'Malcolm', unstruct_event: { data: { schema: 'iglu:com.snowplowanalytics.snowplow/timing/jsonschema/1-0-0', data: { category: 'load', variable: 'map_loaded', timing: 50, label: 'Map loading time', }, }, }, }, }) ).toBe(true); }); it(`${method}: contains consent granted event`, () => { expect( logContains({ event: { event: 'unstruct', platform: 'mob', app_id: `sp-${method}`, user_id: 'Malcolm', contexts: { data: [ { schema: 'iglu:com.snowplowanalytics.snowplow/consent_document/jsonschema/1-0-0', data: { id: '1234', version: '5', name: 'consent_document', description: 'a document granting consent', }, }, ], }, unstruct_event: { data: { schema: 'iglu:com.snowplowanalytics.snowplow/consent_granted/jsonschema/1-0-0', data: { expiry: '2020-11-21T08:00:00.000Z', }, }, }, }, }) ).toBe(true); }); it(`${method}: contains consent withdrawn event`, () => { expect( logContains({ event: { event: 'unstruct', platform: 'mob', app_id: `sp-${method}`, user_id: 'Malcolm', contexts: { data: [ { schema: 'iglu:com.snowplowanalytics.snowplow/consent_document/jsonschema/1-0-0', data: { id: '1234', version: '5', name: 'consent_document', description: 'a document withdrawing consent', }, }, ], }, unstruct_event: { data: { schema: 'iglu:com.snowplowanalytics.snowplow/consent_withdrawn/jsonschema/1-0-0', data: { all: false, }, }, }, }, }) ).toBe(true); }); it(`${method}: contains custom error event`, () => { expect( logContains({ event: { event: 'unstruct', platform: 'mob', app_id: `sp-${method}`, user_id: 'Malcolm', unstruct_event: { data: { schema: 'iglu:com.snowplowanalytics.snowplow/application_error/jsonschema/1-0-1', data: { programmingLanguage: 'JAVASCRIPT', lineNumber: 237, lineColumn: 5, fileName: 'trackError.js', }, }, }, }, }) ).toBe(true); }); it(`${method}: contains enhanced ecommerce event`, () => { expect( logContains({ event: { event: 'unstruct', platform: 'mob', app_id: `sp-${method}`, user_id: 'Malcolm', contexts: { data: [ { schema: 'iglu:com.google.analytics.enhanced-ecommerce/actionFieldObject/jsonschema/1-0-0', data: { id: 'T12345', affiliation: 'Google Store - Online', revenue: 37.39, tax: 2.85, shipping: 5.34, coupon: 'WINTER2016', }, }, { schema: 'iglu:com.google.analytics.enhanced-ecommerce/impressionFieldObject/jsonschema/1-0-0', data: { id: 'P12345', name: 'Android Warhol T-Shirt', list: 'Search Results', brand: 'Google', category: 'Apparel/T-Shirts', variant: 'Black', position: 1, }, }, { schema: 'iglu:com.google.analytics.enhanced-ecommerce/productFieldObject/jsonschema/1-0-0', data: { id: 'P12345', name: 'Android Warhol T-Shirt', list: 'Search Results', brand: 'Google', category: 'Apparel/T-Shirts', variant: 'Black', quantity: 1, }, }, { schema: 'iglu:com.google.analytics.enhanced-ecommerce/promoFieldObject/jsonschema/1-0-0', data: { id: 'PROMO_1234', name: 'Summer Sale', creative: 'summer_banner2', position: 'banner_slot1', }, }, ], }, unstruct_event: { data: { schema: 'iglu:com.google.analytics.enhanced-ecommerce/action/jsonschema/1-0-0', data: { action: 'purchase', }, }, }, }, }) ).toBe(true); }); }); });