diff --git a/packages/app-tests/e2e/recipient/signing-groups.spec.ts b/packages/app-tests/e2e/recipient/signing-groups.spec.ts new file mode 100644 index 000000000..9b1ab89c3 --- /dev/null +++ b/packages/app-tests/e2e/recipient/signing-groups.spec.ts @@ -0,0 +1,95 @@ +import { prisma } from '@documenso/prisma'; +import { seedPendingDocumentWithFullFields } from '@documenso/prisma/seed/documents'; +import { seedUser } from '@documenso/prisma/seed/users'; +import type { Page } from '@playwright/test'; +import { expect, test } from '@playwright/test'; +import { DocumentSigningOrder, DocumentStatus, FieldType } from '@prisma/client'; + +import { signSignaturePad } from '../fixtures/signature'; + +type SeededRecipient = Awaited>['recipients'][number]; + +const completeSigning = async (page: Page, recipient: SeededRecipient) => { + const signUrl = `/sign/${recipient.token}`; + + await page.goto(signUrl); + await expect(page.getByRole('heading', { name: 'Sign Document' })).toBeVisible(); + + await signSignaturePad(page); + + for (const field of recipient.fields) { + await page.locator(`#field-${field.id}`).getByRole('button').click(); + + if (field.type === FieldType.TEXT) { + await page.locator('#custom-text').fill('TEXT'); + await page.getByRole('button', { name: 'Save' }).click(); + } + + await expect(page.locator(`#field-${field.id}`)).toHaveAttribute('data-inserted', 'true'); + } + + await page.getByRole('button', { name: 'Complete' }).click(); + await page.getByRole('button', { name: 'Sign' }).click(); + await page.waitForURL(`${signUrl}/complete`); +}; + +const expectWaiting = async (page: Page, token: string) => { + await page.goto(`/sign/${token}`); + await page.waitForURL(`/sign/${token}/waiting`); +}; + +test('[SIGNING_GROUPS]: group members sign in any order and gate the next step', async ({ page }) => { + const { user, team } = await seedUser(); + const { user: signer1 } = await seedUser(); + const { user: signer2a } = await seedUser(); + const { user: signer2b } = await seedUser(); + const { user: signer3 } = await seedUser(); + + const { recipients, document } = await seedPendingDocumentWithFullFields({ + owner: user, + teamId: team.id, + recipients: [signer1, signer2a, signer2b, signer3], + recipientsCreateOptions: [{ signingOrder: 1 }, { signingOrder: 2 }, { signingOrder: 2 }, { signingOrder: 3 }], + updateDocumentOptions: { + documentMeta: { + upsert: { + create: { signingOrder: DocumentSigningOrder.SEQUENTIAL }, + update: { signingOrder: DocumentSigningOrder.SEQUENTIAL }, + }, + }, + }, + }); + + const [recipient1, recipient2a, recipient2b, recipient3] = recipients; + + // While step 1 is pending, both group members and step 3 are blocked. + await expectWaiting(page, recipient2a.token); + await expectWaiting(page, recipient2b.token); + await expectWaiting(page, recipient3.token); + + await completeSigning(page, recipient1); + + // The group is now active; step 3 is still blocked. + await expectWaiting(page, recipient3.token); + + // Sign with the SECOND group member first to prove any-order signing. + await completeSigning(page, recipient2b); + + // One group member remains — step 3 stays blocked. + await expectWaiting(page, recipient3.token); + + await completeSigning(page, recipient2a); + + // The whole group is done — step 3 unlocks and completes the document. + await completeSigning(page, recipient3); + + await expect + .poll(async () => { + const envelope = await prisma.envelope.findUniqueOrThrow({ + where: { id: document.id }, + }); + + return envelope.status; + }) + .toBe(DocumentStatus.COMPLETED); +});