-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #109 from empreinte-digitale/feature-e2e-tests
End-to-end tests
- Loading branch information
Showing
7 changed files
with
428 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); | ||
}); |
Oops, something went wrong.