diff --git a/apps/web/.env.example b/apps/web/.env.example new file mode 100644 index 000000000..44096e8da --- /dev/null +++ b/apps/web/.env.example @@ -0,0 +1,36 @@ +# [[E2E Tests]] +E2E_TEST_USERNAME="Test" +E2E_TEST_USER_EMAIL="mytestuser@mail.com" +E2E_TEST_USER_PASSWORD="test_password" + +# [[SMTP]] +# OPTIONAL: Defines the transport to use for sending emails. Available options: smtp-auth (default) | smtp-api | mailchannels +NEXT_PRIVATE_SMTP_TRANSPORT="smtp-auth" +# OPTIONAL: Defines the host to use for sending emails. +NEXT_PRIVATE_SMTP_HOST="127.0.0.1" +# OPTIONAL: Defines the port to use for sending emails. +NEXT_PRIVATE_SMTP_PORT=2500 +# OPTIONAL: Defines the username to use with the SMTP server. +NEXT_PRIVATE_SMTP_USERNAME="documenso" +# OPTIONAL: Defines the password to use with the SMTP server. +NEXT_PRIVATE_SMTP_PASSWORD="password" +# OPTIONAL: Defines the API key user to use with the SMTP server. +NEXT_PRIVATE_SMTP_APIKEY_USER= +# OPTIONAL: Defines the API key to use with the SMTP server. +NEXT_PRIVATE_SMTP_APIKEY= +# OPTIONAL: Defines whether to force the use of TLS. +NEXT_PRIVATE_SMTP_SECURE= +# REQUIRED: Defines the sender name to use for the from address. +NEXT_PRIVATE_SMTP_FROM_NAME="No Reply @ Documenso" +# REQUIRED: Defines the email address to use as the from address. +NEXT_PRIVATE_SMTP_FROM_ADDRESS="noreply@documenso.com" +# OPTIONAL: The API key to use for the MailChannels proxy endpoint. +NEXT_PRIVATE_MAILCHANNELS_API_KEY= +# OPTIONAL: The endpoint to use for the MailChannels API if using a proxy. +NEXT_PRIVATE_MAILCHANNELS_ENDPOINT= +# OPTIONAL: The domain to use for DKIM signing. +NEXT_PRIVATE_MAILCHANNELS_DKIM_DOMAIN= +# OPTIONAL: The selector to use for DKIM signing. +NEXT_PRIVATE_MAILCHANNELS_DKIM_SELECTOR= +# OPTIONAL: The private key to use for DKIM signing. +NEXT_PRIVATE_MAILCHANNELS_DKIM_PRIVATE_KEY= diff --git a/apps/web/.github/workflows/playwright.yml b/apps/web/.github/workflows/playwright.yml new file mode 100644 index 000000000..90b6b700d --- /dev/null +++ b/apps/web/.github/workflows/playwright.yml @@ -0,0 +1,27 @@ +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - 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@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/apps/web/.gitignore b/apps/web/.gitignore new file mode 100644 index 000000000..1e2368935 --- /dev/null +++ b/apps/web/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +playwright-report/ diff --git a/apps/web/package.json b/apps/web/package.json index d3ab34f96..cce27f75c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -43,6 +43,7 @@ "zod": "^3.21.4" }, "devDependencies": { + "@playwright/test": "^1.38.0", "@types/formidable": "^2.0.6", "@types/luxon": "^3.3.1", "@types/node": "20.1.0", diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts new file mode 100644 index 000000000..8c921cbeb --- /dev/null +++ b/apps/web/playwright.config.ts @@ -0,0 +1,85 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +import { config as dotenvConfig } from 'dotenv'; + +dotenvConfig(); + +export const STORAGE_STATE = 'playwright/.auth/user.json'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './src/tests/e2e', + /* 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: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost: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: 'setup', testMatch: '**/*.setup.ts' }, + { + name: 'Authenticated User Tests', + testMatch: '*.authenticated.spec.ts', + dependencies: ['setup'], + use: { + ...devices['Desktop Chrome'], + storageState: STORAGE_STATE, + }, + }, + { + name: 'Unauthenticated User Tests', + use: { + ...devices['Desktop Chrome'], + }, + testMatch: '*.unauthenticated.spec.ts', + testIgnore: ['*.setup.ts', '*.authenticated.spec.ts'], + }, + ], + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/apps/web/playwright/.auth/user.json b/apps/web/playwright/.auth/user.json new file mode 100644 index 000000000..251fadb6e --- /dev/null +++ b/apps/web/playwright/.auth/user.json @@ -0,0 +1,45 @@ +{ + "cookies": [ + { + "name": "next-auth.csrf-token", + "value": "4772e794e5f5a82dced421cfe438001a4ee36c905374383a14ff24ea59a53eeb%7Cd73af8796095fa1296860e6979b54c53b94ebbb9553ec2c70b0cbcc4448192d0", + "domain": "localhost", + "path": "/", + "expires": -1, + "httpOnly": true, + "secure": false, + "sameSite": "Lax" + }, + { + "name": "next-auth.callback-url", + "value": "http%3A%2F%2Flocalhost%3A3000%2Fdocuments", + "domain": "localhost", + "path": "/", + "expires": -1, + "httpOnly": true, + "secure": false, + "sameSite": "Lax" + }, + { + "name": "next-auth.session-token", + "value": "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..ZAKfOWExpxch-GkZ.h1-kr4EyMbgfKHz34rsb9PO-upqJOvUYL8e1rVEItSYk_fh4aJPFodW732rVqjNsdXaD2OB0OjKaAxF-0oF264aB5sqiTbaURM8ge8G_5YDgELKi8gzNAK21ta2IaQ57IX-94NhSgVVqhkRcx-Y1kcVdy23vHIzbmhzcP0zMkXVtHtw29zLC_hwLXrY.3fxNbK8FQKwPgWpCxvadeA", + "domain": "localhost", + "path": "/", + "expires": 1697899853.443211, + "httpOnly": true, + "secure": false, + "sameSite": "Lax" + } + ], + "origins": [ + { + "origin": "http://localhost:3000", + "localStorage": [ + { + "name": "nextauth.message", + "value": "{\"event\":\"session\",\"data\":{\"trigger\":\"getSession\"},\"timestamp\":1695307853}" + } + ] + } + ] +} \ No newline at end of file diff --git a/apps/web/src/tests/e2e/auth.setup.ts b/apps/web/src/tests/e2e/auth.setup.ts index eef5bd67f..bc223a54c 100644 --- a/apps/web/src/tests/e2e/auth.setup.ts +++ b/apps/web/src/tests/e2e/auth.setup.ts @@ -1,6 +1,6 @@ import { type Page, expect, test as setup } from '@playwright/test'; -import { STORAGE_STATE } from '../../../../../playwright.config'; +import { STORAGE_STATE } from '../../../playwright.config'; const username = process.env.E2E_TEST_USERNAME || ''; const email = process.env.E2E_TEST_USER_EMAIL || ''; diff --git a/apps/web/src/tests/e2e/test-documents-page.authenticated.spec.ts b/apps/web/src/tests/e2e/test-documents-page.authenticated.spec.ts index 0c98809ab..9d3ea4473 100644 --- a/apps/web/src/tests/e2e/test-documents-page.authenticated.spec.ts +++ b/apps/web/src/tests/e2e/test-documents-page.authenticated.spec.ts @@ -27,7 +27,7 @@ test.describe('Document upload test', () => { await page .getByText('Add a documentDrag & drop your document here.') .locator('input[type=file]') - .setInputFiles('./apps/web/src/tests/e2e/documenso.pdf'); + .setInputFiles('./src/tests/e2e/documenso.pdf'); }); test('user can see /documents page', async ({ page }: { page: Page }) => {