Skip to content

Commit

Permalink
Merge pull request #76 from c4dt/playwright
Browse files Browse the repository at this point in the history
Smaller changes, also undo of breaking change in PR #69
  • Loading branch information
PascalinDe authored Dec 13, 2023
2 parents fbc6666 + 2369d9c commit 0863d1d
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 89 deletions.
4 changes: 2 additions & 2 deletions web/frontend/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "tsconfig.json"
"project": ["tsconfig.json", "tests/tsconfig.json"]
},
"plugins": ["react", "@typescript-eslint", "jest", "unused-imports"],
"root": true,
Expand All @@ -32,7 +32,7 @@
"import/no-extraneous-dependencies": ["error", {"devDependencies": [
"**/*.test.ts", "**/*.test.tsx",
"**/setupTest.ts", "**/setupProxy.js",
"**/mocks/*"
"**/mocks/*", "tests/*.ts"
]}],

// conflicts with the index.ts (eslint prefers default exports which are not compatible with index.ts)
Expand Down
4 changes: 2 additions & 2 deletions web/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"eslint": "./node_modules/.bin/eslint src/",
"eslint-fix": "./node_modules/.bin/eslint src/ --fix",
"eslint": "./node_modules/.bin/eslint src/ tests/",
"eslint-fix": "./node_modules/.bin/eslint src/ tests/ --fix",
"prettier": "./node_modules/.bin/prettier --write src/",
"prettier-check": "./node_modules/.bin/prettier --check src/"
},
Expand Down
15 changes: 11 additions & 4 deletions web/frontend/tests/footer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import { test, expect } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { default as i18n } from 'i18next';
import { initI18n, setUp, getFooter } from './shared';
import { initI18n, setUp } from './shared';
import { UPDATE, mockPersonalInfo } from './mocks';

initI18n();

test.beforeEach(async ({ page }) => {
if (UPDATE === true) {
return;
}
await mockPersonalInfo(page);
await setUp(page, '/about');
});

test('Assert copyright notice is visible', async({ page }) => {
test('Assert copyright notice is visible', async ({ page }) => {
test.skip(UPDATE === true, 'Do not run regular tests when updating HAR files');
const footerCopyright = await page.getByTestId('footerCopyright');
await expect(footerCopyright).toBeVisible();
await expect(footerCopyright).toHaveText(
${new Date().getFullYear()} ${i18n.t('footerCopyright')} https://github.com/c4dt/d-voting`
);
});

test('Assert version information is visible', async({ page }) => {
test('Assert version information is visible', async ({ page }) => {
test.skip(UPDATE === true, 'Do not run regular tests when updating HAR files');
const footerVersion = await page.getByTestId('footerVersion');
await expect(footerVersion).toBeVisible();
await expect(footerVersion).toHaveText(
Expand Down
69 changes: 39 additions & 30 deletions web/frontend/tests/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,51 @@ export const SCIPER_ADMIN = '123456';
export const SCIPER_USER = '789012';
export const UPDATE = false;

export async function mockPersonalInfo (page: any, sciper?: string) {
export async function mockPersonalInfo(page: any, sciper?: string) {
// clear current mock
await page.unroute('/api/personal_info');
await page.routeFromHAR(
`./tests/hars/${sciper ?? 'anonymous'}/personal_info.har`,
{
url: '/api/personal_info',
update: UPDATE,
});
await page.routeFromHAR(`./tests/hars/${sciper ?? 'anonymous'}/personal_info.har`, {
url: '/api/personal_info',
update: UPDATE,
});
}

export async function mockGetDevLogin (page: any) {
await page.routeFromHAR(
`./tests/hars/${SCIPER_ADMIN}/get_dev_login.har`,
{
url: `/api/get_dev_login/${SCIPER_ADMIN}`,
update: UPDATE,
});
await page.routeFromHAR(
`./tests/hars/${SCIPER_USER}/get_dev_login.har`,
{
url: `/api/get_dev_login/${SCIPER_USER}`,
update: UPDATE,
});
if (process.env.REACT_APP_SCIPER_ADMIN !== undefined && process.env.REACT_APP_SCIPER_ADMIN !== SCIPER_ADMIN) {
export async function mockGetDevLogin(page: any) {
await page.routeFromHAR(`./tests/hars/${SCIPER_ADMIN}/get_dev_login.har`, {
url: `/api/get_dev_login/${SCIPER_ADMIN}`,
update: UPDATE,
});
await page.routeFromHAR(`./tests/hars/${SCIPER_USER}/get_dev_login.har`, {
url: `/api/get_dev_login/${SCIPER_USER}`,
update: UPDATE,
});
if (
process.env.REACT_APP_SCIPER_ADMIN !== undefined &&
process.env.REACT_APP_SCIPER_ADMIN !== SCIPER_ADMIN
) {
// dummy route for "Login" button depending on local configuration
await page.route(
`/api/get_dev_login/${process.env.REACT_APP_SCIPER_ADMIN}`,
async route => {await route.fulfill({});}
);
await page.route(`/api/get_dev_login/${process.env.REACT_APP_SCIPER_ADMIN}`, async (route) => {
await route.fulfill({});
});
}
}

export async function mockLogout (page: any) {
await page.route(
'/api/logout',
async route => {await route.fulfill({});}
);
export async function mockLogout(page: any) {
await page.route('/api/logout', async (route) => {
await route.fulfill({});
});
}

export async function mockProxy(page: any) {
await page.route('/api/config/proxy', async (route) => {
await route.fulfill({
status: 200,
contentType: 'text/html',
body: `${process.env.DELA_PROXY_URL}`,
headers: {
'set-cookie':
'connect.sid=s%3A5srES5h7hQ2fN5T71W59qh3cUSQL3Mix.fPoO3rOxui8yfTG7tFd7RPyasaU5VTkhxgdzVRWJyNk',
},
});
});
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
import { default as i18n } from 'i18next';
import { test, expect } from '@playwright/test';
import { expect, test } from '@playwright/test';
import {
assertOnlyVisibleToAdmin,
assertOnlyVisibleToAuthenticated,
initI18n,
setUp,
logIn,
logOut,
assertOnlyVisibleToAuthenticated,
assertOnlyVisibleToAdmin,
setUp,
} from './shared';
import { SCIPER_ADMIN, SCIPER_USER, UPDATE, mockPersonalInfo, mockLogout } from './mocks';
import { SCIPER_ADMIN, SCIPER_USER, UPDATE, mockLogout, mockPersonalInfo } from './mocks';

initI18n();

test.beforeEach(async ({ page }) => {
if (UPDATE === true) {
return;
}
await mockPersonalInfo(page);
await setUp(page, '/about');
});

// helper tests to update related HAR files

test('Assert anonymous user HAR files are up-to-date', async({ page }) => {
// comment the next line to update HAR files
test('Assert anonymous user HAR files are up-to-date', async ({ page }) => {
test.skip(UPDATE === false, 'Do not update HAR files');
await mockPersonalInfo(page);
await setUp(page, '/about');
});

test('Assert non-admin user HAR files are up-to-date', async({ page }) => {
// comment the next line to update HAR files
test('Assert non-admin user HAR files are up-to-date', async ({ page }) => {
test.skip(UPDATE === false, 'Do not update HAR files');
await mockPersonalInfo(page, SCIPER_USER);
await page.context().request.get(`/api/get_dev_login/${SCIPER_USER}`);
await setUp(page, '/about');
});

test('Assert admin user HAR files are up-to-date', async({ page }) => {
// comment the next line to update HAR files
test('Assert admin user HAR files are up-to-date', async ({ page }) => {
test.skip(UPDATE === false, 'Do not update HAR files');
await mockPersonalInfo(page, SCIPER_ADMIN);
await page.context().request.get(`/api/get_dev_login/${SCIPER_ADMIN}`);
Expand All @@ -43,69 +43,71 @@ test('Assert admin user HAR files are up-to-date', async({ page }) => {

// unauthenticated

test('Assert cookie is set', async({ page }) => {
test('Assert cookie is set', async ({ page }) => {
test.skip(UPDATE === true, 'Do not run regular tests when updating HAR files');
const cookies = await page.context().cookies();
expect(cookies.find(cookie => cookie.name === 'connect.sid')).toBeTruthy();
await expect(cookies.find((cookie) => cookie.name === 'connect.sid')).toBeTruthy();
});

test('Assert D-Voting logo is present', async({ page }) => {
test('Assert D-Voting logo is present', async ({ page }) => {
test.skip(UPDATE === true, 'Do not run regular tests when updating HAR files');
const logo = await page.getByAltText(i18n.t('Workflow'));
await expect(logo).toBeVisible();
await logo.click();
await expect(page).toHaveURL('/');
});

test('Assert link to form table is present', async({ page }) => {
test('Assert link to form table is present', async ({ page }) => {
test.skip(UPDATE === true, 'Do not run regular tests when updating HAR files');
const forms = await page.getByRole('link', { name: i18n.t('navBarStatus') });
await expect(forms).toBeVisible();
await forms.click();
await expect(page).toHaveURL('/form/index');
});

test('Assert "Login" button calls login API', async({ page }) => {
test('Assert "Login" button calls login API', async ({ page }) => {
test.skip(UPDATE === true, 'Do not run regular tests when updating HAR files');
const loginRequest = page.waitForRequest(
new RegExp("/api/get_dev_login/[0-9]{6}")
);
page.waitForRequest(new RegExp('/api/get_dev_login/[0-9]{6}'));
await page.getByRole('button', { name: i18n.t('login') }).click();
});

// authenticated non-admin

test('Assert "Profile" button is visible upon logging in', async({ page }) => {
test('Assert "Profile" button is visible upon logging in', async ({ page }) => {
test.skip(UPDATE === true, 'Do not run regular tests when updating HAR files');
await assertOnlyVisibleToAuthenticated(
page, page.getByRole('button', { name: i18n.t('Profile') })
page,
page.getByRole('button', { name: i18n.t('Profile') })
);
});

test('Assert "Logout" calls logout API', async({ page, baseURL }) => {
test('Assert "Logout" calls logout API', async ({ page, baseURL }) => {
test.skip(UPDATE === true, 'Do not run regular tests when updating HAR files');
await mockLogout(page);
await logIn(page, SCIPER_USER);
const logoutRequestPromise = page.waitForRequest(
request => request.url() === `${baseURL}/api/logout` && request.method() === 'POST'
page.waitForRequest(
(request) => request.url() === `${baseURL}/api/logout` && request.method() === 'POST'
);
for (const [role, key] of [['button', 'Profile'], ['menuitem', 'logout'], ['button', 'continue']]) {
for (const [role, key] of [
['button', 'Profile'],
['menuitem', 'logout'],
['button', 'continue'],
]) {
await page.getByRole(role, { name: i18n.t(key) }).click();
}
});

// admin

test('Assert "Create form" button is (only) visible to admin', async({ page }) => {
test('Assert "Create form" button is (only) visible to admin', async ({ page }) => {
test.skip(UPDATE === true, 'Do not run regular tests when updating HAR files');
await assertOnlyVisibleToAdmin(
page, page.getByRole('link', { name: i18n.t('navBarCreateForm')})
page,
page.getByRole('link', { name: i18n.t('navBarCreateForm') })
);
});

test('Assert "Admin" button is (only) visible to admin', async({ page }) => {
test('Assert "Admin" button is (only) visible to admin', async ({ page }) => {
test.skip(UPDATE === true, 'Do not run regular tests when updating HAR files');
await assertOnlyVisibleToAdmin(
page, page.getByRole('link', { name: i18n.t('navBarAdmin') })
);
await assertOnlyVisibleToAdmin(page, page.getByRole('link', { name: i18n.t('navBarAdmin') }));
});
42 changes: 23 additions & 19 deletions web/frontend/tests/shared.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,52 @@
import { default as i18n } from 'i18next';
import { test, expect } from '@playwright/test';
import { expect } from '@playwright/test';
import en from './../src/language/en.json';
import fr from './../src/language/fr.json';
import de from './../src/language/de.json';
import { SCIPER_ADMIN, SCIPER_USER, UPDATE, mockPersonalInfo, mockGetDevLogin, mockLogout } from './mocks';
import {
SCIPER_ADMIN,
SCIPER_USER,
mockGetDevLogin,
mockLogout,
mockPersonalInfo,
mockProxy,
} from './mocks';

export function initI18n () {
export function initI18n() {
i18n.init({
resources: { en, fr, de },
fallbackLng: ['en', 'fr', 'de'],
});
}

export async function setUp(page: any, url: string) {
if (UPDATE === true) {
return;
}
await mockPersonalInfo(page);
await mockProxy(page);
await mockGetDevLogin(page);
await mockLogout(page);
await page.goto(url);
await expect(page).toHaveURL(url); // make sure that page is loaded
await expect(page).toHaveURL(url); // make sure that page is loaded
}

export async function logIn (page: any, sciper: string) {
export async function logIn(page: any, sciper: string) {
await mockPersonalInfo(page, sciper);
await page.reload();
await expect(page).toHaveURL(page.url()); // make sure that page is loaded
await expect(page).toHaveURL(page.url()); // make sure that page is loaded
}

export async function assertOnlyVisibleToAuthenticated (page: any, locator: any) {
await expect(locator).toBeHidden(); // assert is hidden to unauthenticated user
export async function assertOnlyVisibleToAuthenticated(page: any, locator: any) {
await expect(locator).toBeHidden(); // assert is hidden to unauthenticated user
await logIn(page, SCIPER_USER);
await expect(locator).toBeVisible(); // assert is visible to authenticated user
await expect(locator).toBeVisible(); // assert is visible to authenticated user
}

export async function assertOnlyVisibleToAdmin (page: any, locator: any) {
await expect(locator).toBeHidden(); // assert is hidden to unauthenticated user
export async function assertOnlyVisibleToAdmin(page: any, locator: any) {
await expect(locator).toBeHidden(); // assert is hidden to unauthenticated user
await logIn(page, SCIPER_USER);
await expect(locator).toBeHidden(); // assert is hidden to authenticated non-admin user
await expect(locator).toBeHidden(); // assert is hidden to authenticated non-admin user
await logIn(page, SCIPER_ADMIN);
await expect(locator).toBeVisible(); // assert is visible to admin user
await expect(locator).toBeVisible(); // assert is visible to admin user
}

export async function getFooter (page: any) {
return await page.getByTestId('footer');
export async function getFooter(page: any) {
return page.getByTestId('footer');
}
29 changes: 29 additions & 0 deletions web/frontend/tests/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"paths": {
"*": [
"*"
]
},
},
}

0 comments on commit 0863d1d

Please sign in to comment.