From c56e67ec5a418d52201e3211a3d5e0acf21dd8a8 Mon Sep 17 00:00:00 2001 From: Allison King Date: Thu, 30 Nov 2023 17:07:17 -0500 Subject: [PATCH 1/4] Add initial tests --- .../cypress/e2e/consent-banner-gpp.cy.ts | 118 +++++++++++++++++- .../cypress/e2e/consent-banner-tcf.cy.ts | 5 +- .../cypress/support/constants.ts | 1 + 3 files changed, 119 insertions(+), 5 deletions(-) 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..663db76271 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,7 @@ /* 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 +61,119 @@ 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(); + + 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.only("fires appropriate gpp events for returning user", () => { + // Set a cookie to mimic a returning user + 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.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")); + }); + + 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; + console.log({ data }); + expect(cmpDisplayStatus).to.eql("hidden"); + expect(signalStatus).to.eql("ready"); + expect(gppString).to.eql("DBAA"); // empty string, header only + }); + }); + }); }); 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"; From 3b45e3977682bdefd518591e5ffdb0b1af60d2f6 Mon Sep 17 00:00:00 2001 From: Allison King Date: Fri, 1 Dec 2023 12:43:49 -0500 Subject: [PATCH 2/4] Add tests for GPP CMP API --- .../cypress/e2e/consent-banner-gpp.cy.ts | 112 +++++++++++++++++- 1 file changed, 108 insertions(+), 4 deletions(-) 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 663db76271..9216a5b132 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-gpp.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-gpp.cy.ts @@ -1,3 +1,9 @@ +/** + * 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 { CONSENT_COOKIE_NAME, FidesEndpointPaths } from "fides-js"; import { API_URL, TCF_VERSION_HASH } from "../support/constants"; @@ -135,10 +141,12 @@ describe("Fides-js GPP extension", () => { * 10. sectionChange = tcfeuv2 * 11. signalStatus = ready */ - it.only("fires appropriate gpp events for returning user", () => { + 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) => { @@ -160,18 +168,114 @@ describe("Fides-js GPP extension", () => { 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 } = data.pingData; - console.log({ data }); + 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.eql("DBAA"); // empty string, header only + 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("@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); + }); + } + ); }); }); }); From 9cf2f6b1f077e0ebfba29c3a0e08c6f01fa02c17 Mon Sep 17 00:00:00 2001 From: Allison King Date: Fri, 1 Dec 2023 14:12:28 -0500 Subject: [PATCH 3/4] Add waits for slow connections --- clients/privacy-center/cypress/e2e/consent-banner-gpp.cy.ts | 2 ++ 1 file changed, 2 insertions(+) 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 9216a5b132..f0dc6a7488 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-gpp.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-gpp.cy.ts @@ -102,6 +102,7 @@ describe("Fides-js GPP extension", () => { }); cy.get("button").contains("Opt in to all").click(); + cy.get("@FidesUpdated").should("have.been.calledOnce"); const expected = [ { eventName: "cmpDisplayStatus", data: "hidden" }, @@ -203,6 +204,7 @@ describe("Fides-js GPP extension", () => { // 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") From 759859ce482fd47d0bbd40234b8c9eabd1027bed Mon Sep 17 00:00:00 2001 From: Allison King Date: Fri, 1 Dec 2023 14:13:05 -0500 Subject: [PATCH 4/4] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d82077d62f..daf1a06c37 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) ### Fixed - Fix type errors when TCF vendors have no dataDeclaration [#4465](https://github.com/ethyca/fides/pull/4465)