diff --git a/apps/web/global.teardown.ts b/apps/web/global.teardown.ts new file mode 100644 index 000000000..5e6b7e941 --- /dev/null +++ b/apps/web/global.teardown.ts @@ -0,0 +1,11 @@ +import { deleteUser } from '@documenso/lib/server-only/user/delete-user'; + +async function teardown() { + try { + await deleteUser(process.env.E2E_TEST_USERNAME); + } catch (e) { + throw new Error(`Error deleting user: ${e}`); + } +} + +export default teardown; diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts index 8c921cbeb..9ad0407b2 100644 --- a/apps/web/playwright.config.ts +++ b/apps/web/playwright.config.ts @@ -32,6 +32,7 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', + video: 'retain-on-failure', }, /* Configure projects for major browsers */ @@ -54,32 +55,11 @@ export default defineConfig({ testMatch: '*.unauthenticated.spec.ts', testIgnore: ['*.setup.ts', '*.authenticated.spec.ts'], }, + { + name: 'cleanup db', + testMatch: /global.teardown\.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, - // }, + globalTeardown: './global.teardown.ts', }); 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 9d3ea4473..1c88fdb1b 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 @@ -1,66 +1,42 @@ -import { type Page, expect, test } from '@playwright/test'; +import path from 'path'; -/* - There was a bit of code duplication because each test starts from the first step - which is uploading a document. - Then each subsequent test adds a new step. So, for each test we repeat the steps from the previous tests. +import { expect, test } from '../test-fixtures/documents-page/documents-page'; - I extracted the most common steps into functions and then I call them in each test. - Also, the document upload is used in all tests, so I added it to the beforeEach hook. -*/ -const addSigner = async (page: Page) => { - await page.getByLabel('Email*').fill('example@email.com'); - await page.getByLabel('Name').fill('User'); - await page.getByRole('button', { name: 'Continue' }).click(); -}; +const signer_name = process.env.E2E_TEST_SIGNER_NAME; +const signer_email = process.env.E2E_TEST_SIGNER_EMAIL; +const signing_subject = process.env.E2E_TEST_SIGNING_SUBJECT; +const signing_message = process.env.E2E_TEST_SIGNING_MESSAGE; -const addSignatureField = async (page: Page) => { - await page.getByRole('button', { name: 'User Signature' }).dragTo(page.locator('canvas')); - await page.getByRole('button', { name: 'Continue' }).click(); -}; +if (!signer_name || !signer_email || !signing_subject || !signing_message) { + throw new Error('Required environment variables for tests are not defined'); +} test.describe('Document upload test', () => { - test.beforeEach(async ({ page }: { page: Page }) => { - await page.goto('/'); - - await expect(page).toHaveTitle('Documenso - The Open Source DocuSign Alternative'); - - await page - .getByText('Add a documentDrag & drop your document here.') - .locator('input[type=file]') - .setInputFiles('./src/tests/e2e/documenso.pdf'); + test.beforeEach(async ({ documentsPage }) => { + await documentsPage.uploadDocument(path.join(__dirname, './documenso.pdf')); }); - test('user can see /documents page', async ({ page }: { page: Page }) => { - await page.goto('/'); - + test('user can see /documents page', async ({ page, documentsPage }) => { + await documentsPage.goToDocumentsPage(); await expect(page).toHaveTitle('Documenso - The Open Source DocuSign Alternative'); }); - test('user can add 1 signer', async ({ page }: { page: Page }) => { - await addSigner(page); + test('user can upload a document succesfully', async ({ page }) => { + await expect(page.locator('canvas')).toBeVisible(); }); - test('user can add signature field', async ({ page }: { page: Page }) => { - await addSigner(page); - - await addSignatureField(page); + test('user can add 1 signer', async ({ documentsPage }) => { + await documentsPage.addSigner(signer_email, signer_name); }); - test('user can add subject and message', async ({ page }: { page: Page }) => { - await addSigner(page); + test('user can add signature field', async ({ documentsPage }) => { + await documentsPage.addSigner(signer_email, signer_name); + await documentsPage.addSignatureField(signer_name); + }); - await addSignatureField(page); - - await page - .locator('div') - .filter({ hasText: /^Subject \(Optional\)$/ }) - .locator('input') - .fill('New document'); - await page - .locator('div') - .filter({ hasText: /^Message \(Optional\)$/ }) - .locator('textarea') - .fill('Please sign it in and send it back to me.'); - await page.getByRole('button', { name: 'Send' }).click(); + test('user can add subject and message', async ({ documentsPage }) => { + await documentsPage.addSigner(signer_email, signer_name); + await documentsPage.addSignatureField(signer_name); + await documentsPage.addSubjectAndMessage(signing_subject, signing_message); }); }); diff --git a/apps/web/src/tests/test-fixtures/documents-page/DocumentsPageObject.ts b/apps/web/src/tests/test-fixtures/documents-page/DocumentsPageObject.ts new file mode 100644 index 000000000..ec4ab7710 --- /dev/null +++ b/apps/web/src/tests/test-fixtures/documents-page/DocumentsPageObject.ts @@ -0,0 +1,48 @@ +import type { Locator, Page } from '@playwright/test'; + +export class DocumentsPage { + private readonly fileInput: Locator; + private readonly subject: Locator; + private readonly message: Locator; + + constructor(public readonly page: Page) { + this.fileInput = this.page.locator('input[type=file]'); + this.subject = this.page + .locator('div') + .filter({ hasText: /^Subject \(Optional\)$/ }) + .locator('input'); + this.message = this.page + .locator('div') + .filter({ hasText: /^Message \(Optional\)$/ }) + .locator('textarea'); + } + + async goToDocumentsPage() { + await this.page.goto('/documents'); + } + + async uploadDocument(filePath: string) { + await this.goToDocumentsPage(); + + await this.fileInput.setInputFiles(filePath); + } + + async addSigner(email: string, name: string) { + await this.page.getByLabel('Email*').fill(email); + await this.page.getByLabel('Name').fill(name); + await this.page.getByRole('button', { name: 'Continue' }).click(); + } + + async addSignatureField(name: string) { + await this.page + .getByRole('button', { name: `${name} Signature` }) + .dragTo(this.page.locator('canvas')); + await this.page.getByRole('button', { name: 'Continue' }).click(); + } + + async addSubjectAndMessage(subject: string, message: string) { + await this.subject.fill(subject); + await this.message.fill(message); + await this.page.getByRole('button', { name: 'Send' }).click(); + } +} diff --git a/apps/web/src/tests/test-fixtures/documents-page/documents-page.ts b/apps/web/src/tests/test-fixtures/documents-page/documents-page.ts new file mode 100644 index 000000000..c65d05e18 --- /dev/null +++ b/apps/web/src/tests/test-fixtures/documents-page/documents-page.ts @@ -0,0 +1,13 @@ +import { test as base } from '@playwright/test'; + +import { DocumentsPage } from './DocumentsPageObject'; + +export const test = base.extend<{ documentsPage: DocumentsPage }>({ + documentsPage: async ({ page }, use) => { + const documentsPage = new DocumentsPage(page); + + await use(documentsPage); + }, +}); + +export { expect } from '@playwright/test';