Skip to content

Commit

Permalink
Merge pull request #109 from empreinte-digitale/feature-e2e-tests
Browse files Browse the repository at this point in the history
End-to-end tests
  • Loading branch information
felixgirault authored Jan 16, 2025
2 parents f8eddb6 + 1c7ac65 commit 0055c9c
Show file tree
Hide file tree
Showing 7 changed files with 428 additions and 47 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Playwright Tests

on:
- push
- pull_request

jobs:
test:
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@
/dist/*.mts
/dist/stats.json
/dist/example-assets/migrations.js
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
114 changes: 114 additions & 0 deletions e2e/OrejimePage.ts
Original file line number Diff line number Diff line change
@@ -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<Config>) {
await this.page.route('/', async (route) => {
await route.fulfill({
body: `
<!DOCTYPE html>
<html>
<head>
<title>Orejime</title>
<link rel="stylesheet" href="orejime.css" />
</head>
<body>
<script>
window.orejimeConfig = ${JSON.stringify(config)}
</script>
<script src="orejime.js"></script>
</body>
</html>
`
});
});

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<string, unknown>) {
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));
}
}
168 changes: 168 additions & 0 deletions e2e/orejime.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import {test, expect} from '@playwright/test';
import {Config} from '../src/ui';
import {OrejimePage} from './OrejimePage';

test.describe('Orejime', () => {
const BaseConfig: Partial<Config> = {
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 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();
});
});
Loading

0 comments on commit 0055c9c

Please sign in to comment.