diff --git a/package-lock.json b/package-lock.json index bb6095050..b82e8de45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -437,9 +437,9 @@ "dev": true }, "@splitsoftware/splitio-commons": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-1.5.0.tgz", - "integrity": "sha512-7vBv4Yn+EKaAxJ+9RdKOqJNee+izj6BieG0ZmivWk8fTpKFpcvqKes/Y0kXk0tfOiMmUFV9dRr2xDHRYIf0lnw==", + "version": "1.5.1-rc.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-1.5.1-rc.0.tgz", + "integrity": "sha512-4XgahrK5lS9pyuNoYaxNfkPIPEWk+SX38UVf2/xyL/roRpwEnI5VyGaVvKZvRzfIsFndkNgHSp+78uM/zCRBSg==", "requires": { "tslib": "^2.3.1" } @@ -4037,7 +4037,7 @@ "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" }, "lodash.flatten": { "version": "4.4.0", @@ -4053,7 +4053,7 @@ "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" }, "lodash.isequal": { "version": "4.5.0", diff --git a/package.json b/package.json index 815066319..7d7cf2bfa 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "node": ">=6" }, "dependencies": { - "@splitsoftware/splitio-commons": "1.5.0", + "@splitsoftware/splitio-commons": "1.5.1-rc.0", "@types/google.analytics": "0.0.40", "@types/ioredis": "^4.28.0", "ioredis": "^4.28.0", diff --git a/src/__tests__/browser.spec.js b/src/__tests__/browser.spec.js index 254ff0fc7..73a2b2a32 100644 --- a/src/__tests__/browser.spec.js +++ b/src/__tests__/browser.spec.js @@ -122,9 +122,9 @@ tape('## E2E CI Tests ##', function(assert) { assert.test('E2E / Readiness', readinessSuite.bind(null, fetchMock)); /* Validate headers for ip and hostname are not sended with requests (ignore setting IPAddressesEnabled) */ assert.test('E2E / Ignore setting IPAddressesEnabled', ignoreIpAddressesSettingSuite.bind(null, fetchMock)); - /* Check that impressions and events are sended to backend via Beacon API or Fetch when page unload is triggered. */ - assert.test('E2E / Use Beacon API (or Fetch if not available) to send remaining impressions and events when browser page is unload', useBeaconApiSuite.bind(null, fetchMock)); - assert.test('E2E / Use Beacon API DEBUG (or Fetch if not available) to send remaining impressions and events when browser page is unload', useBeaconDebugApiSuite.bind(null, fetchMock)); + /* Check that impressions and events are sended to backend via Beacon API or Fetch when pagehide/visibilitychange events are triggered. */ + assert.test('E2E / Use Beacon API (or Fetch if not available) to send remaining impressions and events when browser page is unload or hidden', useBeaconApiSuite.bind(null, fetchMock)); + assert.test('E2E / Use Beacon API DEBUG (or Fetch if not available) to send remaining impressions and events when browser page is unload or hidden', useBeaconDebugApiSuite.bind(null, fetchMock)); /* Validate ready from cache behaviour (might be merged into another suite if we end up having simple behavior around it as expected) */ assert.test('E2E / Readiness from cache', readyFromCache.bind(null, fetchMock)); /* Validate readiness with ready promises */ diff --git a/src/__tests__/browserSuites/use-beacon-api.debug.spec.js b/src/__tests__/browserSuites/use-beacon-api.debug.spec.js index 4615f1cd0..9d604b494 100644 --- a/src/__tests__/browserSuites/use-beacon-api.debug.spec.js +++ b/src/__tests__/browserSuites/use-beacon-api.debug.spec.js @@ -5,7 +5,7 @@ import splitChangesMock1 from '../mocks/splitchanges.since.-1.json'; import mySegmentsFacundo from '../mocks/mysegments.facundo@split.io.json'; import { DEBUG } from '@splitsoftware/splitio-commons/src/utils/constants'; import { url } from '../testUtils'; -import { triggerUnloadEvent } from '../testUtils/browser'; +import { triggerPagehideEvent, triggerVisibilitychange } from '../testUtils/browser'; const config = { core: { @@ -61,7 +61,7 @@ const assertCallsToBeaconAPI = (assert) => { assertEventSent(assert, parsedPayload.entries[0]); }; -// This E2E test checks that Beacon API is not called when page unload is triggered and there are not events or impressions to send. +// This E2E test checks that Beacon API is not called when page is hidden and there are not events or impressions to send. function beaconApiNotSendTestDebug(fetchMock, assert) { sendBeaconSpyDebug = sinon.spy(window.navigator, 'sendBeacon'); @@ -75,8 +75,9 @@ function beaconApiNotSendTestDebug(fetchMock, assert) { const client = splitio.client(); client.on(client.Event.SDK_READY, () => { - // trigger unload event, without tracked events and impressions - triggerUnloadEvent(); + // trigger events, without tracked events and impressions + triggerPagehideEvent(); + triggerVisibilitychange(); // destroy the client and execute the next E2E test named beaconApiSendTest setTimeout(() => { @@ -90,7 +91,7 @@ function beaconApiNotSendTestDebug(fetchMock, assert) { }); } -// This E2E test checks that impressions and events are sent to backend via Beacon API when page unload is triggered. +// This E2E test checks that impressions and events are sent to backend via Beacon API when page is hidden. function beaconApiSendTestDebug(fetchMock, assert) { // Init and run Split client @@ -100,8 +101,8 @@ function beaconApiSendTestDebug(fetchMock, assert) { client.getTreatment('hierarchical_splits_test'); client.track('sometraffictype', 'someEvent', 10); - // trigger unload event inmmediatly, before scheduled push of events and impressions - triggerUnloadEvent(); + // trigger visibilitychange event inmmediatly, before scheduled push of events and impressions + triggerVisibilitychange(); // queue the assertion of the Beacon requests, destroy the client and execute the next E2E test named fallbackTest setTimeout(() => { @@ -115,7 +116,7 @@ function beaconApiSendTestDebug(fetchMock, assert) { }); } -// This E2E test checks that impressions and events are sent to backend via Axios when page unload is triggered and Beacon API is not available. +// This E2E test checks that impressions and events are sent to backend via Fetch API when page is hidden and Beacon API is not available. function fallbackTest(fetchMock, assert) { // destroy reference to Beacon API @@ -136,7 +137,7 @@ function fallbackTest(fetchMock, assert) { }, 0); })(); - // Mock endpoints used by Axios + // Mock endpoints fetchMock.postOnce(url(settings, '/testImpressions/bulk'), (url, opts) => { const resp = JSON.parse(opts.body); assert.ok(opts, 'Fallback to /testImpressions/bulk'); @@ -155,8 +156,8 @@ function fallbackTest(fetchMock, assert) { client.on(client.Event.SDK_READY, () => { client.getTreatment('hierarchical_splits_test'); client.track('sometraffictype', 'someEvent', 10); - // trigger unload event inmmediatly, before scheduled push of events and impressions - triggerUnloadEvent(); + // trigger pagehide event inmmediatly, before scheduled push of events and impressions + triggerPagehideEvent(); }); } diff --git a/src/__tests__/browserSuites/use-beacon-api.spec.js b/src/__tests__/browserSuites/use-beacon-api.spec.js index f6c6505fa..d36ceaf45 100644 --- a/src/__tests__/browserSuites/use-beacon-api.spec.js +++ b/src/__tests__/browserSuites/use-beacon-api.spec.js @@ -5,7 +5,7 @@ import splitChangesMock1 from '../mocks/splitchanges.since.-1.json'; import mySegmentsFacundo from '../mocks/mysegments.facundo@split.io.json'; import { url } from '../testUtils'; import { OPTIMIZED } from '@splitsoftware/splitio-commons/src/utils/constants'; -import { triggerUnloadEvent } from '../testUtils/browser'; +import { triggerPagehideEvent, triggerVisibilitychange } from '../testUtils/browser'; const config = { core: { @@ -73,7 +73,7 @@ const assertCallsToBeaconAPI = (assert) => { assertImpressionsCountSent(assert, parsedPayload.entries); }; -// This E2E test checks that Beacon API is not called when page unload is triggered and there are not events or impressions to send. +// This E2E test checks that Beacon API is not called when page is hidden and there are not events or impressions to send. function beaconApiNotSendTest(fetchMock, assert) { sendBeaconSpy = sinon.spy(window.navigator, 'sendBeacon'); @@ -87,8 +87,9 @@ function beaconApiNotSendTest(fetchMock, assert) { const client = splitio.client(); client.on(client.Event.SDK_READY, () => { - // trigger unload event, without tracked events and impressions - triggerUnloadEvent(); + // trigger events, without tracked events and impressions + triggerPagehideEvent(); + triggerVisibilitychange(); // destroy the client and execute the next E2E test named beaconApiSendTest setTimeout(() => { @@ -102,7 +103,7 @@ function beaconApiNotSendTest(fetchMock, assert) { }); } -// This E2E test checks that impressions and events are sent to backend via Beacon API when page unload is triggered. +// This E2E test checks that impressions and events are sent to backend via Beacon API when page is hidden. function beaconApiSendTest(fetchMock, assert) { // Init and run Split client @@ -112,8 +113,9 @@ function beaconApiSendTest(fetchMock, assert) { client.getTreatment('hierarchical_splits_test'); client.track('sometraffictype', 'someEvent', 10); - // trigger unload event inmmediatly, before scheduled push of events and impressions - triggerUnloadEvent(); + // trigger both events inmmediatly, before scheduled push of events and impressions, to assert that beacon requests are not duplicated + triggerPagehideEvent(); + triggerVisibilitychange(); // queue the assertion of the Beacon requests, destroy the client and execute the next E2E test named fallbackTest setTimeout(() => { @@ -127,7 +129,7 @@ function beaconApiSendTest(fetchMock, assert) { }); } -// This E2E test checks that impressions and events are sent to backend via Axios when page unload is triggered and Beacon API is not available. +// This E2E test checks that impressions and events are sent to backend via Fetch API when page is hidden and Beacon API is not available. function fallbackTest(fetchMock, assert) { // destroy reference to Beacon API @@ -149,7 +151,7 @@ function fallbackTest(fetchMock, assert) { }, 0); })(); - // Mock endpoints used by Axios + // Mock endpoints fetchMock.postOnce(url(settings, '/testImpressions/bulk'), (url, opts) => { const resp = JSON.parse(opts.body); assert.ok(opts, 'Fallback to /testImpressions/bulk'); @@ -175,8 +177,9 @@ function fallbackTest(fetchMock, assert) { client.on(client.Event.SDK_READY, () => { client.getTreatment('hierarchical_splits_test'); client.track('sometraffictype', 'someEvent', 10); - // trigger unload event inmmediatly, before scheduled push of events and impressions - triggerUnloadEvent(); + // trigger both events inmmediatly, before scheduled push of events and impressions, to assert that POST requests are not duplicated + triggerPagehideEvent(); + triggerVisibilitychange(); }); } diff --git a/src/__tests__/browserSuites/user-consent.spec.js b/src/__tests__/browserSuites/user-consent.spec.js index 32ecd90c4..7dd3cad9e 100644 --- a/src/__tests__/browserSuites/user-consent.spec.js +++ b/src/__tests__/browserSuites/user-consent.spec.js @@ -1,6 +1,6 @@ import sinon from 'sinon'; import { SplitFactory } from '../../'; -import { triggerUnloadEvent } from '../testUtils/browser'; +import { triggerPagehideEvent } from '../testUtils/browser'; import { nearlyEqual, url } from '../testUtils'; const trackedImpressions = []; @@ -97,9 +97,9 @@ export default function userConsent(fetchMock, t) { ], ['on', 'on', 'on', 'on', 'on', 'on', 'on', 'on'], 'evaluating on SDK ready'); if (isTracking) expectedTrackedImpressions += 8; - // Trigger unload event to validate browser listener behaviour + // Trigger pagehide event to validate browser listener behaviour // Beacon API is used only if user consent is GRANTED - triggerUnloadEvent(); + triggerPagehideEvent(); if (factory.UserConsent.getStatus() === factory.UserConsent.Status.GRANTED) { assert.ok(sendBeaconSpy.calledThrice, 'sendBeacon should have been called thrice'); } else { diff --git a/src/__tests__/testUtils/browser.js b/src/__tests__/testUtils/browser.js index b185e93da..60284923d 100644 --- a/src/__tests__/testUtils/browser.js +++ b/src/__tests__/testUtils/browser.js @@ -1,7 +1,19 @@ -// Util method to trigger 'unload' DOM event -export function triggerUnloadEvent() { +function triggerEvent(eventName) { const event = document.createEvent('HTMLEvents'); - event.initEvent('unload', true, true); - event.eventName = 'unload'; + event.initEvent(eventName, true, true); + event.eventName = eventName; window.dispatchEvent(event); } + +export function triggerUnloadEvent() { + triggerEvent('unload'); +} + +export function triggerPagehideEvent() { + triggerEvent('pagehide'); +} + +export function triggerVisibilitychange(state = 'hidden' /* 'hidden' | 'visible' */) { + Object.defineProperty(document, 'visibilityState', { value: state, writable: true }); + document.dispatchEvent(new Event('visibilitychange')); +}