diff --git a/e2e/_utils.ts b/e2e/_utils.ts new file mode 100644 index 000000000..0d5dd8975 --- /dev/null +++ b/e2e/_utils.ts @@ -0,0 +1,13 @@ +import { Page } from '@playwright/test'; + +export const clearLocalStorage = (page: Page) => { + return page.evaluate(`window.localStorage.clear()`); +}; + +export const defaultPageConfig = () => { + return { + extraHTTPHeaders: { + 'ngrok-skip-browser-warning': 'any-value', + }, + }; +}; diff --git a/e2e/authentication.spec.ts b/e2e/authentication.spec.ts index 7495458b7..8e31e3ea1 100644 --- a/e2e/authentication.spec.ts +++ b/e2e/authentication.spec.ts @@ -1,14 +1,23 @@ import { test, expect, Page } from '@playwright/test'; +import { faker } from '@faker-js/faker'; +import { clearLocalStorage, defaultPageConfig } from './_utils'; let authPage: Page; test.describe('authentication', () => { test.beforeAll(async ({ browser }) => { - authPage = await browser.newPage(); + authPage = await browser.newPage({ ...defaultPageConfig() }); + }); + test.afterAll(async () => { + await authPage.close(); + }); + test.afterEach(async ({ context }) => { + context.clearCookies(); + await clearLocalStorage(authPage); }); test.describe('login', () => { - test.beforeAll(async () => { + test.beforeEach(async () => { await authPage.goto('/auth/login'); }); test('should show the login page.', async () => { @@ -30,10 +39,23 @@ test.describe('authentication', () => { await authPage.getByRole('link', { name: 'Sign up' }).click(); await expect(authPage.url()).toContain('/auth/register'); }); + test('should the email or password is not correct.', async () => { + await authPage.getByLabel('Email Address').click(); + await authPage.getByLabel('Email Address').fill(faker.internet.email()); + + await authPage.getByLabel('Password').click(); + await authPage.getByLabel('Password').fill(faker.internet.password()); + + await authPage.getByRole('button', { name: 'Log in' }).click(); + + await expect(authPage.locator('body')).toContainText( + 'The email and password you entered did not match our records.' + ); + }); }); test.describe('register', () => { - test.beforeAll(async () => { + test.beforeEach(async () => { await authPage.goto('/auth/register'); }); test('should first name, last name, email and password be required.', async () => { @@ -52,10 +74,36 @@ test.describe('authentication', () => { 'Password is a required field' ); }); + test('should signup successfully.', async () => { + const form = authPage.locator('form'); + await form.getByLabel('First Name').click(); + await form.getByLabel('First Name').fill(faker.person.firstName()); + + await form.getByLabel('Email').click(); + await form.getByLabel('Email').fill(faker.internet.email()); + + await form.getByLabel('Last Name').click(); + await form.getByLabel('Last Name').fill(faker.person.lastName()); + + await form.getByLabel('Password').click(); + await form.getByLabel('Password').fill(faker.internet.password()); + + await authPage.getByRole('button', { name: 'Register' }).click(); + + await expect(authPage.locator('h1')).toContainText( + 'Register a New Organization now!' + ); + }); }); test.describe('reset password', () => { - test.beforeAll(async () => { + test.beforeAll(async ({ browser }) => { + authPage = await browser.newPage({ ...defaultPageConfig() }); + }); + test.afterAll(async () => { + await authPage.close(); + }); + test.beforeEach(async () => { await authPage.goto('/auth/send_reset_password'); }); test('should email be required.', async () => { diff --git a/e2e/onboarding.spec.ts b/e2e/onboarding.spec.ts new file mode 100644 index 000000000..e80ae7f19 --- /dev/null +++ b/e2e/onboarding.spec.ts @@ -0,0 +1,86 @@ +import { test, expect, Page } from '@playwright/test'; +import { faker } from '@faker-js/faker'; +import { defaultPageConfig } from './_utils'; + +let authPage: Page; +let businessLegalName: string = faker.company.name(); + +test.describe('onboarding', () => { + test.beforeAll(async ({ browser }) => { + authPage = await browser.newPage({ ...defaultPageConfig() }); + await authPage.goto('/auth/register'); + + const form = authPage.locator('form'); + + await form.getByLabel('First Name').fill(faker.person.firstName()); + await form.getByLabel('Email').fill(faker.internet.email()); + await form.getByLabel('Last Name').fill(faker.person.lastName()); + await form.getByLabel('Password').fill(faker.internet.password()); + + await authPage.getByRole('button', { name: 'Register' }).click(); + }); + test('should validation catch all required fields', async () => { + const form = authPage.locator('form'); + + await authPage.getByRole('button', { name: 'Save & Continue' }).click(); + + await expect(form).toContainText('Organization name is a required field'); + await expect(form).toContainText('Location is a required field'); + await expect(form).toContainText('Base currency is a required field'); + await expect(form).toContainText('Fiscal year is a required field'); + await expect(form).toContainText('Time zone is a required field'); + }); + test.describe('after onboarding', () => { + test.beforeAll(async () => { + await authPage.getByLabel('Legal Organization Name').click(); + await authPage + .getByLabel('Legal Organization Name') + .fill(businessLegalName); + + // Fill Business Location. + await authPage + .getByRole('button', { name: 'Select Business Location...' }) + .click(); + await authPage.locator('a').filter({ hasText: 'Albania' }).click(); + + // Fill Base Currency. + await authPage + .getByRole('button', { name: 'Select Base Currency...' }) + .click(); + await authPage + .locator('a') + .filter({ hasText: 'AED - United Arab Emirates Dirham' }) + .click(); + + // Fill Fasical Year. + await authPage + .getByRole('button', { name: 'Select Fiscal Year...' }) + .click(); + await authPage.locator('a').filter({ hasText: 'June - May' }).click(); + + // Fill Timezone. + await authPage + .getByRole('button', { name: 'Select Time Zone...' }) + .click(); + await authPage.getByText('Pacific/Marquesas-09:30').click(); + + // Click on Submit button + await authPage.getByRole('button', { name: 'Save & Continue' }).click(); + }); + test('should onboarding process success', async () => { + await expect(authPage.locator('body')).toContainText( + 'Congrats! You are ready to go', + { + timeout: 30000, + } + ); + }); + test('should go to the dashboard after clicking on "Go to dashboard" button.', async () => { + await authPage.getByRole('button', { name: 'Go to dashboard' }).click(); + + await expect( + authPage.locator('[data-testId="dashboard-topbar"] h1') + ).toContainText(businessLegalName); + }); + }); +}); diff --git a/package-lock.json b/package-lock.json index 8bdc5a647..b7080f99c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -333,6 +333,12 @@ "@jridgewell/trace-mapping": "0.3.9" } }, + "@faker-js/faker": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.0.2.tgz", + "integrity": "sha512-Uo3pGspElQW91PCvKSIAXoEgAUlRnH29sX2/p89kg7sP1m2PzCufHINd0FhTXQf6DYGiUlVncdSPa2F9wxed2A==", + "dev": true + }, "@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -945,6 +951,7 @@ "version": "1.32.3", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.3.tgz", "integrity": "sha512-BvWNvK0RfBriindxhLVabi8BRe3X0J9EVjKlcmhxjg4giWBD/xleLcg2dz7Tx0agu28rczjNIPQWznwzDwVsZQ==", + "dev": true, "requires": { "@types/node": "*", "fsevents": "2.3.2", @@ -954,7 +961,8 @@ "playwright-core": { "version": "1.32.3", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.32.3.tgz", - "integrity": "sha512-SB+cdrnu74ZIn5Ogh/8278ngEh9NEEV0vR4sJFmK04h2iZpybfbqBY0bX6+BLYWVdV12JLLI+JEFtSnYgR+mWg==" + "integrity": "sha512-SB+cdrnu74ZIn5Ogh/8278ngEh9NEEV0vR4sJFmK04h2iZpybfbqBY0bX6+BLYWVdV12JLLI+JEFtSnYgR+mWg==", + "dev": true } } }, @@ -1003,7 +1011,8 @@ "@types/node": { "version": "18.14.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz", - "integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==" + "integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==", + "dev": true }, "@types/normalize-package-data": { "version": "2.4.1", @@ -2324,6 +2333,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "optional": true }, "function-bind": { diff --git a/package.json b/package.json index 167676cec..ebdcc185a 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,13 @@ "shared/*" ], "devDependencies": { + "@commitlint/cli": "^17.4.2", "@commitlint/config-conventional": "^17.4.2", "@commitlint/config-lerna-scopes": "^17.4.2", + "@faker-js/faker": "^8.0.2", + "@playwright/test": "^1.32.3", "husky": "^8.0.3", - "lerna": "^6.4.1", - "@commitlint/cli": "^17.4.2", - "@playwright/test": "^1.32.3" + "lerna": "^6.4.1" }, "engines": { "node": "14.x" diff --git a/packages/webapp/package-lock.json b/packages/webapp/package-lock.json index 1c08f64d6..86134863b 100644 --- a/packages/webapp/package-lock.json +++ b/packages/webapp/package-lock.json @@ -1205,9 +1205,9 @@ } }, "@blueprintjs-formik/core": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@blueprintjs-formik/core/-/core-0.3.3.tgz", - "integrity": "sha512-ko7g54YSEcSq2K/GEpmiTG0foGLqe7DwgXGhkGxYEiHhLAUv8WvQmrFsm8e/KOW7n8mLGq0uaZVe2l8m3JTGGQ==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@blueprintjs-formik/core/-/core-0.3.4.tgz", + "integrity": "sha512-gksuBYXXvX7IhZXbPEFEAHgmJWp1vt/GTMW0GYBkCoGBAvXy08hIHjMc85M0WNyen+tic3NRas6I2wsjgokgqw==", "requires": { "lodash.get": "^4.4.2", "lodash.keyby": "^4.6.0", @@ -1227,9 +1227,9 @@ } }, "@blueprintjs-formik/select": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@blueprintjs-formik/select/-/select-0.2.4.tgz", - "integrity": "sha512-zB28/hLkuO5zZXkjFmqfiVDGW+uvj9b8e6kHh9aOfY70edSFIfw3bj+NYR7BaDZoIYu3KuZQDFGqgs23ua5Z1g==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@blueprintjs-formik/select/-/select-0.2.5.tgz", + "integrity": "sha512-Sztf5dOemedUBfEjnDWD8ryfMU/x95hyhIgJT5/ywC/jQfX+d/K2OhujklTrCDzQilUeAJLoVkSdV+w77n8ckQ==", "requires": { "lodash.get": "^4.4.2", "lodash.keyby": "^4.6.0", @@ -17751,4 +17751,4 @@ "integrity": "sha512-7UlRWU4Q3uCMCeDVMOm7eBrIu145OqsIJ3p6zq58l8UsSYwKWxc6zEapC5YA9tIeh0oheb4cT9Kk2Wq353loFg==" } } -} +} \ No newline at end of file diff --git a/packages/webapp/package.json b/packages/webapp/package.json index 4a46c7da5..96ac5d16f 100644 --- a/packages/webapp/package.json +++ b/packages/webapp/package.json @@ -3,9 +3,9 @@ "version": "0.9.6", "private": true, "dependencies": { - "@blueprintjs-formik/core": "^0.3.3", + "@blueprintjs-formik/core": "^0.3.4", "@blueprintjs-formik/datetime": "^0.3.4", - "@blueprintjs-formik/select": "^0.2.4", + "@blueprintjs-formik/select": "^0.2.5", "@blueprintjs/core": "^3.50.2", "@blueprintjs/datetime": "^3.23.12", "@blueprintjs/popover2": "^0.11.1", diff --git a/packages/webapp/src/components/Dashboard/DashboardTopbar/DashboardTopbar.tsx b/packages/webapp/src/components/Dashboard/DashboardTopbar/DashboardTopbar.tsx index c9e8e6946..549785a87 100644 --- a/packages/webapp/src/components/Dashboard/DashboardTopbar/DashboardTopbar.tsx +++ b/packages/webapp/src/components/Dashboard/DashboardTopbar/DashboardTopbar.tsx @@ -53,7 +53,7 @@ function DashboardTopbar({ }; return ( -
+