diff --git a/packages/app-tests/e2e/api/v1/template-field-prefill.spec.ts b/packages/app-tests/e2e/api/v1/template-field-prefill.spec.ts index c40605fa9..b67645820 100644 --- a/packages/app-tests/e2e/api/v1/template-field-prefill.spec.ts +++ b/packages/app-tests/e2e/api/v1/template-field-prefill.spec.ts @@ -197,7 +197,7 @@ test.describe('Template Field Prefill API v1', () => { id: numberField.id, type: 'number', label: 'Prefilled Number', - value: '42', + value: '98765', }, { id: radioField.id, @@ -256,7 +256,7 @@ test.describe('Template Field Prefill API v1', () => { expect(documentNumberField?.fieldMeta).toMatchObject({ type: 'number', label: 'Prefilled Number', - value: '42', + value: '98765', }); const documentRadioField = document?.fields.find( @@ -329,7 +329,7 @@ test.describe('Template Field Prefill API v1', () => { await expect(page.getByText('This is prefilled')).toBeVisible(); // Number field - await expect(page.getByText('42')).toBeVisible(); + await expect(page.getByText('98765', { exact: true })).toBeVisible(); // Radio field await expect(page.getByText('Option A')).toBeVisible(); @@ -383,7 +383,7 @@ test.describe('Template Field Prefill API v1', () => { // 5. Add fields to the template // Add TEXT field - const textField = await prisma.field.create({ + await prisma.field.create({ data: { templateId: template.id, recipientId: recipient.id, @@ -403,7 +403,7 @@ test.describe('Template Field Prefill API v1', () => { }); // Add NUMBER field - const numberField = await prisma.field.create({ + await prisma.field.create({ data: { templateId: template.id, recipientId: recipient.id, diff --git a/packages/app-tests/e2e/api/v2/template-field-prefill.spec.ts b/packages/app-tests/e2e/api/v2/template-field-prefill.spec.ts index 6288ae5cf..ec83008aa 100644 --- a/packages/app-tests/e2e/api/v2/template-field-prefill.spec.ts +++ b/packages/app-tests/e2e/api/v2/template-field-prefill.spec.ts @@ -194,7 +194,7 @@ test.describe('Template Field Prefill API v2', () => { id: numberField.id, type: 'number', label: 'Prefilled Number', - value: '42', + value: '98765', }, { id: radioField.id, @@ -253,7 +253,7 @@ test.describe('Template Field Prefill API v2', () => { expect(documentNumberField?.fieldMeta).toMatchObject({ type: 'number', label: 'Prefilled Number', - value: '42', + value: '98765', }); const documentRadioField = document?.fields.find( @@ -326,7 +326,7 @@ test.describe('Template Field Prefill API v2', () => { await expect(page.getByText('This is prefilled')).toBeVisible(); // Number field - await expect(page.getByText('42')).toBeVisible(); + await expect(page.getByText('98765', { exact: true })).toBeVisible(); // Radio field await expect(page.getByText('Option A')).toBeVisible(); @@ -380,7 +380,7 @@ test.describe('Template Field Prefill API v2', () => { // 5. Add fields to the template // Add TEXT field - const textField = await prisma.field.create({ + await prisma.field.create({ data: { templateId: template.id, recipientId: recipient.id, @@ -400,7 +400,7 @@ test.describe('Template Field Prefill API v2', () => { }); // Add NUMBER field - const numberField = await prisma.field.create({ + await prisma.field.create({ data: { templateId: template.id, recipientId: recipient.id, diff --git a/packages/app-tests/e2e/document-flow/stepper-component.spec.ts b/packages/app-tests/e2e/document-flow/stepper-component.spec.ts index 26a134185..579e21e26 100644 --- a/packages/app-tests/e2e/document-flow/stepper-component.spec.ts +++ b/packages/app-tests/e2e/document-flow/stepper-component.spec.ts @@ -633,7 +633,7 @@ test('[DOCUMENT_FLOW]: should be able to create and sign a document with 3 recip } // Wait for the document to be signed. - await page.waitForTimeout(5000); + await page.waitForTimeout(10000); const finalDocument = await prisma.document.findFirst({ where: { id: createdDocument?.id }, diff --git a/packages/app-tests/e2e/documents/delete-documents.spec.ts b/packages/app-tests/e2e/documents/delete-documents.spec.ts index 14d44de42..27fae861d 100644 --- a/packages/app-tests/e2e/documents/delete-documents.spec.ts +++ b/packages/app-tests/e2e/documents/delete-documents.spec.ts @@ -283,10 +283,10 @@ test('[DOCUMENTS]: deleting documents as a recipient should only hide it for the }).toPass(); // Delete document. - await page.getByRole('menuitem', { name: 'Hide' }).click(); - await page.getByRole('button', { name: 'Hide' }).click(); - - await page.waitForTimeout(1000); + await page.getByRole('menuitem', { name: 'Hide' }).waitFor({ state: 'visible' }); + await page.getByRole('menuitem', { name: 'Hide' }).click({ force: true }); + await page.getByRole('button', { name: 'Hide' }).click({ force: true }); + await page.waitForTimeout(2000); await expect(async () => { await page @@ -300,8 +300,10 @@ test('[DOCUMENTS]: deleting documents as a recipient should only hide it for the }).toPass(); // Delete document. - await page.getByRole('menuitem', { name: 'Hide' }).click(); - await page.getByRole('button', { name: 'Hide' }).click(); + await page.getByRole('menuitem', { name: 'Hide' }).waitFor({ state: 'visible' }); + await page.getByRole('menuitem', { name: 'Hide' }).click({ force: true }); + await page.getByRole('button', { name: 'Hide' }).click({ force: true }); + await page.waitForTimeout(2000); // Check document counts. await expect(page.getByRole('row', { name: /Document 1 - Completed/ })).not.toBeVisible(); diff --git a/packages/app-tests/e2e/features/include-document-certificate.spec.ts b/packages/app-tests/e2e/features/include-document-certificate.spec.ts index 8a5a544e6..0f8c30be7 100644 --- a/packages/app-tests/e2e/features/include-document-certificate.spec.ts +++ b/packages/app-tests/e2e/features/include-document-certificate.spec.ts @@ -49,9 +49,11 @@ test.describe('Signing Certificate Tests', () => { } await page.getByRole('button', { name: 'Complete' }).click(); - await page.getByRole('button', { name: 'Sign' }).click(); + await page.getByRole('button', { name: 'Sign' }).click({ force: true }); await page.waitForURL(`/sign/${recipient.token}/complete`); + await page.waitForTimeout(10000); + await expect(async () => { const { status } = await getDocumentByToken({ token: recipient.token, diff --git a/packages/app-tests/e2e/folders/team-account-folders.spec.ts b/packages/app-tests/e2e/folders/team-account-folders.spec.ts index 31382d4f5..92f6a8622 100644 --- a/packages/app-tests/e2e/folders/team-account-folders.spec.ts +++ b/packages/app-tests/e2e/folders/team-account-folders.spec.ts @@ -3,8 +3,7 @@ import path from 'node:path'; import { prisma } from '@documenso/prisma'; import { DocumentVisibility, FolderType, TeamMemberRole } from '@documenso/prisma/client'; -import { seedTeamDocuments } from '@documenso/prisma/seed/documents'; -import { seedBlankDocument } from '@documenso/prisma/seed/documents'; +import { seedBlankDocument, seedTeamDocuments } from '@documenso/prisma/seed/documents'; import { seedBlankFolder } from '@documenso/prisma/seed/folders'; import { seedTeamMember } from '@documenso/prisma/seed/teams'; import { seedBlankTemplate } from '@documenso/prisma/seed/templates'; @@ -1328,7 +1327,7 @@ test('[TEAMS]: team admin can move manager document to admin folder', async ({ p const managerDocRow = page.getByRole('row', { name: /\[TEST\] Manager Document/ }); await managerDocRow.getByTestId('document-table-action-btn').click(); - await page.getByRole('menuitem', { name: 'Move to Folder' }).click(); + await page.getByRole('menuitem', { name: 'Move to Folder' }).click({ force: true }); await expect(page.getByRole('button', { name: 'Admin Folder' })).toBeVisible(); await page.getByRole('button', { name: 'Admin Folder' }).click(); @@ -1379,7 +1378,7 @@ test('[TEAMS]: team admin can move manager document to manager folder', async ({ const managerDocRow = page.getByRole('row', { name: /\[TEST\] Manager Document/ }); await managerDocRow.getByTestId('document-table-action-btn').click(); - await page.getByRole('menuitem', { name: 'Move to Folder' }).click(); + await page.getByRole('menuitem', { name: 'Move to Folder' }).click({ force: true }); await expect(page.getByRole('button', { name: 'Manager Folder' })).toBeVisible(); await page.getByRole('button', { name: 'Manager Folder' }).click(); @@ -1430,7 +1429,7 @@ test('[TEAMS]: team admin can move manager document to everyone folder', async ( const managerDocRow = page.getByRole('row', { name: /\[TEST\] Manager Document/ }); await managerDocRow.getByTestId('document-table-action-btn').click(); - await page.getByRole('menuitem', { name: 'Move to Folder' }).click(); + await page.getByRole('menuitem', { name: 'Move to Folder' }).click({ force: true }); await expect(page.getByRole('button', { name: 'Everyone Folder' })).toBeVisible(); await page.getByRole('button', { name: 'Everyone Folder' }).click(); diff --git a/packages/app-tests/e2e/organisations/manage-organisation.spec.ts b/packages/app-tests/e2e/organisations/manage-organisation.spec.ts index b870b07a8..732efb48d 100644 --- a/packages/app-tests/e2e/organisations/manage-organisation.spec.ts +++ b/packages/app-tests/e2e/organisations/manage-organisation.spec.ts @@ -31,7 +31,7 @@ test('[ORGANISATIONS]: create and delete organisation', async ({ page }) => { await page.getByRole('button', { name: 'Delete' }).click(); await page.waitForURL(`/settings/organisations`); - await expect(page.getByText('No results found')).toBeVisible(); + await expectTextToBeVisible(page, 'No results found'); await page.getByRole('button', { name: 'Create organisation' }).click(); await page.getByLabel('Organisation Name*').fill('test'); diff --git a/packages/app-tests/e2e/teams/team-documents.spec.ts b/packages/app-tests/e2e/teams/team-documents.spec.ts index 28de98cb2..e547b0c40 100644 --- a/packages/app-tests/e2e/teams/team-documents.spec.ts +++ b/packages/app-tests/e2e/teams/team-documents.spec.ts @@ -1,8 +1,11 @@ import { expect, test } from '@playwright/test'; import { DocumentStatus, DocumentVisibility, TeamMemberRole } from '@prisma/client'; -import { seedBlankDocument } from '@documenso/prisma/seed/documents'; -import { seedDocuments, seedTeamDocuments } from '@documenso/prisma/seed/documents'; +import { + seedBlankDocument, + seedDocuments, + seedTeamDocuments, +} from '@documenso/prisma/seed/documents'; import { seedTeam, seedTeamEmail, seedTeamMember } from '@documenso/prisma/seed/teams'; import { seedUser } from '@documenso/prisma/seed/users'; @@ -314,9 +317,9 @@ test('[TEAMS]: delete pending team document', async ({ page }) => { await expect(page.getByRole('menuitem', { name: 'Delete' })).toBeVisible(); }).toPass(); - await page.getByRole('menuitem', { name: 'Delete' }).click(); + await page.getByRole('menuitem', { name: 'Delete' }).click({ force: true }); await page.getByPlaceholder("Type 'delete' to confirm").fill('delete'); - await page.getByRole('button', { name: 'Delete' }).click(); + await page.getByRole('button', { name: 'Delete' }).click({ force: true }); await checkDocumentTabCount(page, 'Pending', 1); @@ -359,9 +362,9 @@ test('[TEAMS]: delete completed team document', async ({ page }) => { await expect(page.getByRole('menuitem', { name: 'Delete' })).toBeVisible(); }).toPass(); - await page.getByRole('menuitem', { name: 'Delete' }).click(); + await page.getByRole('menuitem', { name: 'Delete' }).click({ force: true }); await page.getByPlaceholder("Type 'delete' to confirm").fill('delete'); - await page.getByRole('button', { name: 'Delete' }).click(); + await page.getByRole('button', { name: 'Delete' }).click({ force: true }); await checkDocumentTabCount(page, 'Completed', 0); diff --git a/packages/app-tests/e2e/teams/team-signature-settings.spec.ts b/packages/app-tests/e2e/teams/team-signature-settings.spec.ts index dcd195ffe..987fedc23 100644 --- a/packages/app-tests/e2e/teams/team-signature-settings.spec.ts +++ b/packages/app-tests/e2e/teams/team-signature-settings.spec.ts @@ -80,18 +80,27 @@ test('[TEAMS]: check signature modes can be disabled', async ({ page }) => { await page.getByRole('button', { name: 'Update' }).first().click(); + // Wait for the update to complete + const toast = page.locator('li[role="status"][data-state="open"]').first(); + await expect(toast).toBeVisible(); + await expect(toast.getByText('Document preferences updated', { exact: true })).toBeVisible(); + const document = await seedTeamDocumentWithMeta(team); - // Go to document and check that the signatured tabs are correct. + // Go to document and check that the signature tabs are correct. await page.goto(`/sign/${document.recipients[0].token}`); await page.getByTestId('signature-pad-dialog-button').click(); + // Wait for signature dialog to fully load + await page.waitForSelector('[role="dialog"]'); + // Check the tab values for (const tab of allTabs) { if (tabs.includes(tab)) { await expect(page.getByRole('tab', { name: tab })).toBeVisible(); } else { - await expect(page.getByRole('tab', { name: tab })).not.toBeVisible(); + // await expect(page.getByRole('tab', { name: tab })).not.toBeVisible(); + await expect(page.getByRole('tab', { name: tab })).toHaveCount(0); } } } diff --git a/packages/app-tests/e2e/templates/create-document-from-template.spec.ts b/packages/app-tests/e2e/templates/create-document-from-template.spec.ts index 9db76e881..39c58fc78 100644 --- a/packages/app-tests/e2e/templates/create-document-from-template.spec.ts +++ b/packages/app-tests/e2e/templates/create-document-from-template.spec.ts @@ -297,10 +297,22 @@ test('[TEMPLATE]: should create a document from a template with custom document' }, }); + const expectedDocumentDataType = + process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT === 's3' + ? DocumentDataType.S3_PATH + : DocumentDataType.BYTES_64; + expect(document.title).toEqual('TEMPLATE_WITH_CUSTOM_DOC'); - expect(document.documentData.type).toEqual(DocumentDataType.BYTES_64); - expect(document.documentData.data).toEqual(pdfContent); - expect(document.documentData.initialData).toEqual(pdfContent); + expect(document.documentData.type).toEqual(expectedDocumentDataType); + + if (expectedDocumentDataType === DocumentDataType.BYTES_64) { + expect(document.documentData.data).toEqual(pdfContent); + expect(document.documentData.initialData).toEqual(pdfContent); + } else { + // For S3, we expect the data/initialData to be the S3 path (non-empty string) + expect(document.documentData.data).toBeTruthy(); + expect(document.documentData.initialData).toBeTruthy(); + } }); /** @@ -378,11 +390,23 @@ test('[TEMPLATE]: should create a team document from a template with custom docu }, }); + const expectedDocumentDataType = + process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT === 's3' + ? DocumentDataType.S3_PATH + : DocumentDataType.BYTES_64; + expect(document.teamId).toEqual(team.id); expect(document.title).toEqual('TEAM_TEMPLATE_WITH_CUSTOM_DOC'); - expect(document.documentData.type).toEqual(DocumentDataType.BYTES_64); - expect(document.documentData.data).toEqual(pdfContent); - expect(document.documentData.initialData).toEqual(pdfContent); + expect(document.documentData.type).toEqual(expectedDocumentDataType); + + if (expectedDocumentDataType === DocumentDataType.BYTES_64) { + expect(document.documentData.data).toEqual(pdfContent); + expect(document.documentData.initialData).toEqual(pdfContent); + } else { + // For S3, we expect the data/initialData to be the S3 path (non-empty string) + expect(document.documentData.data).toBeTruthy(); + expect(document.documentData.initialData).toBeTruthy(); + } }); /** diff --git a/packages/app-tests/e2e/templates/direct-templates.spec.ts b/packages/app-tests/e2e/templates/direct-templates.spec.ts index 0bdd49980..80adfc34d 100644 --- a/packages/app-tests/e2e/templates/direct-templates.spec.ts +++ b/packages/app-tests/e2e/templates/direct-templates.spec.ts @@ -178,4 +178,7 @@ test('[DIRECT_TEMPLATES]: use direct template link with 1 recipient', async ({ p await page.getByRole('button', { name: 'Sign' }).click(); await page.waitForURL(/\/sign/); await expect(page.getByRole('heading', { name: 'Document Signed' })).toBeVisible(); + + // Add a longer waiting period to ensure document status is updated + await page.waitForTimeout(3000); }); diff --git a/packages/app-tests/e2e/user/auth-flow.spec.ts b/packages/app-tests/e2e/user/auth-flow.spec.ts index f78de8ef5..f56d09a8f 100644 --- a/packages/app-tests/e2e/user/auth-flow.spec.ts +++ b/packages/app-tests/e2e/user/auth-flow.spec.ts @@ -27,6 +27,9 @@ test('[USER] can sign up with email and password', async ({ page }: { page: Page await page.waitForURL('/unverified-account'); + // Wait to ensure token is created in the database + await page.waitForTimeout(2000); + const { token } = await extractUserVerificationToken(email); const team = await prisma.team.findFirstOrThrow({ diff --git a/packages/app-tests/e2e/user/password.spec.ts b/packages/app-tests/e2e/user/password.spec.ts index e989199f6..8325e92fe 100644 --- a/packages/app-tests/e2e/user/password.spec.ts +++ b/packages/app-tests/e2e/user/password.spec.ts @@ -17,9 +17,14 @@ test('[USER] can reset password via forgot password', async ({ page }: { page: P await page.goto('http://localhost:3000/signin'); await page.getByRole('link', { name: 'Forgot your password?' }).click(); + + await page.getByRole('textbox', { name: 'Email' }).click(); await page.getByRole('textbox', { name: 'Email' }).fill(user.email); + + await expect(page.getByRole('button', { name: 'Reset Password' })).toBeEnabled(); await page.getByRole('button', { name: 'Reset Password' }).click(); - await expect(page.locator('body')).toContainText('Reset email sent'); + + await expect(page.locator('body')).toContainText('Reset email sent', { timeout: 10000 }); const foundToken = await prisma.passwordResetToken.findFirstOrThrow({ where: { @@ -33,16 +38,26 @@ test('[USER] can reset password via forgot password', async ({ page }: { page: P await page.goto(`http://localhost:3000/reset-password/${foundToken.token}`); // Assert that password cannot be same as old password. - await page.getByRole('textbox', { name: 'Password', exact: true }).fill(oldPassword); - await page.getByRole('textbox', { name: 'Repeat Password' }).fill(oldPassword); + await page.getByLabel('Password', { exact: true }).fill(oldPassword); + await page.getByLabel('Repeat Password').fill(oldPassword); + + // Ensure both fields are filled before clicking + await expect(page.getByLabel('Password', { exact: true })).toHaveValue(oldPassword); + await expect(page.getByLabel('Repeat Password')).toHaveValue(oldPassword); + await page.getByRole('button', { name: 'Reset Password' }).click(); await expect(page.locator('body')).toContainText( 'Your new password cannot be the same as your old password.', ); // Assert password reset. - await page.getByRole('textbox', { name: 'Password', exact: true }).fill(newPassword); - await page.getByRole('textbox', { name: 'Repeat Password' }).fill(newPassword); + await page.getByLabel('Password', { exact: true }).fill(newPassword); + await page.getByLabel('Repeat Password').fill(newPassword); + + // Ensure both fields are filled before clicking + await expect(page.getByLabel('Password', { exact: true })).toHaveValue(newPassword); + await expect(page.getByLabel('Repeat Password')).toHaveValue(newPassword); + await page.getByRole('button', { name: 'Reset Password' }).click(); await expect(page.locator('body')).toContainText('Your password has been updated successfully.'); @@ -73,9 +88,9 @@ test('[USER] can reset password via user settings', async ({ page }: { page: Pag redirectPath: '/settings/security', }); - await page.getByRole('textbox', { name: 'Current password' }).fill(oldPassword); - await page.getByRole('textbox', { name: 'New password' }).fill(newPassword); - await page.getByRole('textbox', { name: 'Repeat password' }).fill(newPassword); + await page.getByLabel('Current password').fill(oldPassword); + await page.getByLabel('New password').fill(newPassword); + await page.getByLabel('Repeat password').fill(newPassword); await page.getByRole('button', { name: 'Update password' }).click(); await expect(page.locator('body')).toContainText('Password updated'); diff --git a/packages/app-tests/playwright.config.ts b/packages/app-tests/playwright.config.ts index 41d04621a..5fb03ada5 100644 --- a/packages/app-tests/playwright.config.ts +++ b/packages/app-tests/playwright.config.ts @@ -24,19 +24,23 @@ export default defineConfig({ /* Retry on CI only */ retries: process.env.CI ? 4 : 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', + reporter: [['html'], ['list']], /* 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', + trace: 'on', video: 'retain-on-failure', + + /* Add explicit timeouts for actions */ + actionTimeout: 15_000, + navigationTimeout: 30_000, }, - timeout: 30_000, + timeout: 60_000, /* Configure projects for major browsers */ projects: [