diff --git a/e2e/demo.spec.ts b/e2e/demo.spec.ts deleted file mode 100644 index 2cbffa02..00000000 --- a/e2e/demo.spec.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {test, expect} from '@playwright/test'; - -test.describe('Demo page', () => { - // A very basic test to assess the availability of the dev server. - test('should have a title', async ({page}) => { - await page.goto('/'); - await expect(page).toHaveTitle(/Orejime/); - }); -}); diff --git a/e2e/orejime.spec.ts b/e2e/orejime.spec.ts deleted file mode 100644 index 7914a8ab..00000000 --- a/e2e/orejime.spec.ts +++ /dev/null @@ -1,217 +0,0 @@ -import {test, expect, type BrowserContext} from '@playwright/test'; - -test.describe('Orejime', () => { - const getConsentsFromCookies = async (context: BrowserContext) => { - const cookies = await context.cookies(); - const orejimeCookie = cookies.find(({name}) => name === 'eu-consent'); - - // `js-cookie` encodes cookie values - const decodedValue = orejimeCookie!.value - .replace(/%22/gi, '"') - .replace(/%2C/gi, ','); - - return JSON.parse(decodedValue); - }; - - test.beforeEach(async ({page}) => { - await page.goto('/'); - }); - - test('should show a notice', async ({page}) => { - const notice = page.locator('.orejime-Banner'); - await expect(notice).toBeVisible(); - }); - - test('should navigate to the notice first', async ({page}) => { - await page.keyboard.press('Tab'); - const firstButton = page.locator('.orejime-Banner-saveButton'); - await expect(firstButton).toBeVisible(); - }); - - test('should accept all purposes from the notice', async ({ - page, - context - }) => { - await page.locator('.orejime-Banner-saveButton').click(); - const notice = page.locator('.orejime-Banner'); - await expect(notice).not.toBeVisible(); - - const consents = await getConsentsFromCookies(context); - - expect(consents).toEqual( - expect.objectContaining({ - 'mandatory': true, - 'inline-tracker': true, - 'external-tracker': true - }) - ); - }); - - test('should decline all purposes from the notice', async ({ - page, - context - }) => { - await page.locator('.orejime-Banner-declineButton').click(); - const notice = page.locator('.orejime-Banner'); - await expect(notice).not.toBeVisible(); - - const consents = await getConsentsFromCookies(context); - - expect(consents).toEqual( - expect.objectContaining({ - 'mandatory': true, - 'inline-tracker': false, - 'external-tracker': false - }) - ); - }); - - test('should open a modal', async ({page}) => { - await page.locator('.orejime-Banner-learnMoreButton').click(); - const notice = page.locator('.orejime-Banner'); - await expect(notice).toBeVisible(); - - const modal = page.locator('.orejime-Modal'); - await expect(modal).toBeVisible(); - await expect(notice).toBeVisible(); - }); - - test('should close the modal via the close button', async ({page}) => { - await page.locator('.orejime-Banner-learnMoreButton').click(); - const modal = page.locator('.orejime-Modal'); - await expect(modal).toBeVisible(); - - await page.locator('.orejime-Modal-closeButton').click(); - await expect(modal).toHaveCount(0); - - const notice = page.locator('.orejime-Banner'); - await expect(notice).toBeVisible(); - }); - - test('should close the modal via the overlay', async ({page}) => { - await page.locator('.orejime-Banner-learnMoreButton').click(); - const modal = page.locator('.orejime-Modal'); - await expect(modal).toBeVisible(); - - // We're clicking in a corner to avoid clicking on the - // modal itself, which has no effect. - await page.locator('.orejime-ModalOverlay').click({ - position: { - x: 1, - y: 1 - } - }); - - await expect(modal).toHaveCount(0); - - const notice = page.locator('.orejime-Banner'); - await expect(notice).toBeVisible(); - }); - - test('should close the modal via `Escape` key', async ({page}) => { - await page.locator('.orejime-Banner-learnMoreButton').click(); - const modal = page.locator('.orejime-Modal'); - await expect(modal).toBeVisible(); - - await page.keyboard.press('Escape'); - await expect(modal).toHaveCount(0); - - const notice = page.locator('.orejime-Banner'); - await expect(notice).toBeVisible(); - }); - - test('should move focus after closing the modal', async ({page}) => { - const openModalButton = page.locator('.orejime-Banner-learnMoreButton'); - openModalButton.click(); - - const modal = page.locator('.orejime-Modal'); - await expect(modal).toBeVisible(); - - await page.keyboard.press('Escape'); - await expect(openModalButton).toBeFocused(); - }); - - test('should accept all purposes from the modal', async ({ - page, - context - }) => { - await page.locator('.orejime-Banner-learnMoreButton').click(); - await page.locator('.orejime-PurposeToggles-enableAll').click(); - - const checkbox = page.locator('#orejime-purpose-inline-tracker'); - await expect(checkbox).toBeChecked(); - - const mandatoryCheckbox = page.locator('#orejime-purpose-mandatory'); - await expect(mandatoryCheckbox).toBeChecked(); - - await page.locator('.orejime-Modal-saveButton').click(); - const consents = await getConsentsFromCookies(context); - - expect(consents).toEqual( - expect.objectContaining({ - 'mandatory': true, - 'inline-tracker': true, - 'external-tracker': true - }) - ); - - const notice = page.locator('.orejime-Banner'); - await expect(notice).not.toBeVisible(); - }); - - test('should decline all purposes from the modal', async ({ - page, - context - }) => { - await page.locator('.orejime-Banner-learnMoreButton').click(); - await page.locator('.orejime-PurposeToggles-enableAll').click(); - await page.locator('.orejime-PurposeToggles-disableAll').click(); - - const checkbox = page.locator('#orejime-purpose-inline-tracker'); - await expect(checkbox).not.toBeChecked(); - - const mandatoryCheckbox = page.locator('#orejime-purpose-mandatory'); - await expect(mandatoryCheckbox).toBeChecked(); - - await page.locator('.orejime-Modal-saveButton').click(); - const consents = await getConsentsFromCookies(context); - - expect(consents).toEqual( - expect.objectContaining({ - 'mandatory': true, - 'inline-tracker': false, - 'external-tracker': false - }) - ); - - const notice = page.locator('.orejime-Banner'); - await expect(notice).not.toBeVisible(); - }); - - test('should sync grouped purposes', async ({page}) => { - await page.locator('.orejime-Banner-learnMoreButton').click(); - - const checkbox = page.locator('#orejime-purpose-inline-tracker'); - await expect(checkbox).not.toBeChecked(); - - const checkbox2 = page.locator('#orejime-purpose-external-tracker'); - await expect(checkbox2).not.toBeChecked(); - - const groupCheckbox = page.locator('#orejime-purpose-group'); - await groupCheckbox.check(); - await expect(groupCheckbox).toBeChecked(); - await expect(checkbox).toBeChecked(); - await expect(checkbox2).toBeChecked(); - - await checkbox.uncheck(); - await expect(groupCheckbox).not.toBeChecked(); - await expect(groupCheckbox).toHaveJSProperty('indeterminate', true); - await expect(checkbox).not.toBeChecked(); - await expect(checkbox2).toBeChecked(); - - await checkbox2.uncheck(); - await expect(groupCheckbox).not.toBeChecked(); - await expect(checkbox).not.toBeChecked(); - await expect(checkbox2).not.toBeChecked(); - }); -}); diff --git a/e2e/specs/OrejimePage.ts b/e2e/specs/OrejimePage.ts new file mode 100644 index 00000000..b900008a --- /dev/null +++ b/e2e/specs/OrejimePage.ts @@ -0,0 +1,114 @@ +import {expect, BrowserContext, Page} from '@playwright/test'; +import Cookie from 'js-cookie'; +import {Config} from '../../src/ui'; + +export class OrejimePage { + constructor( + public readonly page: Page, + public readonly context: BrowserContext + ) {} + + async load(config: Partial) { + await this.page.route('/', async (route) => { + await route.fulfill({ + body: ` + + + + + Orejime + + + + + + + + + ` + }); + }); + + await this.page.goto('/'); + } + + get banner() { + return this.page.locator('.orejime-Banner'); + } + + get leanMoreBannerButton() { + return this.page.locator('.orejime-Banner-learnMoreButton'); + } + + get firstFocusableElementFromBanner() { + return this.page.locator('.orejime-Banner :is(a, button)').first(); + } + + get modal() { + return this.page.locator('.orejime-Modal'); + } + + purposeCheckbox(purposeId: string) { + return this.page.locator(`#orejime-purpose-${purposeId}`); + } + + async focusNext() { + await this.page.keyboard.press('Tab'); + } + + async acceptAllFromBanner() { + await this.page.locator('.orejime-Banner-saveButton').click(); + } + + async declineAllFromBanner() { + await this.page.locator('.orejime-Banner-declineButton').click(); + } + + async openModalFromBanner() { + await this.leanMoreBannerButton.click(); + } + + async enableAllFromModal() { + await this.page.locator('.orejime-PurposeToggles-enableAll').click(); + } + + async disableAllFromModal() { + await this.page.locator('.orejime-PurposeToggles-disableAll').click(); + } + + async saveFromModal() { + await this.page.locator('.orejime-Modal-saveButton').click(); + } + + async closeModalByClickingButton() { + await this.page.locator('.orejime-Modal-closeButton').click(); + } + + async closeModalByClickingOutside() { + // We're clicking in a corner to avoid clicking on the + // modal itself, which has no effect. + await this.page.locator('.orejime-ModalOverlay').click({ + position: { + x: 1, + y: 1 + } + }); + } + + async closeModalByPressingEscape() { + await this.page.keyboard.press('Escape'); + } + + async expectConsents(consents: Record) { + expect(await this.getConsentsFromCookies()).toEqual(consents); + } + + async getConsentsFromCookies() { + const name = 'eu-consent'; + const cookies = await this.context.cookies(); + const {value} = cookies.find((cookie) => cookie.name === name)!; + return JSON.parse(Cookie.converter.read(value, name)); + } +} diff --git a/e2e/specs/orejime.spec.ts b/e2e/specs/orejime.spec.ts new file mode 100644 index 00000000..6fa64bd6 --- /dev/null +++ b/e2e/specs/orejime.spec.ts @@ -0,0 +1,169 @@ +import {test, expect} from '@playwright/test'; +import {Config} from '../../src/ui'; +import {OrejimePage} from './OrejimePage'; + +test.describe('Orejime', () => { + const BaseConfig: Partial = { + privacyPolicyUrl: 'https://example.org/privacy', + purposes: [ + { + id: 'mandatory', + title: 'Mandatory', + cookies: ['mandatory'], + isMandatory: true + }, + { + id: 'group', + title: 'Group', + purposes: [ + { + id: 'child-1', + title: 'First child', + cookies: ['child-1'] + }, + { + id: 'child-2', + title: 'Second child', + cookies: ['child-2'] + } + ] + } + ] + }; + + let orejimePage: OrejimePage; + + test.beforeEach(async ({page, context}) => { + orejimePage = new OrejimePage(page, context); + await orejimePage.load(BaseConfig); + }); + + test('should show a banner', async () => { + await expect(orejimePage.banner).toBeVisible(); + }); + + test('should navigate to the banner first', async () => { + await orejimePage.focusNext(); + await expect(orejimePage.firstFocusableElementFromBanner).toBeFocused(); + }); + + test('should accept all purposes from the banner', async () => { + await orejimePage.acceptAllFromBanner(); + await expect(orejimePage.banner).not.toBeVisible(); + + orejimePage.expectConsents({ + 'mandatory': true, + 'child-1': true, + 'child-2': true + }); + }); + + test('should decline all purposes from the banner', async () => { + await orejimePage.declineAllFromBanner(); + await expect(orejimePage.banner).not.toBeVisible(); + + orejimePage.expectConsents({ + 'mandatory': true, + 'child-1': false, + 'child-2': false + }); + }); + + test('should open a modal', async () => { + await orejimePage.openModalFromBanner(); + + await expect(orejimePage.banner).toBeVisible(); + await expect(orejimePage.modal).toBeVisible(); + }); + + test('should close the modal via the close button', async () => { + await orejimePage.openModalFromBanner(); + await expect(orejimePage.modal).toBeVisible(); + + await orejimePage.closeModalByClickingButton(); + await expect(orejimePage.modal).toHaveCount(0); + await expect(orejimePage.banner).toBeVisible(); + }); + + test('should close the modal via the overlay', async () => { + await orejimePage.openModalFromBanner(); + await expect(orejimePage.modal).toBeVisible(); + + await orejimePage.closeModalByClickingOutside(); + await expect(orejimePage.modal).toHaveCount(0); + await expect(orejimePage.banner).toBeVisible(); + }); + + test('should close the modal via `Escape` key', async () => { + await orejimePage.openModalFromBanner(); + await expect(orejimePage.modal).toBeVisible(); + + await orejimePage.closeModalByPressingEscape(); + await expect(orejimePage.modal).toHaveCount(0); + await expect(orejimePage.banner).toBeVisible(); + }); + + test('should move focus after closing the modal', async () => { + await orejimePage.openModalFromBanner(); + await expect(orejimePage.modal).toBeVisible(); + + await orejimePage.closeModalByPressingEscape(); + await expect(orejimePage.leanMoreBannerButton).toBeFocused(); + }); + + test('should accept all purposes from the modal', async () => { + await orejimePage.openModalFromBanner(); + await orejimePage.enableAllFromModal(); + await expect(orejimePage.purposeCheckbox('child-1')).toBeChecked(); + await expect(orejimePage.purposeCheckbox('mandatory')).toBeChecked(); + await orejimePage.saveFromModal(); + + orejimePage.expectConsents({ + 'mandatory': true, + 'child-1': true, + 'child-2': true + }); + }); + + test('should decline all purposes from the modal', async () => { + await orejimePage.openModalFromBanner(); + await orejimePage.enableAllFromModal(); + await orejimePage.disableAllFromModal(); + await expect(orejimePage.purposeCheckbox('child-1')).not.toBeChecked(); + await expect(orejimePage.purposeCheckbox('mandatory')).toBeChecked(); + await orejimePage.saveFromModal(); + + orejimePage.expectConsents({ + 'mandatory': true, + 'child-1': false, + 'child-2': false + }); + }); + + test('should sync grouped purposes', async () => { + await orejimePage.openModalFromBanner(); + + const checkbox = orejimePage.purposeCheckbox('child-1'); + await expect(checkbox).not.toBeChecked(); + + const checkbox2 = orejimePage.purposeCheckbox('child-2'); + await expect(checkbox2).not.toBeChecked(); + + const groupCheckbox = orejimePage.purposeCheckbox('group'); + await groupCheckbox.check(); + await expect(groupCheckbox).toBeChecked(); + await expect(checkbox).toBeChecked(); + await expect(checkbox2).toBeChecked(); + + await checkbox.uncheck(); + await expect(groupCheckbox).not.toBeChecked(); + await expect(groupCheckbox).toHaveJSProperty('indeterminate', true); + await expect(checkbox).not.toBeChecked(); + await expect(checkbox2).toBeChecked(); + + await checkbox2.uncheck(); + await expect(groupCheckbox).not.toBeChecked(); + await expect(checkbox).not.toBeChecked(); + await expect(checkbox2).not.toBeChecked(); + }); +});