diff --git a/CHANGELOG.md b/CHANGELOG.md index 97e34221ba..b9188d28f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The types of changes are: - Dynamic importing for GPP bundle [#4447](https://github.com/ethyca/fides/pull/4447) - Paging to vendors in the TCF overlay [#4463](https://github.com/ethyca/fides/pull/4463) - New purposes endpoint and indices to improve system lookups [#4452](https://github.com/ethyca/fides/pull/4452) +- Cypress tests for fides.js GPP extension [#4476](https://github.com/ethyca/fides/pull/4476) - Add support for global TCF Purpose Overrides [#4464](https://github.com/ethyca/fides/pull/4464) ### Fixed diff --git a/clients/privacy-center/cypress/e2e/consent-banner-gpp.cy.ts b/clients/privacy-center/cypress/e2e/consent-banner-gpp.cy.ts index 66116748e0..f0dc6a7488 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-gpp.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-gpp.cy.ts @@ -1,6 +1,13 @@ +/** + * The GPP extension is often cached by the Cypress browser. You may need to manually + * clear your Cypress browser's cache in the Network tab if you are making changes to + * the source file while also running tests. + */ + /* eslint-disable no-underscore-dangle */ -import { FidesEndpointPaths } from "fides-js"; -import { API_URL } from "../support/constants"; +import { CONSENT_COOKIE_NAME, FidesEndpointPaths } from "fides-js"; +import { API_URL, TCF_VERSION_HASH } from "../support/constants"; +import { mockCookie } from "../support/mocks"; import { stubConfig } from "../support/stubs"; describe("Fides-js GPP extension", () => { @@ -60,6 +67,219 @@ describe("Fides-js GPP extension", () => { }); }); }); + + /** + * Follows the flow documented here for Event Order Example 1: + * https://github.com/InteractiveAdvertisingBureau/Global-Privacy-Platform/blob/main/Core/CMP%20API%20Specification.md#eventlistener- + * 1. initial data from stub + * 2. listenerRegistered + * 3. cmpStatus = loaded + * 4. cmpDisplayStatus = visible + * 5. User makes a choice + * 6. cmpDisplayStatus = hidden + * 7. sectionChange = tcfeuv2 + * 8. signalStatus = ready + */ + it("fires appropriate gpp events for first time user", () => { + cy.waitUntilFidesInitialized().then(() => { + cy.get("@FidesUIShown").should("have.been.calledOnce"); + // TODO(PROD#1439): Because the stub is too late right now, we can't listen for events + // 3 and 4 yet. + cy.window().then((win) => { + win.__gpp("addEventListener", cy.stub().as("gppListener")); + }); + + cy.get("@gppListener") + .should("have.been.calledOnce") + .its("lastCall.args") + .then(([data, success]) => { + expect(success).to.eql(true); + expect(data.eventName).to.eql("listenerRegistered"); + const { cmpDisplayStatus, signalStatus, gppString } = data.pingData; + expect(cmpDisplayStatus).to.eql("visible"); + expect(signalStatus).to.eql("not ready"); + expect(gppString).to.eql("DBAA"); // empty string, header only + }); + + cy.get("button").contains("Opt in to all").click(); + cy.get("@FidesUpdated").should("have.been.calledOnce"); + + const expected = [ + { eventName: "cmpDisplayStatus", data: "hidden" }, + { eventName: "sectionChange", data: "tcfeuv2" }, + { eventName: "signalStatus", data: "ready" }, + ]; + + cy.get("@gppListener") + .its("args") + .then((args) => { + expect(args.length).to.eql(4); + // we already checked the first arg, so inspect the other three + [args[1], args[2], args[3]].forEach(([data, success], idx) => { + expect(success).to.eql(true); + expect(data.eventName).to.eql(expected[idx].eventName); + expect(data.data).to.eql(expected[idx].data); + }); + // The gpp string should also have an extra section now and the header should + // indicate TCF + expect(args[3][0].pingData.gppString).to.contain("DBABMA~"); + }); + }); + }); + + /** + * Follows the flow documented here for Event Order Example 2: + * https://github.com/InteractiveAdvertisingBureau/Global-Privacy-Platform/blob/main/Core/CMP%20API%20Specification.md#eventlistener- + * 1. initial data from stub + * 2. listenerRegistered + * 3. cmpStatus = loaded + * 4. signalStatus = ready + * 5. User opens the consent layer to change their voice + * 6. signalStatus = not ready + * 7. cmpDisplayStatus = visible + * 8. User makes their choice + * 9. cmpDisplayStatus = hidden + * 10. sectionChange = tcfeuv2 + * 11. signalStatus = ready + */ + it("fires appropriate gpp events for returning user", () => { + const tcString = "CPziCYAPziCYAGXABBENATEIAACAAAAAAAAAABEAAAAA"; + // Set a cookie to mimic a returning user + const cookie = mockCookie({ + tcf_version_hash: TCF_VERSION_HASH, + fides_string: tcString, + }); + cy.setCookie(CONSENT_COOKIE_NAME, JSON.stringify(cookie)); + cy.fixture("consent/experience_tcf.json").then((experience) => { + stubConfig({ + options: { + isOverlayEnabled: true, + tcfEnabled: true, + gppEnabled: true, + }, + experience: experience.items[0], + }); + }); + + cy.waitUntilFidesInitialized().then(() => { + cy.get("@FidesUIShown").should("not.have.been.called"); + // TODO(PROD#1439): Because the stub is too late right now, we can't listen for events + // 3 and 4 yet. + cy.window().then((win) => { + win.__gpp("addEventListener", cy.stub().as("gppListener")); + }); + + // Check initial data which should signal Ready and have the cookie's TC string + cy.get("@gppListener") + .should("have.been.calledOnce") + .its("lastCall.args") + .then(([data, success]) => { + expect(success).to.eql(true); + expect(data.eventName).to.eql("listenerRegistered"); + const { cmpDisplayStatus, signalStatus, gppString, cmpStatus } = + data.pingData; + expect(cmpStatus).to.eql("loaded"); + expect(cmpDisplayStatus).to.eql("hidden"); + expect(signalStatus).to.eql("ready"); + expect(gppString).to.contain(tcString); + }); + + // User opens the modal so signal should be "not ready" and display should be "visible" + cy.get("#fides-modal-link").click(); + cy.get("@gppListener") + .its("args") + .then((args) => { + expect(args.length).to.eql(3); + const expected = [ + { eventName: "signalStatus", data: "not ready" }, + { eventName: "cmpDisplayStatus", data: "visible" }, + ]; + [args[1], args[2]].forEach(([data, success], idx) => { + expect(success).to.eql(true); + expect(data.eventName).to.eql(expected[idx].eventName); + expect(data.data).to.eql(expected[idx].data); + }); + }); + + // User makes a choice + cy.getByTestId("consent-modal").within(() => { + cy.get("button").contains("Opt out of all").click(); + cy.get("@FidesUpdated").should("have.been.calledOnce"); + }); + cy.get("@gppListener") + .its("args") + .then((args) => { + expect(args.length).to.eql(6); + const expected = [ + { eventName: "cmpDisplayStatus", data: "hidden" }, + { eventName: "sectionChange", data: "tcfeuv2" }, + { eventName: "signalStatus", data: "ready" }, + ]; + [args[3], args[4], args[5]].forEach(([data, success], idx) => { + expect(success).to.eql(true); + expect(data.eventName).to.eql(expected[idx].eventName); + expect(data.data).to.eql(expected[idx].data); + }); + // Check that the TC string changed-still the same header, but different contents + const { gppString } = args[5][0].pingData; + expect(gppString).to.contain("DBABMA~"); + expect(gppString).not.to.contain(tcString); + }); + }); + }); + + /** + * Expected flow for a returning user who opens but then closes the modal without making a change: + * 1. listenerRegistered + * 2. User opens the modal + * 3. signalStatus = not ready + * 4. cmpDisplayStatus = visible + * 5. User closes the modal without saving anything + * 6. cmpDisplayStatus = hidden + * 7. signalStatus = ready + */ + it("can handle returning user closing the modal without a preference change", () => { + const cookie = mockCookie({ + tcf_version_hash: TCF_VERSION_HASH, + }); + cy.setCookie(CONSENT_COOKIE_NAME, JSON.stringify(cookie)); + cy.fixture("consent/experience_tcf.json").then((experience) => { + stubConfig({ + options: { + isOverlayEnabled: true, + tcfEnabled: true, + gppEnabled: true, + }, + experience: experience.items[0], + }); + }); + cy.waitUntilFidesInitialized().then(() => { + cy.window().then((win) => { + win.__gpp("addEventListener", cy.stub().as("gppListener")); + }); + cy.get("#fides-modal-link").click(); + const expected = [ + { eventName: "listenerRegistered", data: true }, + { eventName: "signalStatus", data: "not ready" }, + { eventName: "cmpDisplayStatus", data: "visible" }, + { eventName: "cmpDisplayStatus", data: "hidden" }, + { eventName: "signalStatus", data: "ready" }, + ]; + cy.get("@gppListener") + .its("args") + .then( + ( + args: [{ eventName: string; data: string | boolean }, boolean][] + ) => { + args.forEach(([data, success], idx) => { + expect(success).to.eql(true); + expect(data.eventName).to.eql(expected[idx].eventName); + expect(data.data).to.eql(expected[idx].data); + }); + } + ); + }); + }); }); describe("with TCF disabled and GPP enabled", () => { diff --git a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts index dd3f796040..40cf2136b6 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts @@ -7,7 +7,7 @@ import { PrivacyExperience, } from "fides-js"; import { CookieKeyConsent } from "fides-js/src/lib/cookie"; -import { API_URL } from "../support/constants"; +import { API_URL, TCF_VERSION_HASH } from "../support/constants"; import { mockCookie, mockTcfVendorObjects } from "../support/mocks"; import { OVERRIDE, stubConfig } from "../support/stubs"; @@ -67,7 +67,6 @@ const SPECIAL_FEATURE_1 = { name: "Use precise geolocation data", served_notice_history_id: "ser_9f3641ce-9863-4a32-b4db-ef1aac9046db", }; -const VERSION_HASH = "q34r3qr4"; const checkDefaultExperienceRender = () => { // Purposes @@ -157,7 +156,7 @@ describe("Fides-js TCF", () => { it("should not render the banner if the saved hashes match", () => { const cookie = mockCookie({ - tcf_version_hash: VERSION_HASH, + tcf_version_hash: TCF_VERSION_HASH, }); cy.setCookie(CONSENT_COOKIE_NAME, JSON.stringify(cookie)); cy.fixture("consent/experience_tcf.json").then((experience) => { diff --git a/clients/privacy-center/cypress/support/constants.ts b/clients/privacy-center/cypress/support/constants.ts index 33709f711e..c07c946942 100644 --- a/clients/privacy-center/cypress/support/constants.ts +++ b/clients/privacy-center/cypress/support/constants.ts @@ -1 +1,2 @@ export const API_URL = Cypress.env("API_URL"); +export const TCF_VERSION_HASH = "q34r3qr4";