diff --git a/README.docker.md b/README.docker.md index e99fef976..23f8ff29e 100644 --- a/README.docker.md +++ b/README.docker.md @@ -21,7 +21,7 @@ DATABASE_PASSWORD=XXX # choose any PostgreSQL password DATABASE_HOST=db DATABASE_PORT=5432 DB_PATH=dvoting # LMDB database path -FRONT_END_URL=http://127.0.0.1:3000 +FRONT_END_URL=http://127.0.0.1:3000 # the automated frontend tests expect this value do not change it BACKEND_HOST=backend BACKEND_PORT=5000 SESSION_SECRET=XXX # choose any secret diff --git a/web/frontend/.gitignore b/web/frontend/.gitignore index f2e59e5f7..0f2aa48fc 100644 --- a/web/frontend/.gitignore +++ b/web/frontend/.gitignore @@ -25,4 +25,7 @@ yarn-error.log* .idea .vscode - +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/web/frontend/README.md b/web/frontend/README.md new file mode 100644 index 000000000..6625e1319 --- /dev/null +++ b/web/frontend/README.md @@ -0,0 +1,52 @@ +# Automated Tests w/ Playwright + +## Setup + +To install Playwright run + +``` +npm ci +npx playwright install +``` + +which will install the module and its dependencies. In +order to execute the environment necessary for the tests, +additional system dependencies need to be installed. + +Run + +``` +npx playwright install-deps --dry-run +``` + +to be shown the dependencies that you need to install on your machine (requires `root` access). + +Your local frontend must be accessible at `http://127.0.0.1:3000`. + +## Run tests + +Run + +``` +npx playwright test +``` + +to run the tests. This will open a window in your browser w/ the test results. + +To run interactive tests, run + +``` +npx playwright test --ui +``` + +this will open an user interface where you can interactively run and evaluate tests. + +## Update HAR files + +To update the HAR files, you need to make sure + +* that a complete D-Voting setup is running, as the API will be called for real, and +* the `REACT_APP_SCIPER_ADMIN` of the D-Voting instance value is set to `123456` (i.e. the `SCIPER_ADMIN` value in the mocks). + +You then change the `UPDATE = false` value in `tests/mocks.ts` to `UPDATE = true` and execute +the tests as usual. The tests that update the mocks will be run and the other tests will be skipped. diff --git a/web/frontend/package-lock.json b/web/frontend/package-lock.json index 30dcfa2ce..3d558a594 100644 --- a/web/frontend/package-lock.json +++ b/web/frontend/package-lock.json @@ -31,6 +31,7 @@ "yup": "^0.32.11" }, "devDependencies": { + "@playwright/test": "^1.40.1", "@testing-library/jest-dom": "^5.16.2", "@testing-library/react": "^12.1.5", "@types/file-saver": "^2.0.5", @@ -2865,6 +2866,21 @@ "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==", "dev": true }, + "node_modules/@playwright/test": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", + "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", + "dev": true, + "dependencies": { + "playwright": "1.40.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.7.tgz", @@ -12221,6 +12237,36 @@ "node": ">=6" } }, + "node_modules/playwright": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", + "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", + "dev": true, + "dependencies": { + "playwright-core": "1.40.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", + "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/postcss": { "version": "8.4.14", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", @@ -18706,6 +18752,15 @@ "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==", "dev": true }, + "@playwright/test": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", + "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", + "dev": true, + "requires": { + "playwright": "1.40.1" + } + }, "@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.7.tgz", @@ -25657,6 +25712,22 @@ } } }, + "playwright": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", + "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.40.1" + } + }, + "playwright-core": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", + "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", + "dev": true + }, "postcss": { "version": "8.4.14", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", diff --git a/web/frontend/package.json b/web/frontend/package.json index b3a63d718..ccb6f6526 100644 --- a/web/frontend/package.json +++ b/web/frontend/package.json @@ -23,6 +23,7 @@ "file-saver": "^2.0.5", "i18next": "^21.6.10", "i18next-browser-languagedetector": "^6.1.3", + "pg": "^8.11.1", "prop-types": "^15.8.1", "react": "^17.0.1", "react-beautiful-dnd": "^13.1.0", @@ -33,10 +34,10 @@ "react-scripts": "5.0.0", "short-unique-id": "^4.4.4", "web-vitals": "^2.1.4", - "yup": "^0.32.11", - "pg": "^8.11.1" + "yup": "^0.32.11" }, "devDependencies": { + "@playwright/test": "^1.40.1", "@testing-library/jest-dom": "^5.16.2", "@testing-library/react": "^12.1.5", "@types/file-saver": "^2.0.5", diff --git a/web/frontend/playwright.config.ts b/web/frontend/playwright.config.ts new file mode 100644 index 000000000..e29a0ce76 --- /dev/null +++ b/web/frontend/playwright.config.ts @@ -0,0 +1,38 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + baseURL: 'http://127.0.0.1:3000', + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + ], + +}); diff --git a/web/frontend/src/layout/NavBar.tsx b/web/frontend/src/layout/NavBar.tsx index 3ac090269..fc2721824 100644 --- a/web/frontend/src/layout/NavBar.tsx +++ b/web/frontend/src/layout/NavBar.tsx @@ -179,7 +179,6 @@ const LeftSideNavBar = ({ authCtx, t }) => (
- Workflow Workflow
diff --git a/web/frontend/tests/hars/123456/a03037964db4d746517f562ccb09b049db7f7a12.json b/web/frontend/tests/hars/123456/a03037964db4d746517f562ccb09b049db7f7a12.json new file mode 100644 index 000000000..18abd32b4 --- /dev/null +++ b/web/frontend/tests/hars/123456/a03037964db4d746517f562ccb09b049db7f7a12.json @@ -0,0 +1 @@ +{"sciper":123456,"lastName":"123456","firstName":"sciper-#","isLoggedIn":true,"authorization":{"roles":["add","list","remove"],"proxies":["post","put","delete"],"election":["create"]}} \ No newline at end of file diff --git a/web/frontend/tests/hars/123456/get_dev_login.har b/web/frontend/tests/hars/123456/get_dev_login.har new file mode 100644 index 000000000..3b9cb5b4c --- /dev/null +++ b/web/frontend/tests/hars/123456/get_dev_login.har @@ -0,0 +1,14 @@ +{ + "log": { + "version": "1.2", + "creator": { + "name": "Playwright", + "version": "1.40.1" + }, + "browser": { + "name": "chromium", + "version": "120.0.6099.28" + }, + "entries": [] + } +} \ No newline at end of file diff --git a/web/frontend/tests/hars/123456/personal_info.har b/web/frontend/tests/hars/123456/personal_info.har new file mode 100644 index 000000000..1482d5dda --- /dev/null +++ b/web/frontend/tests/hars/123456/personal_info.har @@ -0,0 +1,61 @@ +{ + "log": { + "version": "1.2", + "creator": { + "name": "Playwright", + "version": "1.40.1" + }, + "browser": { + "name": "chromium", + "version": "120.0.6099.28" + }, + "entries": [ + { + "startedDateTime": "2023-12-07T15:16:05.427Z", + "time": 63.36, + "request": { + "method": "GET", + "url": "http://127.0.0.1:3000/api/personal_info", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { "name": "Accept", "value": "*/*" }, + { "name": "Accept-Language", "value": "en-US" }, + { "name": "Cookie", "value": "connect.sid=s%3AxeUPf49ZYc9AUrUtzig9-O4g0Z_RVtVV.ChZVAx%2FL%2BIzfgIlkxzUxTLVduImdZIp68OdvODtveTU" }, + { "name": "Referer", "value": "http://127.0.0.1:3000/about" }, + { "name": "User-Agent", "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36" } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { "name": "access-control-allow-origin", "value": "*" }, + { "name": "connection", "value": "keep-alive" }, + { "name": "content-length", "value": "184" }, + { "name": "content-type", "value": "application/json; charset=utf-8" }, + { "name": "date", "value": "Thu, 07 Dec 2023 15:16:05 GMT" }, + { "name": "etag", "value": "W/\"b8-oDA3lk2010ZRf1YsywmwSdt/ehI\"" }, + { "name": "keep-alive", "value": "timeout=5" }, + { "name": "x-powered-by", "value": "Express" } + ], + "content": { + "size": -1, + "mimeType": "application/json; charset=utf-8", + "_file": "a03037964db4d746517f562ccb09b049db7f7a12.json" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { "send": -1, "wait": -1, "receive": 63.36 } + } + ] + } +} \ No newline at end of file diff --git a/web/frontend/tests/hars/789012/5f05240cb33cb9e581ea0ce24c4e728a04e2a5d1.json b/web/frontend/tests/hars/789012/5f05240cb33cb9e581ea0ce24c4e728a04e2a5d1.json new file mode 100644 index 000000000..fbbb5fb20 --- /dev/null +++ b/web/frontend/tests/hars/789012/5f05240cb33cb9e581ea0ce24c4e728a04e2a5d1.json @@ -0,0 +1 @@ +{"sciper":789012,"lastName":"789012","firstName":"sciper-#","isLoggedIn":true,"authorization":{}} \ No newline at end of file diff --git a/web/frontend/tests/hars/789012/get_dev_login.har b/web/frontend/tests/hars/789012/get_dev_login.har new file mode 100644 index 000000000..3b9cb5b4c --- /dev/null +++ b/web/frontend/tests/hars/789012/get_dev_login.har @@ -0,0 +1,14 @@ +{ + "log": { + "version": "1.2", + "creator": { + "name": "Playwright", + "version": "1.40.1" + }, + "browser": { + "name": "chromium", + "version": "120.0.6099.28" + }, + "entries": [] + } +} \ No newline at end of file diff --git a/web/frontend/tests/hars/789012/personal_info.har b/web/frontend/tests/hars/789012/personal_info.har new file mode 100644 index 000000000..2f68ed6a7 --- /dev/null +++ b/web/frontend/tests/hars/789012/personal_info.har @@ -0,0 +1,61 @@ +{ + "log": { + "version": "1.2", + "creator": { + "name": "Playwright", + "version": "1.40.1" + }, + "browser": { + "name": "chromium", + "version": "120.0.6099.28" + }, + "entries": [ + { + "startedDateTime": "2023-12-07T15:14:39.129Z", + "time": 56.608, + "request": { + "method": "GET", + "url": "http://127.0.0.1:3000/api/personal_info", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { "name": "Accept", "value": "*/*" }, + { "name": "Accept-Language", "value": "en-US" }, + { "name": "Cookie", "value": "connect.sid=s%3AAN1F283daySUs-3ytVagTfigWaedTgqO.iahChcGgJKx0khpkcm1jBBrQ89QsRN5cW9tOWnIgKgs" }, + { "name": "Referer", "value": "http://127.0.0.1:3000/about" }, + { "name": "User-Agent", "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36" } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { "name": "access-control-allow-origin", "value": "*" }, + { "name": "connection", "value": "keep-alive" }, + { "name": "content-length", "value": "97" }, + { "name": "content-type", "value": "application/json; charset=utf-8" }, + { "name": "date", "value": "Thu, 07 Dec 2023 15:14:39 GMT" }, + { "name": "etag", "value": "W/\"61-XwUkDLM8ueWB6gziTE5yigTipdE\"" }, + { "name": "keep-alive", "value": "timeout=5" }, + { "name": "x-powered-by", "value": "Express" } + ], + "content": { + "size": -1, + "mimeType": "application/json; charset=utf-8", + "_file": "5f05240cb33cb9e581ea0ce24c4e728a04e2a5d1.json" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { "send": -1, "wait": -1, "receive": 56.608 } + } + ] + } +} \ No newline at end of file diff --git a/web/frontend/tests/hars/anonymous/9fd2ff4fc1fb163717539fb04aec420feeb11e5a.html b/web/frontend/tests/hars/anonymous/9fd2ff4fc1fb163717539fb04aec420feeb11e5a.html new file mode 100644 index 000000000..e2c1c215e --- /dev/null +++ b/web/frontend/tests/hars/anonymous/9fd2ff4fc1fb163717539fb04aec420feeb11e5a.html @@ -0,0 +1 @@ +Unauthenticated \ No newline at end of file diff --git a/web/frontend/tests/hars/anonymous/personal_info.har b/web/frontend/tests/hars/anonymous/personal_info.har new file mode 100644 index 000000000..91474fd6a --- /dev/null +++ b/web/frontend/tests/hars/anonymous/personal_info.har @@ -0,0 +1,60 @@ +{ + "log": { + "version": "1.2", + "creator": { + "name": "Playwright", + "version": "1.40.1" + }, + "browser": { + "name": "chromium", + "version": "120.0.6099.28" + }, + "entries": [ + { + "startedDateTime": "2023-12-07T15:11:00.609Z", + "time": 50.718, + "request": { + "method": "GET", + "url": "http://127.0.0.1:3000/api/personal_info", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { "name": "Accept", "value": "*/*" }, + { "name": "Accept-Language", "value": "en-US" }, + { "name": "Referer", "value": "http://127.0.0.1:3000/about" }, + { "name": "User-Agent", "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36" } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 401, + "statusText": "Unauthorized", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { "name": "connection", "value": "keep-alive" }, + { "name": "content-length", "value": "15" }, + { "name": "content-type", "value": "text/html; charset=utf-8" }, + { "name": "date", "value": "Thu, 07 Dec 2023 15:11:00 GMT" }, + { "name": "etag", "value": "W/\"f-n9L/T8H7FjcXU5+wSuxCD+6xHlo\"" }, + { "name": "keep-alive", "value": "timeout=5" }, + { "name": "set-cookie", "value": "connect.sid=s%3AQhEmafnnGB6uPjxRFKwNdJg9MDQYmbkB.r0PBLNSKHdFLqMKbOVv%2FrgQVtN1i6JnbLElS%2Fzt7q%2F8; Path=/; Expires=Fri, 08 Dec 2023 15:11:00 GMT; HttpOnly" }, + { "name": "x-powered-by", "value": "Express" } + ], + "content": { + "size": -1, + "mimeType": "text/html; charset=utf-8", + "_file": "9fd2ff4fc1fb163717539fb04aec420feeb11e5a.html" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { "send": -1, "wait": -1, "receive": 50.718 } + } + ] + } +} \ No newline at end of file diff --git a/web/frontend/tests/mocks.ts b/web/frontend/tests/mocks.ts new file mode 100644 index 000000000..fdbef3b07 --- /dev/null +++ b/web/frontend/tests/mocks.ts @@ -0,0 +1,43 @@ +export const SCIPER_ADMIN = '123456'; +export const SCIPER_USER = '789012'; +export const UPDATE = false; + +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, + }); +} + +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({});} + ); + } +} + +export async function mockLogout (page: any) { + await page.route( + '/api/logout', + async route => {await route.fulfill({});} + ); +} diff --git a/web/frontend/tests/navigation.spec.ts b/web/frontend/tests/navigation.spec.ts new file mode 100644 index 000000000..06d96a527 --- /dev/null +++ b/web/frontend/tests/navigation.spec.ts @@ -0,0 +1,112 @@ +import { default as i18n } from 'i18next'; +import { test, expect } from '@playwright/test'; +import { + initI18n, + setUp, + logIn, + logOut, + assertOnlyVisibleToAuthenticated, + assertOnlyVisibleToAdmin, +} from './shared'; +import { SCIPER_ADMIN, SCIPER_USER, UPDATE, mockPersonalInfo, mockLogout } 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.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.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.skip(UPDATE === false, 'Do not update HAR files'); + await mockPersonalInfo(page, SCIPER_ADMIN); + await page.context().request.get(`/api/get_dev_login/${SCIPER_ADMIN}`); + await setUp(page, '/about'); +}); + +// unauthenticated + +test('Assert cookie is set', async({ page }) => { + const cookies = await page.context().cookies(); + expect(cookies.find(cookie => cookie.name === 'connect.sid')).toBeTruthy(); +}); + +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.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 }) => { + const loginRequest = 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.skip(UPDATE === true, 'Do not run regular tests when updating HAR files'); + await assertOnlyVisibleToAuthenticated( + page, page.getByRole('button', { name: i18n.t('Profile') }) + ); +}); + +test('Assert "Logout" calls logout API', async({ page, baseURL }) => { + await mockLogout(page); + await logIn(page, SCIPER_USER); + const logoutRequestPromise = page.waitForRequest( + request => request.url() === `${baseURL}/api/logout` && request.method() === 'POST' + ); + 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.skip(UPDATE === true, 'Do not run regular tests when updating HAR files'); + await assertOnlyVisibleToAdmin( + page, page.getByRole('link', { name: i18n.t('navBarCreateForm')}) + ); +}); + +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') }) + ); +}); diff --git a/web/frontend/tests/shared.ts b/web/frontend/tests/shared.ts new file mode 100644 index 000000000..d33f532c1 --- /dev/null +++ b/web/frontend/tests/shared.ts @@ -0,0 +1,40 @@ +import { default as i18n } from 'i18next'; +import { test, 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, mockPersonalInfo, mockGetDevLogin, mockLogout } from './mocks'; + +export function initI18n () { + i18n.init({ + resources: { en, fr, de }, + fallbackLng: ['en', 'fr', 'de'], + }); +} + +export async function setUp(page: any, url: string) { + await mockGetDevLogin(page); + await mockLogout(page); + await page.goto(url); + await expect(page).toHaveURL(url); // make sure that page is loaded +} + +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 +} + +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 +} + +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 logIn(page, SCIPER_ADMIN); + await expect(locator).toBeVisible(); // assert is visible to admin user +}