diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 000000000..ac206d611 --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,66 @@ +name: E2E +on: + push: + branches: + - main + - develop + paths: + - '**.ts' + - '**.tsx' + - '**/tsconfig.json' + - 'yarn.lock' + - '.github/workflows/e2e.yml' + pull_request: + paths: + - '**.ts' + - '**.tsx' + - '**/tsconfig.json' + - 'yarn.lock' + - '.github/workflows/e2e.yml' + +defaults: + run: + shell: 'bash' + +jobs: + test_setup: + name: Test setup + runs-on: ubuntu-latest + outputs: + preview_url: ${{ steps.waitForVercelPreviewDeployment.outputs.url }} + steps: + - name: Wait for Vercel preview deployment to be ready + uses: patrickedqvist/wait-for-vercel-preview@v1.3.1 + id: waitForVercelPreviewDeployment + with: + token: ${{ secrets.GITHUB_TOKEN }} + max_timeout: 3000 + + test_e2e: + runs-on: ubuntu-latest + needs: test_setup + name: Playwright tests + timeout-minutes: 15 + environment: ${{ vars.ENVIRONMENT_STAGE }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: 14 # Need for npm >=7.7 + cache: 'npm' + + - name: Install dependencies + run: npm install + + - name: Install Playwright with deps + run: npx playwright install --with-deps + + - name: Run tests + run: npm run test:e2e + + - uses: actions/upload-artifact@v2 + if: always() + with: + name: playwright-report + path: test-results/ + retention-days: 30 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9796bfc5d..8e489a403 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ data -.env \ No newline at end of file +.env +test-results/ \ No newline at end of file diff --git a/e2e/authentication.spec.ts b/e2e/authentication.spec.ts new file mode 100644 index 000000000..7495458b7 --- /dev/null +++ b/e2e/authentication.spec.ts @@ -0,0 +1,68 @@ +import { test, expect, Page } from '@playwright/test'; + +let authPage: Page; + +test.describe('authentication', () => { + test.beforeAll(async ({ browser }) => { + authPage = await browser.newPage(); + }); + + test.describe('login', () => { + test.beforeAll(async () => { + await authPage.goto('/auth/login'); + }); + test('should show the login page.', async () => { + await expect(authPage.locator('body')).toContainText( + "Don't have an account? Sign up" + ); + }); + test('should email and password be required.', async () => { + await authPage.getByRole('button', { name: 'Log in' }).click(); + + await expect(authPage.locator('form')).toContainText( + 'Email is a required field' + ); + await expect(authPage.locator('form')).toContainText( + 'Password is a required field' + ); + }); + test('should go to the register page when click on sign up link', async () => { + await authPage.getByRole('link', { name: 'Sign up' }).click(); + await expect(authPage.url()).toContain('/auth/register'); + }); + }); + + test.describe('register', () => { + test.beforeAll(async () => { + await authPage.goto('/auth/register'); + }); + test('should first name, last name, email and password be required.', async () => { + await authPage.getByRole('button', { name: 'Register' }).click(); + + await expect(authPage.locator('form')).toContainText( + 'First name is a required field' + ); + await expect(authPage.locator('form')).toContainText( + 'Last name is a required field' + ); + await expect(authPage.locator('form')).toContainText( + 'Email is a required field' + ); + await expect(authPage.locator('form')).toContainText( + 'Password is a required field' + ); + }); + }); + + test.describe('reset password', () => { + test.beforeAll(async () => { + await authPage.goto('/auth/send_reset_password'); + }); + test('should email be required.', async () => { + await authPage.getByRole('button', { name: 'Reset Password' }).click(); + await expect(authPage.locator('form')).toContainText( + 'Email is a required field' + ); + }); + }); +}); diff --git a/package-lock.json b/package-lock.json index b0c85a129..8bdc5a647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -941,6 +941,23 @@ "esquery": "^1.0.1" } }, + "@playwright/test": { + "version": "1.32.3", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.3.tgz", + "integrity": "sha512-BvWNvK0RfBriindxhLVabi8BRe3X0J9EVjKlcmhxjg4giWBD/xleLcg2dz7Tx0agu28rczjNIPQWznwzDwVsZQ==", + "requires": { + "@types/node": "*", + "fsevents": "2.3.2", + "playwright-core": "1.32.3" + }, + "dependencies": { + "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==" + } + } + }, "@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -986,8 +1003,7 @@ "@types/node": { "version": "18.14.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz", - "integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==", - "dev": true + "integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==" }, "@types/normalize-package-data": { "version": "2.4.1", @@ -2304,6 +2320,12 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", diff --git a/package.json b/package.json index fce7139e6..167676cec 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "dev:server": "lerna run dev --scope \"@bigcapital/server\"", "build:server": "lerna run build --scope \"@bigcapital/server\"", "serve:server": "lerna run serve --scope \"@bigcapital/server\"", + "test:e2e": "playwright test", "prepare": "husky install" }, "workspaces": [ @@ -21,7 +22,8 @@ "@commitlint/config-lerna-scopes": "^17.4.2", "husky": "^8.0.3", "lerna": "^6.4.1", - "@commitlint/cli": "^17.4.2" + "@commitlint/cli": "^17.4.2", + "@playwright/test": "^1.32.3" }, "engines": { "node": "14.x" @@ -30,6 +32,5 @@ "hooks": { "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } - }, - "dependencies": {} + } } diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 000000000..885656169 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,38 @@ +import path from 'path'; +import { PlaywrightTestConfig, devices } from '@playwright/test'; +import dotenv from 'dotenv'; + +dotenv.config(); + +// Reference: https://playwright.dev/docs/test-configuration +const config: PlaywrightTestConfig = { + // Timeout per test + timeout: 60 * 1000, + workers: 1, + // Test directory + testDir: path.join(__dirname, 'e2e'), + // If a test fails, retry it additional 2 times + retries: 0, + // Artifacts folder where screenshots, videos, and traces are stored. + outputDir: 'test-results/', + use: { + // Retry a test if its failing with enabled tracing. This allows you to analyse the DOM, console logs, network traffic etc. + // More information: https://playwright.dev/docs/trace-viewer + trace: 'retain-on-failure', + + // All available context options: https://playwright.dev/docs/api/class-browser#browser-new-context + // contextOptions: { + // ignoreHTTPSErrors: true, + // }, + baseURL: 'http://localhost:4000', + }, + projects: [ + { + name: 'Desktop Chrome', + use: { + ...devices['Desktop Chrome'], + }, + }, + ], +}; +export default config; \ No newline at end of file