feat: add organisations (#1820)

This commit is contained in:
David Nguyen
2025-06-10 11:49:52 +10:00
committed by GitHub
parent 0b37f19641
commit e6dc237ad2
631 changed files with 37616 additions and 25695 deletions

View File

@ -8,15 +8,17 @@ import { seedUser } from '@documenso/prisma/seed/users';
test.describe('Document API', () => {
test('sendDocument: should respect sendCompletionEmails setting', async ({ request }) => {
const user = await seedUser();
const { user, team } = await seedUser();
const { document } = await seedPendingDocumentWithFullFields({
owner: user,
recipients: ['signer@example.com'],
teamId: team.id,
});
const { token } = await createApiToken({
userId: user.id,
teamId: team.id,
tokenName: 'test',
expiresIn: null,
});
@ -81,11 +83,12 @@ test.describe('Document API', () => {
test('sendDocument: should not modify email settings when sendCompletionEmails is not provided', async ({
request,
}) => {
const user = await seedUser();
const { user, team } = await seedUser();
const { document } = await seedPendingDocumentWithFullFields({
owner: user,
recipients: ['signer@example.com'],
teamId: team.id,
});
// Set initial email settings
@ -109,6 +112,7 @@ test.describe('Document API', () => {
const { token } = await createApiToken({
userId: user.id,
teamId: team.id,
tokenName: 'test',
expiresIn: null,
});

View File

@ -1,281 +0,0 @@
import { expect, test } from '@playwright/test';
import { TeamMemberRole } from '@prisma/client';
import {
ZFindTeamMembersResponseSchema,
ZSuccessfulInviteTeamMemberResponseSchema,
ZSuccessfulRemoveTeamMemberResponseSchema,
ZSuccessfulUpdateTeamMemberResponseSchema,
ZUnsuccessfulResponseSchema,
} from '@documenso/api/v1/schema';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import { prisma } from '@documenso/prisma';
import { seedTeam } from '@documenso/prisma/seed/teams';
import { seedUser } from '@documenso/prisma/seed/users';
test.describe('Team API', () => {
test('findTeamMembers: should list team members', async ({ request }) => {
const team = await seedTeam({
createTeamMembers: 3,
});
const ownerMember = team.members.find((member) => member.userId === team.owner.id)!;
// Should not be undefined
expect(ownerMember).toBeTruthy();
const { token } = await createApiToken({
userId: team.owner.id,
teamId: team.id,
tokenName: 'test',
expiresIn: null,
});
const response = await request.get(
`${NEXT_PUBLIC_WEBAPP_URL()}/api/v1/team/${team.id}/members`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
);
expect(response.ok()).toBeTruthy();
expect(response.status()).toBe(200);
const data = await response.json();
const parsed = ZFindTeamMembersResponseSchema.safeParse(data);
const safeData = parsed.success ? parsed.data : null;
expect(parsed.success).toBeTruthy();
expect(safeData!.members).toHaveLength(4); // Owner + 3 members
expect(safeData!.members[0]).toHaveProperty('id');
expect(safeData!.members[0]).toHaveProperty('email');
expect(safeData!.members[0]).toHaveProperty('role');
expect(safeData!.members).toContainEqual({
id: ownerMember.id,
email: ownerMember.user.email,
role: ownerMember.role,
});
});
test('inviteTeamMember: should invite a new team member', async ({ request }) => {
const team = await seedTeam();
const { token } = await createApiToken({
userId: team.owner.id,
teamId: team.id,
tokenName: 'test',
expiresIn: null,
});
const newUser = await seedUser();
const response = await request.post(
`${NEXT_PUBLIC_WEBAPP_URL()}/api/v1/team/${team.id}/members/invite`,
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
data: {
email: newUser.email,
role: TeamMemberRole.MEMBER,
},
},
);
expect(response.ok()).toBeTruthy();
const data = await response.json();
const parsed = ZSuccessfulInviteTeamMemberResponseSchema.safeParse(data);
const safeData = parsed.success ? parsed.data : null;
expect(parsed.success).toBeTruthy();
expect(safeData!.message).toBe('An invite has been sent to the member');
const invite = await prisma.teamMemberInvite.findFirst({
where: {
email: newUser.email,
teamId: team.id,
},
});
expect(invite).toBeTruthy();
});
test('updateTeamMember: should update a team member role', async ({ request }) => {
const team = await seedTeam({
createTeamMembers: 3,
});
const { token } = await createApiToken({
userId: team.owner.id,
teamId: team.id,
tokenName: 'test',
expiresIn: null,
});
const member = team.members.find((member) => member.role === TeamMemberRole.MEMBER)!;
// Should not be undefined
expect(member).toBeTruthy();
const response = await request.put(
`${NEXT_PUBLIC_WEBAPP_URL()}/api/v1/team/${team.id}/members/${member.id}`,
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
data: {
role: TeamMemberRole.ADMIN,
},
},
);
expect(response.ok()).toBeTruthy();
const data = await response.json();
const parsed = ZSuccessfulUpdateTeamMemberResponseSchema.safeParse(data);
const safeData = parsed.success ? parsed.data : null;
expect(parsed.success).toBeTruthy();
expect(safeData!.id).toBe(member.id);
expect(safeData!.email).toBe(member.user.email);
expect(safeData!.role).toBe(TeamMemberRole.ADMIN);
});
test('removeTeamMember: should remove a team member', async ({ request }) => {
const team = await seedTeam({
createTeamMembers: 3,
});
const { token } = await createApiToken({
userId: team.owner.id,
teamId: team.id,
tokenName: 'test',
expiresIn: null,
});
const member = team.members.find((member) => member.role === TeamMemberRole.MEMBER)!;
// Should not be undefined
expect(member).toBeTruthy();
const response = await request.delete(
`${NEXT_PUBLIC_WEBAPP_URL()}/api/v1/team/${team.id}/members/${member.id}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
);
expect(response.status()).toBe(200);
const data = await response.json();
const parsed = ZSuccessfulRemoveTeamMemberResponseSchema.safeParse(data);
const safeData = parsed.success ? parsed.data : null;
expect(parsed.success).toBeTruthy();
expect(safeData!.id).toBe(member.id);
expect(safeData!.email).toBe(member.user.email);
expect(safeData!.role).toBe(member.role);
const removedMemberCount = await prisma.teamMember.count({
where: {
id: member.id,
teamId: team.id,
},
});
expect(removedMemberCount).toBe(0);
});
test('removeTeamMember: should not remove team owner', async ({ request }) => {
const team = await seedTeam({
createTeamMembers: 3,
});
const { token } = await createApiToken({
userId: team.owner.id,
teamId: team.id,
tokenName: 'test',
expiresIn: null,
});
const ownerMember = team.members.find((member) => member.userId === team.owner.id)!;
// Should not be undefined
expect(ownerMember).toBeTruthy();
const response = await request.delete(
`${NEXT_PUBLIC_WEBAPP_URL()}/api/v1/team/${team.id}/members/${ownerMember.id}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
);
expect(response.status()).toBe(403);
const parsed = ZUnsuccessfulResponseSchema.safeParse(await response.json());
expect(parsed.success).toBeTruthy();
});
test('removeTeamMember: should not remove self', async ({ request }) => {
const team = await seedTeam({
createTeamMembers: 3,
});
const member = team.members.find((member) => member.role === TeamMemberRole.MEMBER)!;
// Make our non-owner member an admin
await prisma.teamMember.update({
where: {
id: member.id,
},
data: {
role: TeamMemberRole.ADMIN,
},
});
const { token } = await createApiToken({
userId: member.userId,
teamId: team.id,
tokenName: 'test',
expiresIn: null,
});
const response = await request.delete(
`${NEXT_PUBLIC_WEBAPP_URL()}/api/v1/team/${team.id}/members/${member.id}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
);
expect(response.status()).toBe(403);
const parsed = ZUnsuccessfulResponseSchema.safeParse(await response.json());
expect(parsed.success).toBeTruthy();
});
});

View File

@ -18,17 +18,18 @@ test.describe('Template Field Prefill API v1', () => {
request,
}) => {
// 1. Create a user
const user = await seedUser();
const { user, team } = await seedUser();
// 2. Create an API token for the user
const { token } = await createApiToken({
userId: user.id,
teamId: team.id,
tokenName: 'test-token',
expiresIn: null,
});
// 3. Create a template with seedBlankTemplate
const template = await seedBlankTemplate(user, {
const template = await seedBlankTemplate(user, team.id, {
createTemplateOptions: {
title: 'Template with Advanced Fields',
},
@ -349,17 +350,18 @@ test.describe('Template Field Prefill API v1', () => {
request,
}) => {
// 1. Create a user
const user = await seedUser();
const { user, team } = await seedUser();
// 2. Create an API token for the user
const { token } = await createApiToken({
userId: user.id,
teamId: team.id,
tokenName: 'test-token',
expiresIn: null,
});
// 3. Create a template with seedBlankTemplate
const template = await seedBlankTemplate(user, {
const template = await seedBlankTemplate(user, team.id, {
createTemplateOptions: {
title: 'Template with Default Fields',
},
@ -519,17 +521,18 @@ test.describe('Template Field Prefill API v1', () => {
test('should handle invalid field prefill values', async ({ request }) => {
// 1. Create a user
const user = await seedUser();
const { user, team } = await seedUser();
// 2. Create an API token for the user
const { token } = await createApiToken({
userId: user.id,
teamId: team.id,
tokenName: 'test-token',
expiresIn: null,
});
// 3. Create a template using seedBlankTemplate
const template = await seedBlankTemplate(user, {
const template = await seedBlankTemplate(user, team.id, {
createTemplateOptions: {
title: 'Template for Invalid Test',
visibility: 'EVERYONE',

View File

@ -8,10 +8,11 @@ test.describe('Embedding Presign API', () => {
test('createEmbeddingPresignToken: should create a token with default expiration', async ({
request,
}) => {
const user = await seedUser();
const { user, team } = await seedUser();
const { token } = await createApiToken({
userId: user.id,
teamId: team.id,
tokenName: 'test',
expiresIn: null,
});
@ -44,10 +45,11 @@ test.describe('Embedding Presign API', () => {
test('createEmbeddingPresignToken: should create a token with custom expiration', async ({
request,
}) => {
const user = await seedUser();
const { user, team } = await seedUser();
const { token } = await createApiToken({
userId: user.id,
teamId: team.id,
tokenName: 'test',
expiresIn: null,
});
@ -81,10 +83,11 @@ test.describe('Embedding Presign API', () => {
test.skip('createEmbeddingPresignToken: should create a token with immediate expiration in dev mode', async ({
request,
}) => {
const user = await seedUser();
const { user, team } = await seedUser();
const { token } = await createApiToken({
userId: user.id,
teamId: team.id,
tokenName: 'test',
expiresIn: null,
});
@ -116,10 +119,11 @@ test.describe('Embedding Presign API', () => {
});
test('verifyEmbeddingPresignToken: should verify a valid token', async ({ request }) => {
const user = await seedUser();
const { user, team } = await seedUser();
const { token } = await createApiToken({
userId: user.id,
teamId: team.id,
tokenName: 'test',
expiresIn: null,
});
@ -170,10 +174,11 @@ test.describe('Embedding Presign API', () => {
});
test('verifyEmbeddingPresignToken: should reject an invalid token', async ({ request }) => {
const user = await seedUser();
const { user, team } = await seedUser();
const { token } = await createApiToken({
userId: user.id,
teamId: team.id,
tokenName: 'test',
expiresIn: null,
});

View File

@ -18,17 +18,18 @@ test.describe('Template Field Prefill API v2', () => {
request,
}) => {
// 1. Create a user
const user = await seedUser();
const { user, team } = await seedUser();
// 2. Create an API token for the user
const { token } = await createApiToken({
userId: user.id,
teamId: team.id,
tokenName: 'test-token',
expiresIn: null,
});
// 3. Create a template with seedBlankTemplate
const template = await seedBlankTemplate(user, {
const template = await seedBlankTemplate(user, team.id, {
createTemplateOptions: {
title: 'Template with Advanced Fields V2',
},
@ -346,17 +347,18 @@ test.describe('Template Field Prefill API v2', () => {
request,
}) => {
// 1. Create a user
const user = await seedUser();
const { user, team } = await seedUser();
// 2. Create an API token for the user
const { token } = await createApiToken({
userId: user.id,
teamId: team.id,
tokenName: 'test-token',
expiresIn: null,
});
// 3. Create a template with seedBlankTemplate
const template = await seedBlankTemplate(user, {
const template = await seedBlankTemplate(user, team.id, {
createTemplateOptions: {
title: 'Template with Default Fields V2',
},
@ -511,17 +513,18 @@ test.describe('Template Field Prefill API v2', () => {
test('should handle invalid field prefill values', async ({ request }) => {
// 1. Create a user
const user = await seedUser();
const { user, team } = await seedUser();
// 2. Create an API token for the user
const { token } = await createApiToken({
userId: user.id,
teamId: team.id,
tokenName: 'test-token',
expiresIn: null,
});
// 3. Create a template using seedBlankTemplate
const template = await seedBlankTemplate(user, {
const template = await seedBlankTemplate(user, team.id, {
createTemplateOptions: {
title: 'Template for Invalid Test V2',
visibility: 'EVERYONE',

View File

@ -6,9 +6,9 @@ import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
test('[COMMAND_MENU]: should see sent documents', async ({ page }) => {
const user = await seedUser();
const recipient = await seedUser();
const document = await seedPendingDocument(user, [recipient]);
const { user, team } = await seedUser();
const { user: recipient } = await seedUser();
const document = await seedPendingDocument(user, team.id, [recipient]);
await apiSignin({
page,
@ -22,9 +22,9 @@ test('[COMMAND_MENU]: should see sent documents', async ({ page }) => {
});
test('[COMMAND_MENU]: should see received documents', async ({ page }) => {
const user = await seedUser();
const recipient = await seedUser();
const document = await seedPendingDocument(user, [recipient]);
const { user, team } = await seedUser();
const { user: recipient } = await seedUser();
const document = await seedPendingDocument(user, team.id, [recipient]);
await apiSignin({
page,
@ -38,9 +38,9 @@ test('[COMMAND_MENU]: should see received documents', async ({ page }) => {
});
test('[COMMAND_MENU]: should be able to search by recipient', async ({ page }) => {
const user = await seedUser();
const recipient = await seedUser();
const document = await seedPendingDocument(user, [recipient]);
const { user, team } = await seedUser();
const { user: recipient } = await seedUser();
const document = await seedPendingDocument(user, team.id, [recipient]);
await apiSignin({
page,

View File

@ -8,11 +8,11 @@ import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
test('[DOCUMENT_AUTH]: should grant access when not required', async ({ page }) => {
const user = await seedUser();
const { user, team } = await seedUser();
const recipientWithAccount = await seedUser();
const { user: recipientWithAccount } = await seedUser();
const document = await seedPendingDocument(user, [
const document = await seedPendingDocument(user, team.id, [
recipientWithAccount,
'recipientwithoutaccount@documenso.com',
]);
@ -32,12 +32,13 @@ test('[DOCUMENT_AUTH]: should grant access when not required', async ({ page })
});
test('[DOCUMENT_AUTH]: should allow or deny access when required', async ({ page }) => {
const user = await seedUser();
const { user, team } = await seedUser();
const recipientWithAccount = await seedUser();
const { user: recipientWithAccount } = await seedUser();
const document = await seedPendingDocument(
user,
team.id,
[recipientWithAccount, 'recipientwithoutaccount@documenso.com'],
{
createDocumentOptions: {

View File

@ -18,12 +18,13 @@ import { signSignaturePad } from '../fixtures/signature';
test.describe.configure({ mode: 'parallel', timeout: 60000 });
test('[DOCUMENT_AUTH]: should allow signing when no auth setup', async ({ page }) => {
const user = await seedUser();
const { user, team } = await seedUser();
const recipientWithAccount = await seedUser();
const { user: recipientWithAccount } = await seedUser();
const { recipients } = await seedPendingDocumentWithFullFields({
owner: user,
teamId: team.id,
recipients: [recipientWithAccount, seedTestEmail()],
});
@ -56,12 +57,13 @@ test('[DOCUMENT_AUTH]: should allow signing when no auth setup', async ({ page }
});
test('[DOCUMENT_AUTH]: should allow signing with valid global auth', async ({ page }) => {
const user = await seedUser();
const { user, team } = await seedUser();
const recipientWithAccount = await seedUser();
const { user: recipientWithAccount } = await seedUser();
const { recipients } = await seedPendingDocumentWithFullFields({
owner: user,
teamId: team.id,
recipients: [recipientWithAccount],
updateDocumentOptions: {
authOptions: createDocumentAuthOptions({
@ -107,12 +109,13 @@ test('[DOCUMENT_AUTH]: should allow signing with valid global auth', async ({ pa
test.skip('[DOCUMENT_AUTH]: should deny signing document when required for global auth', async ({
page,
}) => {
const user = await seedUser();
const { user, team } = await seedUser();
const recipientWithAccount = await seedUser();
const { user: recipientWithAccount } = await seedUser();
const { recipients } = await seedPendingDocumentNoFields({
owner: user,
teamId: team.id,
recipients: [recipientWithAccount],
updateDocumentOptions: {
authOptions: createDocumentAuthOptions({
@ -138,12 +141,13 @@ test.skip('[DOCUMENT_AUTH]: should deny signing document when required for globa
test('[DOCUMENT_AUTH]: should deny signing fields when required for global auth', async ({
page,
}) => {
const user = await seedUser();
const { user, team } = await seedUser();
const recipientWithAccount = await seedUser();
const { user: recipientWithAccount } = await seedUser();
const { recipients } = await seedPendingDocumentWithFullFields({
owner: user,
teamId: team.id,
recipients: [recipientWithAccount, seedTestEmail()],
updateDocumentOptions: {
authOptions: createDocumentAuthOptions({
@ -177,14 +181,15 @@ test('[DOCUMENT_AUTH]: should deny signing fields when required for global auth'
test('[DOCUMENT_AUTH]: should allow field signing when required for recipient auth', async ({
page,
}) => {
const user = await seedUser();
const { user, team } = await seedUser();
const recipientWithInheritAuth = await seedUser();
const recipientWithExplicitNoneAuth = await seedUser();
const recipientWithExplicitAccountAuth = await seedUser();
const { user: recipientWithInheritAuth } = await seedUser();
const { user: recipientWithExplicitNoneAuth } = await seedUser();
const { user: recipientWithExplicitAccountAuth } = await seedUser();
const { recipients } = await seedPendingDocumentWithFullFields({
owner: user,
teamId: team.id,
recipients: [
recipientWithInheritAuth,
recipientWithExplicitNoneAuth,
@ -276,14 +281,15 @@ test('[DOCUMENT_AUTH]: should allow field signing when required for recipient au
test('[DOCUMENT_AUTH]: should allow field signing when required for recipient and global auth', async ({
page,
}) => {
const user = await seedUser();
const { user, team } = await seedUser();
const recipientWithInheritAuth = await seedUser();
const recipientWithExplicitNoneAuth = await seedUser();
const recipientWithExplicitAccountAuth = await seedUser();
const { user: recipientWithInheritAuth } = await seedUser();
const { user: recipientWithExplicitNoneAuth } = await seedUser();
const { user: recipientWithExplicitAccountAuth } = await seedUser();
const { recipients } = await seedPendingDocumentWithFullFields({
owner: user,
teamId: team.id,
recipients: [
recipientWithInheritAuth,
recipientWithExplicitNoneAuth,

View File

@ -16,13 +16,14 @@ import { signDirectSignaturePad, signSignaturePad } from '../fixtures/signature'
test('[NEXT_RECIPIENT_DICTATION]: should allow updating next recipient when dictation is enabled', async ({
page,
}) => {
const user = await seedUser();
const firstSigner = await seedUser();
const secondSigner = await seedUser();
const thirdSigner = await seedUser();
const { user, team } = await seedUser();
const { user: firstSigner } = await seedUser();
const { user: secondSigner } = await seedUser();
const { user: thirdSigner } = await seedUser();
const { recipients, document } = await seedPendingDocumentWithFullFields({
owner: user,
teamId: team.id,
recipients: [firstSigner, secondSigner, thirdSigner],
recipientsCreateOptions: [{ signingOrder: 1 }, { signingOrder: 2 }, { signingOrder: 3 }],
updateDocumentOptions: {
@ -109,12 +110,13 @@ test('[NEXT_RECIPIENT_DICTATION]: should allow updating next recipient when dict
});
test('[NEXT_RECIPIENT_DICTATION]: should not show dictation UI when disabled', async ({ page }) => {
const user = await seedUser();
const firstSigner = await seedUser();
const secondSigner = await seedUser();
const { user, team } = await seedUser();
const { user: firstSigner } = await seedUser();
const { user: secondSigner } = await seedUser();
const { recipients, document } = await seedPendingDocumentWithFullFields({
owner: user,
teamId: team.id,
recipients: [firstSigner, secondSigner],
recipientsCreateOptions: [{ signingOrder: 1 }, { signingOrder: 2 }],
updateDocumentOptions: {
@ -194,12 +196,13 @@ test('[NEXT_RECIPIENT_DICTATION]: should not show dictation UI when disabled', a
});
test('[NEXT_RECIPIENT_DICTATION]: should work with parallel signing flow', async ({ page }) => {
const user = await seedUser();
const firstSigner = await seedUser();
const secondSigner = await seedUser();
const { user, team } = await seedUser();
const { user: firstSigner } = await seedUser();
const { user: secondSigner } = await seedUser();
const { recipients, document } = await seedPendingDocumentWithFullFields({
owner: user,
teamId: team.id,
recipients: [firstSigner, secondSigner],
recipientsCreateOptions: [{ signingOrder: 1 }, { signingOrder: 2 }],
updateDocumentOptions: {
@ -278,13 +281,14 @@ test('[NEXT_RECIPIENT_DICTATION]: should work with parallel signing flow', async
test('[NEXT_RECIPIENT_DICTATION]: should allow assistant to dictate next signer', async ({
page,
}) => {
const user = await seedUser();
const assistant = await seedUser();
const signer = await seedUser();
const thirdSigner = await seedUser();
const { user, team } = await seedUser();
const { user: assistant } = await seedUser();
const { user: signer } = await seedUser();
const { user: thirdSigner } = await seedUser();
const { recipients, document } = await seedPendingDocumentWithFullFields({
owner: user,
teamId: team.id,
recipients: [assistant, signer, thirdSigner],
recipientsCreateOptions: [
{ signingOrder: 1, role: RecipientRole.ASSISTANT },

View File

@ -5,137 +5,18 @@ import {
seedDraftDocument,
seedPendingDocument,
} from '@documenso/prisma/seed/documents';
import { seedUserSubscription } from '@documenso/prisma/seed/subscriptions';
import { seedTeam } from '@documenso/prisma/seed/teams';
import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
test.describe('[EE_ONLY]', () => {
// eslint-disable-next-line turbo/no-undeclared-env-vars
const enterprisePriceId = process.env.NEXT_PUBLIC_STRIPE_ENTERPRISE_PLAN_MONTHLY_PRICE_ID || '';
test.beforeEach(() => {
test.skip(
process.env.NEXT_PUBLIC_FEATURE_BILLING_ENABLED !== 'true' || !enterprisePriceId,
'Billing required for this test',
);
});
test('[DOCUMENT_FLOW] add action auth settings', async ({ page }) => {
const user = await seedUser();
await seedUserSubscription({
userId: user.id,
priceId: enterprisePriceId,
});
const document = await seedBlankDocument(user);
await apiSignin({
page,
email: user.email,
redirectPath: `/documents/${document.id}/edit`,
});
// Set EE action auth.
await page.getByTestId('documentActionSelectValue').click();
await page.getByRole('option').filter({ hasText: 'Require passkey' }).click();
await expect(page.getByTestId('documentActionSelectValue')).toContainText('Require passkey');
// Save the settings by going to the next step.
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
// Return to the settings step to check that the results are saved correctly.
await page.getByRole('button', { name: 'Go Back' }).click();
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
await expect(page.getByTestId('documentActionSelectValue')).toContainText('Require passkey');
});
test('[DOCUMENT_FLOW] enterprise team member can add action auth settings', async ({ page }) => {
const team = await seedTeam({
createTeamMembers: 1,
});
const owner = team.owner;
const teamMemberUser = team.members[1].user;
// Make the team enterprise by giving the owner the enterprise subscription.
await seedUserSubscription({
userId: team.ownerUserId,
priceId: enterprisePriceId,
});
const document = await seedBlankDocument(owner, {
createDocumentOptions: {
teamId: team.id,
},
});
await apiSignin({
page,
email: teamMemberUser.email,
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
});
// Set EE action auth.
await page.getByTestId('documentActionSelectValue').click();
await page.getByRole('option').filter({ hasText: 'Require passkey' }).click();
await expect(page.getByTestId('documentActionSelectValue')).toContainText('Require passkey');
// Save the settings by going to the next step.
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
// Advanced settings should be visible.
await expect(page.getByLabel('Show advanced settings')).toBeVisible();
});
test('[DOCUMENT_FLOW] enterprise team member should not have access to enterprise on personal account', async ({
page,
}) => {
const team = await seedTeam({
createTeamMembers: 1,
});
const teamMemberUser = team.members[1].user;
// Make the team enterprise by giving the owner the enterprise subscription.
await seedUserSubscription({
userId: team.ownerUserId,
priceId: enterprisePriceId,
});
const document = await seedBlankDocument(teamMemberUser);
await apiSignin({
page,
email: teamMemberUser.email,
redirectPath: `/documents/${document.id}/edit`,
});
// Global action auth should not be visible.
await expect(page.getByTestId('documentActionSelectValue')).not.toBeVisible();
// Next step.
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
// Advanced settings should not be visible.
await expect(page.getByLabel('Show advanced settings')).not.toBeVisible();
});
});
test('[DOCUMENT_FLOW]: add settings', async ({ page }) => {
const user = await seedUser();
const document = await seedBlankDocument(user);
const { user, team } = await seedUser();
const document = await seedBlankDocument(user, team.id);
await apiSignin({
page,
email: user.email,
redirectPath: `/documents/${document.id}/edit`,
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
});
// Set title.
@ -163,21 +44,21 @@ test('[DOCUMENT_FLOW]: add settings', async ({ page }) => {
});
test('[DOCUMENT_FLOW]: title should be disabled depending on document status', async ({ page }) => {
const user = await seedUser();
const { user, team } = await seedUser();
const pendingDocument = await seedPendingDocument(user, []);
const draftDocument = await seedDraftDocument(user, []);
const pendingDocument = await seedPendingDocument(user, team.id, []);
const draftDocument = await seedDraftDocument(user, team.id, []);
await apiSignin({
page,
email: user.email,
redirectPath: `/documents/${pendingDocument.id}/edit`,
redirectPath: `/t/${team.url}/documents/${pendingDocument.id}/edit`,
});
// Should be disabled for pending documents.
await expect(page.getByLabel('Title')).toBeDisabled();
// Should be enabled for draft documents.
await page.goto(`/documents/${draftDocument.id}/edit`);
await page.goto(`/t/${team.url}/documents/${draftDocument.id}/edit`);
await expect(page.getByLabel('Title')).toBeEnabled();
});

View File

@ -1,71 +1,18 @@
import { expect, test } from '@playwright/test';
import { seedBlankDocument } from '@documenso/prisma/seed/documents';
import { seedUserSubscription } from '@documenso/prisma/seed/subscriptions';
import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
test.describe('[EE_ONLY]', () => {
// eslint-disable-next-line turbo/no-undeclared-env-vars
const enterprisePriceId = process.env.NEXT_PUBLIC_STRIPE_ENTERPRISE_PLAN_MONTHLY_PRICE_ID || '';
test.beforeEach(() => {
test.skip(
process.env.NEXT_PUBLIC_FEATURE_BILLING_ENABLED !== 'true' || !enterprisePriceId,
'Billing required for this test',
);
});
test('[DOCUMENT_FLOW] add EE settings', async ({ page }) => {
const user = await seedUser();
await seedUserSubscription({
userId: user.id,
priceId: enterprisePriceId,
});
const document = await seedBlankDocument(user);
await apiSignin({
page,
email: user.email,
redirectPath: `/documents/${document.id}/edit`,
});
// Save the settings by going to the next step.
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
// Add 2 signers.
await page.getByPlaceholder('Email').fill('recipient1@documenso.com');
await page.getByPlaceholder('Name').fill('Recipient 1');
await page.getByRole('button', { name: 'Add Signer' }).click();
await page.getByLabel('Email').nth(1).fill('recipient2@documenso.com');
await page.getByLabel('Name').nth(1).fill('Recipient 2');
// Display advanced settings.
await page.getByLabel('Show advanced settings').check();
// Navigate to the next step and back.
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
await page.getByRole('button', { name: 'Go Back' }).click();
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
// Todo: Fix stepper component back issue before finishing test.
});
});
test('[DOCUMENT_FLOW]: add signers', async ({ page }) => {
const user = await seedUser();
const document = await seedBlankDocument(user);
const { user, team } = await seedUser();
const document = await seedBlankDocument(user, team.id);
await apiSignin({
page,
email: user.email,
redirectPath: `/documents/${document.id}/edit`,
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
});
// Save the settings by going to the next step.

View File

@ -35,11 +35,12 @@ const getDocumentByToken = async (token: string) => {
};
test('[DOCUMENT_FLOW]: should be able to upload a PDF document', async ({ page }) => {
const user = await seedUser();
const { user, team } = await seedUser();
await apiSignin({
page,
email: user.email,
redirectPath: `/t/${team.url}/documents`,
});
// Upload document.
@ -58,17 +59,17 @@ test('[DOCUMENT_FLOW]: should be able to upload a PDF document', async ({ page }
await fileChooser.setFiles(path.join(__dirname, '../../../../assets/example.pdf'));
// Wait to be redirected to the edit page.
await page.waitForURL(/\/documents\/\d+/);
await page.waitForURL(new RegExp(`/t/${team.url}/documents/\\d+`));
});
test('[DOCUMENT_FLOW]: should be able to create a document', async ({ page }) => {
const user = await seedUser();
const document = await seedBlankDocument(user);
const { user, team } = await seedUser();
const document = await seedBlankDocument(user, team.id);
await apiSignin({
page,
email: user.email,
redirectPath: `/documents/${document.id}/edit`,
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
});
const documentTitle = `example-${Date.now()}.pdf`;
@ -114,7 +115,7 @@ test('[DOCUMENT_FLOW]: should be able to create a document', async ({ page }) =>
await page.waitForTimeout(2500);
await page.getByRole('button', { name: 'Send' }).click();
await page.waitForURL('/documents');
await page.waitForURL(new RegExp(`/t/${team.url}/documents/\\d+`));
// Assert document was created
await expect(page.getByRole('link', { name: documentTitle })).toBeVisible();
@ -123,13 +124,13 @@ test('[DOCUMENT_FLOW]: should be able to create a document', async ({ page }) =>
test('[DOCUMENT_FLOW]: should be able to create a document with multiple recipients', async ({
page,
}) => {
const user = await seedUser();
const document = await seedBlankDocument(user);
const { user, team } = await seedUser();
const document = await seedBlankDocument(user, team.id);
await apiSignin({
page,
email: user.email,
redirectPath: `/documents/${document.id}/edit`,
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
});
const documentTitle = `example-${Date.now()}.pdf`;
@ -199,7 +200,7 @@ test('[DOCUMENT_FLOW]: should be able to create a document with multiple recipie
await page.waitForTimeout(2500);
await page.getByRole('button', { name: 'Send' }).click();
await page.waitForURL('/documents');
await page.waitForURL(new RegExp(`/t/${team.url}/documents/\\d+`));
// Assert document was created
await expect(page.getByRole('link', { name: documentTitle })).toBeVisible();
@ -208,13 +209,13 @@ test('[DOCUMENT_FLOW]: should be able to create a document with multiple recipie
test('[DOCUMENT_FLOW]: should be able to create a document with multiple recipients with different roles', async ({
page,
}) => {
const user = await seedUser();
const document = await seedBlankDocument(user);
const { user, team } = await seedUser();
const document = await seedBlankDocument(user, team.id);
await apiSignin({
page,
email: user.email,
redirectPath: `/documents/${document.id}/edit`,
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
});
// Set title
@ -297,7 +298,7 @@ test('[DOCUMENT_FLOW]: should be able to create a document with multiple recipie
await page.waitForTimeout(2500);
await page.getByRole('button', { name: 'Send' }).click();
await page.waitForURL('/documents');
await page.waitForURL(new RegExp(`/t/${team.url}/documents/\\d+`));
// Assert document was created
await expect(page.getByRole('link', { name: 'Test Title' })).toBeVisible();
@ -306,13 +307,13 @@ test('[DOCUMENT_FLOW]: should be able to create a document with multiple recipie
test('[DOCUMENT_FLOW]: should not be able to create a document without signatures', async ({
page,
}) => {
const user = await seedUser();
const document = await seedBlankDocument(user);
const { user, team } = await seedUser();
const document = await seedBlankDocument(user, team.id);
await apiSignin({
page,
email: user.email,
redirectPath: `/documents/${document.id}/edit`,
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
});
const documentTitle = `example-${Date.now()}.pdf`;
@ -342,10 +343,11 @@ test('[DOCUMENT_FLOW]: should not be able to create a document without signature
});
test('[DOCUMENT_FLOW]: should be able to approve a document', async ({ page }) => {
const user = await seedUser();
const { user, team } = await seedUser();
const { recipients } = await seedPendingDocumentWithFullFields({
owner: user,
teamId: team.id,
recipients: ['user@documenso.com', 'approver@documenso.com'],
recipientsCreateOptions: [
{
@ -393,13 +395,13 @@ test('[DOCUMENT_FLOW]: should be able to approve a document', async ({ page }) =
test('[DOCUMENT_FLOW]: should be able to create, send with redirect url, sign a document and redirect to redirect url', async ({
page,
}) => {
const user = await seedUser();
const document = await seedBlankDocument(user);
const { user, team } = await seedUser();
const document = await seedBlankDocument(user, team.id);
await apiSignin({
page,
email: user.email,
redirectPath: `/documents/${document.id}/edit`,
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
});
const documentTitle = `example-${Date.now()}.pdf`;
@ -430,12 +432,12 @@ test('[DOCUMENT_FLOW]: should be able to create, send with redirect url, sign a
await page.waitForTimeout(2500);
await page.getByRole('button', { name: 'Send' }).click();
await page.waitForURL('/documents');
await page.waitForURL(`/t/${team.url}/documents`);
// Assert document was created
await expect(page.getByRole('link', { name: documentTitle })).toBeVisible();
await page.getByRole('link', { name: documentTitle }).click();
await page.waitForURL(/\/documents\/\d+/);
await page.waitForURL(new RegExp(`/t/${team.url}/documents/\\d+`));
const url = page.url().split('/');
const documentId = url[url.length - 1];
@ -467,11 +469,12 @@ test('[DOCUMENT_FLOW]: should be able to create, send with redirect url, sign a
});
test('[DOCUMENT_FLOW]: should be able to sign a document with custom date', async ({ page }) => {
const user = await seedUser();
const { user, team } = await seedUser();
const now = DateTime.utc();
const { document, recipients } = await seedPendingDocumentWithFullFields({
teamId: team.id,
owner: user,
recipients: ['user1@example.com'],
fields: [FieldType.DATE],
@ -516,13 +519,13 @@ test('[DOCUMENT_FLOW]: should be able to sign a document with custom date', asyn
test('[DOCUMENT_FLOW]: should be able to create and sign a document with 3 recipients in sequential order', async ({
page,
}) => {
const user = await seedUser();
const document = await seedBlankDocument(user);
const { user, team } = await seedUser();
const document = await seedBlankDocument(user, team.id);
await apiSignin({
page,
email: user.email,
redirectPath: `/documents/${document.id}/edit`,
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
});
const documentTitle = `Sequential-Signing-${Date.now()}.pdf`;
@ -579,7 +582,7 @@ test('[DOCUMENT_FLOW]: should be able to create and sign a document with 3 recip
await page.waitForTimeout(2500);
await page.getByRole('button', { name: 'Send' }).click();
await page.waitForURL('/documents');
await page.waitForURL(new RegExp(`/t/${team.url}/documents/\\d+`));
await expect(page.getByRole('link', { name: documentTitle })).toBeVisible();
@ -642,9 +645,10 @@ test('[DOCUMENT_FLOW]: should be able to create and sign a document with 3 recip
test('[DOCUMENT_FLOW]: should prevent out-of-order signing in sequential mode', async ({
page,
}) => {
const user = await seedUser();
const { user, team } = await seedUser();
const { recipients } = await seedPendingDocumentWithFullFields({
teamId: team.id,
owner: user,
recipients: ['user1@example.com', 'user2@example.com', 'user3@example.com'],
fields: [FieldType.SIGNATURE],

View File

@ -13,16 +13,20 @@ import { checkDocumentTabCount } from '../fixtures/documents';
test.describe.configure({ mode: 'serial' });
const seedDeleteDocumentsTestRequirements = async () => {
const [sender, recipientA, recipientB] = await Promise.all([seedUser(), seedUser(), seedUser()]);
const [sender, recipientA, recipientB] = await Promise.all([
seedUser({ setTeamEmailAsOwner: true }),
seedUser({ setTeamEmailAsOwner: true }),
seedUser({ setTeamEmailAsOwner: true }),
]);
const [draftDocument, pendingDocument, completedDocument] = await Promise.all([
seedDraftDocument(sender, [recipientA, recipientB], {
seedDraftDocument(sender.user, sender.team.id, [recipientA.user, recipientB.user], {
createDocumentOptions: { title: 'Document 1 - Draft' },
}),
seedPendingDocument(sender, [recipientA, recipientB], {
seedPendingDocument(sender.user, sender.team.id, [recipientA.user, recipientB.user], {
createDocumentOptions: { title: 'Document 1 - Pending' },
}),
seedCompletedDocument(sender, [recipientA, recipientB], {
seedCompletedDocument(sender.user, sender.team.id, [recipientA.user, recipientB.user], {
createDocumentOptions: { title: 'Document 1 - Completed' },
}),
]);
@ -41,7 +45,8 @@ test('[DOCUMENTS]: seeded documents should be visible', async ({ page }) => {
await apiSignin({
page,
email: sender.email,
email: sender.user.email,
redirectPath: `/t/${sender.team.url}/documents`,
});
await expect(page.getByRole('link', { name: 'Document 1 - Completed' })).toBeVisible();
@ -53,7 +58,8 @@ test('[DOCUMENTS]: seeded documents should be visible', async ({ page }) => {
for (const recipient of recipients) {
await apiSignin({
page,
email: recipient.email,
email: recipient.user.email,
redirectPath: `/t/${recipient.team.url}/documents`,
});
await expect(page.getByRole('link', { name: 'Document 1 - Completed' })).toBeVisible();
@ -72,7 +78,8 @@ test('[DOCUMENTS]: deleting a completed document should not remove it from recip
await apiSignin({
page,
email: sender.email,
email: sender.user.email,
redirectPath: `/t/${sender.team.url}/documents`,
});
// Open document action menu.
@ -95,7 +102,8 @@ test('[DOCUMENTS]: deleting a completed document should not remove it from recip
for (const recipient of recipients) {
await apiSignin({
page,
email: recipient.email,
email: recipient.user.email,
redirectPath: `/t/${recipient.team.url}/documents`,
});
await expect(page.getByRole('link', { name: 'Document 1 - Completed' })).toBeVisible();
@ -113,7 +121,8 @@ test('[DOCUMENTS]: deleting a pending document should remove it from recipients'
await apiSignin({
page,
email: sender.email,
email: sender.user.email,
redirectPath: `/t/${sender.team.url}/documents`,
});
// Open document action menu.
@ -135,6 +144,7 @@ test('[DOCUMENTS]: deleting a pending document should remove it from recipients'
email: recipient.email,
});
// Check dashboard inbox.
await expect(page.getByRole('link', { name: 'Document 1 - Pending' })).not.toBeVisible();
await apiSignout({ page });
}
@ -145,7 +155,8 @@ test('[DOCUMENTS]: deleting draft documents should permanently remove it', async
await apiSignin({
page,
email: sender.email,
email: sender.user.email,
redirectPath: `/t/${sender.team.url}/documents`,
});
// Open document action menu.
@ -174,7 +185,8 @@ test('[DOCUMENTS]: deleting pending documents should permanently remove it', asy
await apiSignin({
page,
email: sender.email,
email: sender.user.email,
redirectPath: `/t/${sender.team.url}/documents`,
});
// Open document action menu.
@ -205,7 +217,8 @@ test('[DOCUMENTS]: deleting completed documents as an owner should hide it from
await apiSignin({
page,
email: sender.email,
email: sender.user.email,
redirectPath: `/t/${sender.team.url}/documents`,
});
// Open document action menu.
@ -231,7 +244,8 @@ test('[DOCUMENTS]: deleting completed documents as an owner should hide it from
await apiSignout({ page });
await apiSignin({
page,
email: recipients[0].email,
email: recipients[0].user.email,
redirectPath: `/t/${recipients[0].team.url}/documents`,
});
// Check document counts.
@ -252,7 +266,8 @@ test('[DOCUMENTS]: deleting documents as a recipient should only hide it for the
await apiSignin({
page,
email: recipientA.email,
email: recipientA.user.email,
redirectPath: `/t/${recipientA.team.url}/documents`,
});
// Open document action menu.
@ -301,7 +316,8 @@ test('[DOCUMENTS]: deleting documents as a recipient should only hide it for the
await apiSignout({ page });
await apiSignin({
page,
email: sender.email,
email: sender.user.email,
redirectPath: `/t/${sender.team.url}/documents`,
});
// Check document counts for sender.
@ -315,7 +331,8 @@ test('[DOCUMENTS]: deleting documents as a recipient should only hide it for the
await apiSignout({ page });
await apiSignin({
page,
email: recipientB.email,
email: recipientB.user.email,
redirectPath: `/t/${recipientB.team.url}/documents`,
});
// Check document counts for other recipient.

View File

@ -14,12 +14,15 @@ import { signSignaturePad } from '../fixtures/signature';
test.describe('Signing Certificate Tests', () => {
test('individual document should always include signing certificate', async ({ page }) => {
const user = await seedUser();
const { user, team } = await seedUser({
isPersonalOrganisation: true,
});
const { document, recipients } = await seedPendingDocumentWithFullFields({
owner: user,
recipients: ['signer@example.com'],
fields: [FieldType.SIGNATURE],
teamId: team.id,
});
const documentData = await prisma.documentData
@ -76,20 +79,28 @@ test.describe('Signing Certificate Tests', () => {
test('team document with signing certificate enabled should include certificate', async ({
page,
}) => {
const team = await seedTeam();
const { owner, team } = await seedTeam();
const { document, recipients } = await seedPendingDocumentWithFullFields({
owner: team.owner,
owner: owner,
recipients: ['signer@example.com'],
fields: [FieldType.SIGNATURE],
updateDocumentOptions: {
teamId: team.id,
teamId: team.id,
});
const teamSettingsId = await prisma.teamGlobalSettings.findFirstOrThrow({
where: {
team: {
id: team.id,
},
},
});
await prisma.teamGlobalSettings.create({
await prisma.teamGlobalSettings.update({
where: {
id: teamSettingsId.id,
},
data: {
teamId: team.id,
includeSigningCertificate: true,
},
});
@ -148,20 +159,28 @@ test.describe('Signing Certificate Tests', () => {
test('team document with signing certificate disabled should not include certificate', async ({
page,
}) => {
const team = await seedTeam();
const { owner, team } = await seedTeam();
const { document, recipients } = await seedPendingDocumentWithFullFields({
owner: team.owner,
owner: owner,
recipients: ['signer@example.com'],
fields: [FieldType.SIGNATURE],
updateDocumentOptions: {
teamId: team.id,
teamId: team.id,
});
const teamSettingsId = await prisma.teamGlobalSettings.findFirstOrThrow({
where: {
team: {
id: team.id,
},
},
});
await prisma.teamGlobalSettings.create({
await prisma.teamGlobalSettings.update({
where: {
id: teamSettingsId.id,
},
data: {
teamId: team.id,
includeSigningCertificate: false,
},
});
@ -218,16 +237,22 @@ test.describe('Signing Certificate Tests', () => {
});
test('team can toggle signing certificate setting', async ({ page }) => {
const team = await seedTeam();
const { owner, team } = await seedTeam();
await apiSignin({
page,
email: team.owner.email,
email: owner.email,
redirectPath: `/t/${team.url}/settings/preferences`,
});
// Toggle signing certificate setting
await page.getByLabel('Include the Signing Certificate in the Document').click();
await page
.getByRole('group')
.locator('div')
.filter({ hasText: 'Include the Signing' })
.getByRole('combobox')
.click();
await page.getByRole('option', { name: 'No' }).click();
await page
.getByRole('button', { name: /Update/ })
.first()
@ -244,7 +269,13 @@ test.describe('Signing Certificate Tests', () => {
expect(updatedTeam.teamGlobalSettings?.includeSigningCertificate).toBe(false);
// Toggle the setting back to true
await page.getByLabel('Include the Signing Certificate in the Document').click();
await page
.getByRole('group')
.locator('div')
.filter({ hasText: 'Include the Signing' })
.getByRole('combobox')
.click();
await page.getByRole('option', { name: 'Yes' }).click();
await page
.getByRole('button', { name: /Update/ })
.first()

View File

@ -17,7 +17,7 @@ export const apiSignin = async ({
page,
email = 'example@documenso.com',
password = 'password',
redirectPath = '/documents',
redirectPath = '/',
}: LoginOptions) => {
const { request } = page.context();

View File

@ -0,0 +1,9 @@
import { type Page, expect } from '@playwright/test';
export const expectTextToBeVisible = async (page: Page, text: string) => {
await expect(page.getByText(text).first()).toBeVisible();
};
export const expectTextToNotBeVisible = async (page: Page, text: string) => {
await expect(page.getByText(text).first()).not.toBeVisible();
};

View File

@ -1,866 +0,0 @@
import { expect, test } from '@playwright/test';
import path from 'node:path';
import { FolderType } from '@documenso/prisma/client';
import { seedBlankDocument } from '@documenso/prisma/seed/documents';
import { seedBlankFolder } from '@documenso/prisma/seed/folders';
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
test.describe.configure({ mode: 'parallel' });
test('create folder button is visible on documents page', async ({ page }) => {
const user = await seedUser();
await apiSignin({
page,
email: user.email,
redirectPath: '/',
});
await expect(page.getByRole('button', { name: 'Create Folder' })).toBeVisible();
});
test('user can create a document folder', async ({ page }) => {
const user = await seedUser();
await apiSignin({
page,
email: user.email,
redirectPath: '/',
});
await page.getByRole('button', { name: 'Create Folder' }).click();
await expect(page.getByRole('dialog', { name: 'Create New folder' })).toBeVisible();
await page.getByLabel('Folder name').fill('My folder');
await page.getByRole('button', { name: 'Create' }).click();
await page.waitForTimeout(1000);
await expect(page.getByText('My folder')).toBeVisible();
await page.goto('/documents');
await expect(page.locator('div').filter({ hasText: 'My folder' }).nth(3)).toBeVisible();
});
test('user can create a document subfolder inside a document folder', async ({ page }) => {
const user = await seedUser();
const folder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Client Contracts',
},
});
await apiSignin({
page,
email: user.email,
redirectPath: `/documents/f/${folder.id}`,
});
await expect(page.getByText('Client Contracts')).toBeVisible();
await page.getByRole('button', { name: 'Create Folder' }).click();
await expect(page.getByRole('dialog', { name: 'Create New folder' })).toBeVisible();
await page.getByLabel('Folder name').fill('Invoices');
await page.getByRole('button', { name: 'Create' }).click();
await page.waitForTimeout(1000);
await expect(page.getByText('Invoices')).toBeVisible();
});
test('user can create a document inside a document folder', async ({ page }) => {
const user = await seedUser();
const folder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Client Contracts',
},
});
await apiSignin({
page,
email: user.email,
redirectPath: `/documents/f/${folder.id}`,
});
const fileInput = page.locator('input[type="file"]').nth(1);
await fileInput.waitFor({ state: 'attached' });
await fileInput.setInputFiles(
path.join(__dirname, '../../../assets/documenso-supporter-pledge.pdf'),
);
await page.waitForTimeout(3000);
await expect(page.getByText('documenso-supporter-pledge.pdf')).toBeVisible();
await page.goto(`/documents/f/${folder.id}`);
await expect(page.getByText('documenso-supporter-pledge.pdf')).toBeVisible();
});
test('user can pin a document folder', async ({ page }) => {
const user = await seedUser();
await seedBlankFolder(user, {
createFolderOptions: {
name: 'Contracts',
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/documents',
});
await page.getByRole('button', { name: '•••' }).click();
await page.getByRole('menuitem', { name: 'Pin' }).click();
await page.reload();
await expect(page.locator('svg.text-documenso.h-3.w-3')).toBeVisible();
});
test('user can unpin a document folder', async ({ page }) => {
const user = await seedUser();
await seedBlankFolder(user, {
createFolderOptions: {
name: 'Contracts',
pinned: true,
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/documents',
});
await page.getByRole('button', { name: '•••' }).click();
await page.getByRole('menuitem', { name: 'Unpin' }).click();
await page.reload();
await expect(page.locator('svg.text-documenso.h-3.w-3')).not.toBeVisible();
});
test('user can rename a document folder', async ({ page }) => {
const user = await seedUser();
await seedBlankFolder(user, {
createFolderOptions: {
name: 'Contracts',
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/documents',
});
await page.getByRole('button', { name: '•••' }).click();
await page.getByRole('menuitem', { name: 'Settings' }).click();
await page.getByLabel('Name').fill('Archive');
await page.getByRole('button', { name: 'Save Changes' }).click();
await expect(page.getByText('Archive')).toBeVisible();
});
test('document folder visibility is not visible to user', async ({ page }) => {
const user = await seedUser();
await seedBlankFolder(user, {
createFolderOptions: {
name: 'Contracts',
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/documents',
});
await page.getByRole('button', { name: '•••' }).click();
await page.getByRole('menuitem', { name: 'Settings' }).click();
await expect(page.getByRole('menuitem', { name: 'Visibility' })).not.toBeVisible();
});
test('document folder can be moved to another document folder', async ({ page }) => {
const user = await seedUser();
const folder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Clients',
},
});
await seedBlankFolder(user, {
createFolderOptions: {
name: 'Contracts',
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/documents',
});
await page.getByRole('button', { name: '•••' }).nth(0).click();
await page.getByRole('menuitem', { name: 'Move' }).click();
await page.getByRole('button', { name: 'Clients' }).click();
await page.getByRole('button', { name: 'Move Folder' }).click();
await page.waitForTimeout(1000);
await page.goto(`/documents/f/${folder.id}`);
await expect(page.getByText('Contracts')).toBeVisible();
});
test('document folder can be moved to the root', async ({ page }) => {
const user = await seedUser();
const parentFolder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Clients',
},
});
await seedBlankFolder(user, {
createFolderOptions: {
name: 'Contracts',
parentId: parentFolder.id,
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/documents',
});
await page.getByText('Clients').click();
await page.getByRole('button', { name: '•••' }).nth(0).click();
await page.getByRole('menuitem', { name: 'Move' }).click();
await page.getByRole('button', { name: 'Root' }).click();
await page.getByRole('button', { name: 'Move Folder' }).click();
await page.waitForTimeout(1000);
await page.goto('/documents');
await expect(page.getByText('Clients')).toBeVisible();
});
test('document folder and its contents can be deleted', async ({ page }) => {
const user = await seedUser();
const folder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Proposals',
},
});
const proposal = await seedBlankDocument(user, {
createDocumentOptions: {
title: 'Proposal 1',
folderId: folder.id,
},
});
const reportsFolder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Reports',
parentId: folder.id,
},
});
const report = await seedBlankDocument(user, {
createDocumentOptions: {
title: 'Report 1',
folderId: reportsFolder.id,
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/documents',
});
await page.getByRole('button', { name: '•••' }).click();
await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByRole('textbox').fill(`delete ${folder.name}`);
await page.getByRole('button', { name: 'Delete' }).click();
await page.goto('/documents');
await expect(page.locator('div').filter({ hasText: folder.name })).not.toBeVisible();
await expect(page.getByText(proposal.title)).not.toBeVisible();
await page.goto(`/documents/f/${folder.id}`);
await expect(page.getByText(report.title)).not.toBeVisible();
await expect(page.locator('div').filter({ hasText: reportsFolder.name })).not.toBeVisible();
});
test('user can move a document to a document folder', async ({ page }) => {
const user = await seedUser();
const folder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Proposals',
},
});
await seedBlankDocument(user, {
createDocumentOptions: {
title: 'Proposal 1',
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/documents',
});
await expect(async () => {
await page.getByTestId('document-table-action-btn').first().click();
await page.waitForTimeout(1000);
await expect(page.getByRole('menuitem', { name: 'Move to Folder' })).toBeVisible();
}).toPass();
await page.getByRole('menuitem', { name: 'Move to Folder' }).click();
await page.getByRole('button', { name: 'Proposals' }).click();
await page.getByRole('button', { name: 'Move' }).click();
await page.waitForTimeout(1000);
await page.goto(`/documents/f/${folder.id}`);
await expect(page.getByText('Proposal 1')).toBeVisible();
});
test('user can move a document from folder to the root', async ({ page }) => {
const user = await seedUser();
const folder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Proposals',
},
});
await seedBlankDocument(user, {
createDocumentOptions: {
title: 'Proposal 1',
folderId: folder.id,
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/documents',
});
await page.getByText('Proposals').click();
await expect(async () => {
await page.getByTestId('document-table-action-btn').first().click();
await page.waitForTimeout(1000);
await expect(page.getByRole('menuitem', { name: 'Move to Folder' })).toBeVisible();
}).toPass();
await page.getByRole('menuitem', { name: 'Move to Folder' }).click();
await page.getByRole('button', { name: 'Root' }).click();
await page.getByRole('button', { name: 'Move' }).click();
await page.waitForTimeout(1000);
await page.goto('/documents');
await expect(page.getByText('Proposal 1')).toBeVisible();
});
test('create folder button is visible on templates page', async ({ page }) => {
const user = await seedUser();
await apiSignin({
page,
email: user.email,
redirectPath: '/templates',
});
await expect(page.getByRole('button', { name: 'Create folder' })).toBeVisible();
});
test('user can create a template folder', async ({ page }) => {
const user = await seedUser();
await apiSignin({
page,
email: user.email,
redirectPath: '/templates',
});
await page.getByRole('button', { name: 'Create folder' }).click();
await expect(page.getByRole('dialog', { name: 'Create New folder' })).toBeVisible();
await page.getByLabel('Folder name').fill('My template folder');
await page.getByRole('button', { name: 'Create' }).click();
await page.waitForTimeout(1000);
await expect(page.getByText('My template folder')).toBeVisible();
await page.goto('/templates');
await expect(page.locator('div').filter({ hasText: 'My template folder' }).nth(3)).toBeVisible();
});
test('user can create a template subfolder inside a template folder', async ({ page }) => {
const user = await seedUser();
const folder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Client Templates',
type: FolderType.TEMPLATE,
},
});
await apiSignin({
page,
email: user.email,
redirectPath: `/templates/f/${folder.id}`,
});
await expect(page.getByText('Client Templates')).toBeVisible();
await page.getByRole('button', { name: 'Create folder' }).click();
await expect(page.getByRole('dialog', { name: 'Create New folder' })).toBeVisible();
await page.getByLabel('Folder name').fill('Contract Templates');
await page.getByRole('button', { name: 'Create' }).click();
await page.waitForTimeout(1000);
await expect(page.getByText('Contract Templates')).toBeVisible();
});
test('user can create a template inside a template folder', async ({ page }) => {
const user = await seedUser();
const folder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Client Templates',
type: FolderType.TEMPLATE,
},
});
await apiSignin({
page,
email: user.email,
redirectPath: `/templates/f/${folder.id}`,
});
await expect(page.getByText('Client Templates')).toBeVisible();
await page.getByRole('button', { name: 'New Template' }).click();
// await expect(page.getByRole('dialog', { name: 'New Template' })).toBeVisible();
await page
.locator('div')
.filter({ hasText: /^Upload Template DocumentDrag & drop your PDF here\.$/ })
.nth(2)
.click();
await page.locator('input[type="file"]').waitFor({ state: 'attached' });
await page
.locator('input[type="file"]')
.setInputFiles(path.join(__dirname, '../../../assets/documenso-supporter-pledge.pdf'));
await page.waitForTimeout(3000);
await page.getByRole('button', { name: 'Create' }).click();
await page.waitForTimeout(1000);
await expect(page.getByText('documenso-supporter-pledge.pdf')).toBeVisible();
await page.goto(`/templates/f/${folder.id}`);
await expect(page.getByText('documenso-supporter-pledge.pdf')).toBeVisible();
});
test('user can pin a template folder', async ({ page }) => {
const user = await seedUser();
await seedBlankFolder(user, {
createFolderOptions: {
name: 'Contract Templates',
type: FolderType.TEMPLATE,
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/templates',
});
await page.getByRole('button', { name: '•••' }).click();
await page.getByRole('menuitem', { name: 'Pin' }).click();
await page.reload();
await expect(page.locator('svg.text-documenso.h-3.w-3')).toBeVisible();
});
test('user can unpin a template folder', async ({ page }) => {
const user = await seedUser();
await seedBlankFolder(user, {
createFolderOptions: {
name: 'Contract Templates',
pinned: true,
type: FolderType.TEMPLATE,
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/templates',
});
await page.getByRole('button', { name: '•••' }).click();
await page.getByRole('menuitem', { name: 'Unpin' }).click();
await page.reload();
await expect(page.locator('svg.text-documenso.h-3.w-3')).not.toBeVisible();
});
test('user can rename a template folder', async ({ page }) => {
const user = await seedUser();
await seedBlankFolder(user, {
createFolderOptions: {
name: 'Contract Templates',
type: FolderType.TEMPLATE,
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/templates',
});
await page.getByRole('button', { name: '•••' }).click();
await page.getByRole('menuitem', { name: 'Settings' }).click();
await page.getByLabel('Name').fill('Updated Template Folder');
await page.getByRole('button', { name: 'Save Changes' }).click();
await expect(page.getByText('Updated Template Folder')).toBeVisible();
});
test('template folder visibility is not visible to user', async ({ page }) => {
const user = await seedUser();
await seedBlankFolder(user, {
createFolderOptions: {
name: 'Contract Templates',
type: FolderType.TEMPLATE,
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/templates',
});
await page.getByRole('button', { name: '•••' }).click();
await page.getByRole('menuitem', { name: 'Settings' }).click();
await expect(page.getByRole('menuitem', { name: 'Visibility' })).not.toBeVisible();
});
test('template folder can be moved to another template folder', async ({ page }) => {
const user = await seedUser();
const folder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Client Templates',
type: FolderType.TEMPLATE,
},
});
await seedBlankFolder(user, {
createFolderOptions: {
name: 'Contract Templates',
type: FolderType.TEMPLATE,
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/templates',
});
await page.getByRole('button', { name: '•••' }).nth(0).click();
await page.getByRole('menuitem', { name: 'Move' }).click();
await page.getByRole('button', { name: 'Client Templates' }).click();
await page.getByRole('button', { name: 'Move Folder' }).click();
await page.waitForTimeout(1000);
await page.goto(`/templates/f/${folder.id}`);
await expect(page.getByText('Contract Templates')).toBeVisible();
});
test('template folder can be moved to the root', async ({ page }) => {
const user = await seedUser();
const parentFolder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Client Templates',
type: FolderType.TEMPLATE,
},
});
await seedBlankFolder(user, {
createFolderOptions: {
name: 'Contract Templates',
parentId: parentFolder.id,
type: FolderType.TEMPLATE,
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/templates',
});
await page.getByText('Client Templates').click();
await page.getByRole('button', { name: '•••' }).nth(0).click();
await page.getByRole('menuitem', { name: 'Move' }).click();
await page.getByRole('button', { name: 'Root' }).click();
await page.getByRole('button', { name: 'Move Folder' }).click();
await page.waitForTimeout(1000);
await page.goto('/templates');
await expect(page.getByText('Contract Templates')).toBeVisible();
});
test('template folder and its contents can be deleted', async ({ page }) => {
const user = await seedUser();
const folder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Proposal Templates',
type: FolderType.TEMPLATE,
},
});
const template = await seedBlankTemplate(user, {
createTemplateOptions: {
title: 'Proposal Template 1',
folderId: folder.id,
},
});
const subfolder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Report Templates',
parentId: folder.id,
type: FolderType.TEMPLATE,
},
});
const reportTemplate = await seedBlankTemplate(user, {
createTemplateOptions: {
title: 'Report Template 1',
folderId: subfolder.id,
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/templates',
});
await page.getByRole('button', { name: '•••' }).click();
await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByRole('textbox').fill(`delete ${folder.name}`);
await page.getByRole('button', { name: 'Delete' }).click();
await page.goto('/templates');
await expect(page.locator('div').filter({ hasText: folder.name })).not.toBeVisible();
await expect(page.getByText(template.title)).not.toBeVisible();
await page.goto(`/templates/f/${folder.id}`);
await expect(page.getByText(reportTemplate.title)).not.toBeVisible();
await expect(page.locator('div').filter({ hasText: subfolder.name })).not.toBeVisible();
});
test('user can navigate between template folders', async ({ page }) => {
const user = await seedUser();
const parentFolder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Client Templates',
type: FolderType.TEMPLATE,
},
});
const subfolder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Contract Templates',
parentId: parentFolder.id,
type: FolderType.TEMPLATE,
},
});
await seedBlankTemplate(user, {
createTemplateOptions: {
title: 'Contract Template 1',
folderId: subfolder.id,
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/templates',
});
await page.getByText('Client Templates').click();
await expect(page.getByText('Contract Templates')).toBeVisible();
await page.getByText('Contract Templates').click();
await expect(page.getByText('Contract Template 1')).toBeVisible();
await page.getByRole('button', { name: parentFolder.name }).click();
await expect(page.getByText('Contract Templates')).toBeVisible();
await page.getByRole('button', { name: subfolder.name }).click();
await expect(page.getByText('Contract Template 1')).toBeVisible();
});
test('user can move a template to a template folder', async ({ page }) => {
const user = await seedUser();
const folder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Client Templates',
type: FolderType.TEMPLATE,
},
});
await seedBlankTemplate(user, {
createTemplateOptions: {
title: 'Proposal Template 1',
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/templates',
});
await expect(async () => {
await page.getByTestId('template-table-action-btn').first().click();
await page.waitForTimeout(1000);
await expect(page.getByRole('menuitem', { name: 'Move to Folder' })).toBeVisible();
}).toPass();
await page.getByRole('menuitem', { name: 'Move to Folder' }).click();
await page.getByRole('button', { name: 'Client Templates' }).click();
await page.getByRole('button', { name: 'Move' }).click();
await page.goto(`/templates/f/${folder.id}`);
await page.waitForTimeout(1000);
await expect(page.getByText('Proposal Template 1')).toBeVisible();
});
test('user can move a template from a folder to the root', async ({ page }) => {
const user = await seedUser();
const folder = await seedBlankFolder(user, {
createFolderOptions: {
name: 'Client Templates',
type: FolderType.TEMPLATE,
},
});
await seedBlankTemplate(user, {
createTemplateOptions: {
title: 'Proposal Template 1',
folderId: folder.id,
},
});
await apiSignin({
page,
email: user.email,
redirectPath: '/templates',
});
await page.getByText('Client Templates').click();
await expect(async () => {
await page.getByTestId('template-table-action-btn').first().click();
await page.waitForTimeout(1000);
await expect(page.getByRole('menuitem', { name: 'Move to Folder' })).toBeVisible();
}).toPass();
await page.getByRole('menuitem', { name: 'Move to Folder' }).click();
await page.getByRole('button', { name: 'Root' }).click();
await page.getByRole('button', { name: 'Move' }).click();
await page.waitForTimeout(1000);
await page.goto('/templates');
await expect(page.getByText('Proposal Template 1')).toBeVisible();
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,565 @@
import { expect, test } from '@playwright/test';
import { createTeam } from '@documenso/lib/server-only/team/create-team';
import { nanoid } from '@documenso/lib/universal/id';
import { seedOrganisationMembers } from '@documenso/prisma/seed/organisations';
import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin, apiSignout } from '../fixtures/authentication';
import { expectTextToBeVisible, expectTextToNotBeVisible } from '../fixtures/generic';
test('[ORGANISATIONS]: create and delete organisation', async ({ page }) => {
const { user, organisation } = await seedUser({
isPersonalOrganisation: false,
});
await apiSignin({
page,
email: user.email,
redirectPath: `/settings/organisations`,
});
await expect(page.getByRole('button', { name: 'Leave' })).toBeDisabled();
await page.getByRole('link', { name: 'Manage' }).click();
await page.waitForURL(`/o/${organisation.url}/settings/general`);
await page.getByRole('button', { name: 'Delete' }).click();
await page
.getByLabel(`Confirm by typing delete ${organisation.name}`)
.fill(`delete ${organisation.name}`);
await page.getByRole('button', { name: 'Delete' }).click();
await page.waitForURL(`/settings/organisations`);
await expect(page.getByText('No results found')).toBeVisible();
await page.getByRole('button', { name: 'Create organisation' }).click();
await page.getByLabel('Organisation Name*').fill('test');
await page.getByRole('button', { name: 'Create' }).click();
await expect(page.getByText('Your organisation has been created').first()).toBeVisible();
await page.reload();
await page.getByRole('row').filter({ hasText: 'test' }).getByRole('link').nth(1).click();
});
test('[ORGANISATIONS]: manage general settings', async ({ page }) => {
const { user, organisation } = await seedUser({
isPersonalOrganisation: false,
});
await apiSignin({
page,
email: user.email,
redirectPath: `/o/${organisation.url}/settings/general`,
});
const updatedOrganisationId = `organisation-${Date.now()}`;
// Update team.
await page.getByLabel('Organisation Name*').click();
await page.getByLabel('Organisation Name*').clear();
await page.getByLabel('Organisation Name*').fill(updatedOrganisationId);
await page.getByLabel('Organisation URL*').click();
await page.getByLabel('Organisation URL*').clear();
await page.getByLabel('Organisation URL*').fill(updatedOrganisationId);
await page.getByRole('button', { name: 'Update organisation' }).click();
// Check we have been redirected to the new organisation URL and the name is updated.
await page.waitForURL(`/o/${updatedOrganisationId}/settings/general`);
});
test('[ORGANISATIONS]: inherit members', async ({ page }) => {
const {
user,
organisation,
team: teamWithInheritMembers,
} = await seedUser({
isPersonalOrganisation: false,
});
const teamWithoutInheritedMembersUrl = `team-${nanoid()}`;
await createTeam({
userId: user.id,
teamName: 'No inherit',
teamUrl: teamWithoutInheritedMembersUrl,
organisationId: organisation.id,
inheritMembers: false,
});
const memberEmail = `member-${nanoid()}@test.documenso.com`;
const memberEmail2 = `member-2-${nanoid()}@test.documenso.com`;
const memberEmail3 = `member-3-${nanoid()}@test.documenso.com`;
const managerEmail = `manager-${nanoid()}@test.documenso.com`;
const adminEmail = `admin-${nanoid()}@test.documenso.com`;
const ownerEmail = user.email;
await seedOrganisationMembers({
members: [
{
email: memberEmail,
name: 'Member 1',
organisationRole: 'MEMBER',
},
{
email: memberEmail2,
name: 'Member 2',
organisationRole: 'MEMBER',
},
{
email: memberEmail3,
name: 'Member 3',
organisationRole: 'MEMBER',
},
{
email: managerEmail,
name: 'Manager',
organisationRole: 'MANAGER',
},
{
email: adminEmail,
name: 'Admin',
organisationRole: 'ADMIN',
},
],
organisationId: organisation.id,
});
await apiSignin({
page,
email: user.email,
redirectPath: `/t/${teamWithoutInheritedMembersUrl}/settings/members`,
});
// Check from admin POV that member counts are correct
// You should only see the manager/admins from the organisation in this table.
await expect(
page.getByRole('row').filter({ hasText: 'Team Admin' }).getByText(managerEmail),
).toBeVisible();
await expect(
page.getByRole('row').filter({ hasText: 'Team Admin' }).getByText(adminEmail),
).toBeVisible();
await expect(
page.getByRole('row').filter({ hasText: 'Team Admin' }).getByText(ownerEmail),
).toBeVisible();
await expect(page.getByRole('row').filter({ hasText: memberEmail })).not.toBeVisible();
await expect(page.getByRole('row').filter({ hasText: memberEmail2 })).not.toBeVisible();
await expect(page.getByRole('row').filter({ hasText: memberEmail3 })).not.toBeVisible();
// Explicitly add a member to the team.
await page.getByRole('button', { name: 'Add members' }).click();
await page.getByRole('combobox').click();
await page.getByRole('option', { name: 'Member 1' }).first().click();
await page.getByRole('button', { name: 'Next' }).click();
await page.getByRole('button', { name: 'Add Members' }).click();
await expect(
page.getByRole('row').filter({ hasText: 'Team Member' }).getByText(memberEmail),
).toBeVisible();
await page.goto(`/t/${teamWithInheritMembers.url}/settings/members`);
// Check from member POV that member counts are correct for inherit members team.
await expect(
page.getByRole('row').filter({ hasText: 'Team Admin' }).getByText(managerEmail),
).toBeVisible();
await expect(
page.getByRole('row').filter({ hasText: 'Team Admin' }).getByText(adminEmail),
).toBeVisible();
await expect(
page.getByRole('row').filter({ hasText: 'Team Admin' }).getByText(ownerEmail),
).toBeVisible();
await expect(
page.getByRole('row').filter({ hasText: 'Team Member' }).getByText(memberEmail),
).toBeVisible();
await expect(
page.getByRole('row').filter({ hasText: 'Team Member' }).getByText(memberEmail2),
).toBeVisible();
await expect(
page.getByRole('row').filter({ hasText: 'Team Member' }).getByText(memberEmail3),
).toBeVisible();
// Disable inherit mode.
await page.goto(`/t/${teamWithInheritMembers.url}/settings/groups`);
await page.getByRole('button', { name: 'Disable access' }).click();
await page.getByRole('button', { name: 'Disable' }).click();
await expect(page.getByText('Enable Access').first()).toBeVisible();
// Expect the inherited members to disappear
await page.goto(`/t/${teamWithInheritMembers.url}/settings/members`);
await expect(
page.getByRole('row').filter({ hasText: 'Team Admin' }).getByText(managerEmail),
).toBeVisible();
await expect(
page.getByRole('row').filter({ hasText: 'Team Admin' }).getByText(adminEmail),
).toBeVisible();
await expect(
page.getByRole('row').filter({ hasText: 'Team Admin' }).getByText(ownerEmail),
).toBeVisible();
await expect(page.getByRole('row').filter({ hasText: memberEmail })).not.toBeVisible();
await expect(page.getByRole('row').filter({ hasText: memberEmail2 })).not.toBeVisible();
await expect(page.getByRole('row').filter({ hasText: memberEmail3 })).not.toBeVisible();
});
test('[ORGANISATIONS]: manage groups and members', async ({ page }) => {
const {
user,
organisation,
team: teamInherit,
} = await seedUser({
isPersonalOrganisation: false,
});
const teamInheritName = teamInherit.name;
const teamA = `team-${nanoid()}`;
const teamAName = `TeamA - No inherit`;
const teamB = `team-${nanoid()}`;
const teamBName = `TeamB - No inherit`;
await createTeam({
userId: user.id,
teamName: teamAName,
teamUrl: teamA,
organisationId: organisation.id,
inheritMembers: false,
});
await createTeam({
userId: user.id,
teamName: teamBName,
teamUrl: teamB,
organisationId: organisation.id,
inheritMembers: false,
});
const memberEmail1 = `member-1-${nanoid()}@test.documenso.com`;
const memberEmail2 = `member-2-${nanoid()}@test.documenso.com`;
const memberEmail3 = `member-3-${nanoid()}@test.documenso.com`;
const memberEmail4 = `member-4-${nanoid()}@test.documenso.com`;
const memberEmail5 = `member-5-${nanoid()}@test.documenso.com`;
const memberEmail6 = `member-6-${nanoid()}@test.documenso.com`;
const adminEmail1 = `admin-1-${nanoid()}@test.documenso.com`;
const adminEmail2 = `admin-2-${nanoid()}@test.documenso.com`;
const adminEmail3 = `admin-3-${nanoid()}@test.documenso.com`;
const ownerEmail = user.email;
await seedOrganisationMembers({
members: [
{
email: memberEmail1,
name: 'Member1',
organisationRole: 'MEMBER',
},
{
email: memberEmail2,
name: 'Member2',
organisationRole: 'MEMBER',
},
{
email: memberEmail3,
name: 'Member3',
organisationRole: 'MEMBER',
},
{
email: memberEmail4,
name: 'Member4',
organisationRole: 'MEMBER',
},
{
email: memberEmail5,
name: 'Member5',
organisationRole: 'MEMBER',
},
{
email: memberEmail6,
name: 'Member6',
organisationRole: 'MEMBER',
},
{
email: adminEmail1,
name: 'Admin1',
organisationRole: 'ADMIN',
},
{
email: adminEmail2,
name: 'Admin2',
organisationRole: 'ADMIN',
},
{
email: adminEmail3,
name: 'Admin3',
organisationRole: 'ADMIN',
},
],
organisationId: organisation.id,
});
await apiSignin({
page,
email: user.email,
redirectPath: `/o/${organisation.url}/settings/groups`,
});
// Create a custom group A with 3 members "ORGANISATION ADMIN" to check that they get the correct roles.
await page.getByRole('button', { name: 'Create group' }).click();
await page.getByRole('textbox', { name: 'Group Name *' }).fill('CUSTOM_GROUP');
await page.getByRole('combobox').filter({ hasText: 'Organisation Member' }).click();
await page.getByRole('option', { name: 'Organisation Admin' }).click();
await page.getByRole('combobox').filter({ hasText: 'Select members' }).click();
await page.getByRole('option', { name: 'Member1' }).click();
await page.getByRole('option', { name: 'Member2' }).click();
await page.getByRole('option', { name: 'Member3' }).click();
await page.getByTestId('dialog-create-organisation-button').click();
await expect(page.getByText('Group has been created.').first()).toBeVisible();
await page.goto(`/o/${organisation.url}/settings/members`);
// Confirm org roles have been applied to these members.
await expect(
page.getByRole('row').filter({ hasText: 'Organisation Admin' }).getByText(memberEmail1),
).toBeVisible();
await expect(
page.getByRole('row').filter({ hasText: 'Organisation Admin' }).getByText(memberEmail2),
).toBeVisible();
await expect(
page.getByRole('row').filter({ hasText: 'Organisation Admin' }).getByText(memberEmail3),
).toBeVisible();
// Test updating the group.
await page.goto(`/o/${organisation.url}/settings/groups`);
await page.getByRole('link', { name: 'Manage' }).click();
await page.getByRole('textbox', { name: 'Group Name *' }).fill('CUSTOM_GROUP_A');
await page.getByRole('combobox').filter({ hasText: 'Organisation Admin' }).click();
await page.getByRole('option', { name: 'Organisation Member' }).click();
await page.getByRole('combobox').filter({ hasText: 'Member1, Member2, Member3' }).click();
await page.getByRole('option', { name: 'Member3' }).click();
await page.getByRole('button', { name: 'Update' }).click();
await expect(page.getByText('Group has been updated successfully').first()).toBeVisible();
await page.goto(`/o/${organisation.url}/settings/groups`);
// Create a custom member group with the 3 admins to check that they still get the ADMIN roles.
await page.getByRole('button', { name: 'Create group' }).click();
await page.getByRole('textbox', { name: 'Group Name *' }).fill('CUSTOM_GROUP_ADMINS');
await page.getByRole('combobox').filter({ hasText: 'Select members' }).click();
await page.getByRole('option', { name: 'Admin1' }).click();
await page.getByRole('option', { name: 'Admin2' }).click();
await page.getByRole('option', { name: 'Admin3' }).click();
await page.getByTestId('dialog-create-organisation-button').click();
await expect(page.getByText('Group has been created.').first()).toBeVisible();
await page.goto(`/o/${organisation.url}/settings/members`);
// Confirm admins still get admin roles.
await expect(
page.getByRole('row').filter({ hasText: 'Organisation Admin' }).getByText(adminEmail1),
).toBeVisible();
await expect(
page.getByRole('row').filter({ hasText: 'Organisation Admin' }).getByText(adminEmail2),
).toBeVisible();
await expect(
page.getByRole('row').filter({ hasText: 'Organisation Admin' }).getByText(adminEmail3),
).toBeVisible();
// Create another custom group with 3 members with "ORGANISATION MEMBER" role.
await page.goto(`/o/${organisation.url}/settings/groups`);
await page.getByRole('button', { name: 'Create group' }).click();
await page.getByRole('textbox', { name: 'Group Name *' }).fill('CUSTOM_GROUP_B');
await page.getByRole('combobox').filter({ hasText: 'Organisation Member' }).click();
await page.getByRole('option', { name: 'Organisation Admin' }).click();
await page.getByRole('combobox').filter({ hasText: 'Select members' }).click();
await page.getByRole('option', { name: 'Member4' }).click();
await page.getByRole('option', { name: 'Member5' }).click();
await page.getByTestId('dialog-create-organisation-button').click();
await expect(page.getByText('Group has been created.').first()).toBeVisible();
// Assign CUSTOM_GROUP_A to TeamA
await page.goto(`/t/${teamA}/settings/groups`);
await page.getByRole('button', { name: 'Add groups' }).click();
await page.getByRole('combobox').click();
await page.getByRole('option', { name: 'CUSTOM_GROUP_A', exact: true }).click();
await page.getByRole('button', { name: 'Next' }).click();
await page.getByRole('combobox').click();
await page.getByRole('option', { name: 'Manager' }).click();
await page.getByRole('button', { name: 'Create Groups' }).click();
await expect(page.getByText('Team members have been added').first()).toBeVisible();
// Assign CUSTOM_GROUP_B to TeamA
await page.goto(`/t/${teamA}/settings/groups`);
await page.getByRole('button', { name: 'Add groups' }).click();
await page.getByRole('combobox').click();
await page.getByRole('option', { name: 'CUSTOM_GROUP_B', exact: true }).click();
await page.getByRole('button', { name: 'Next' }).click();
await page.getByRole('combobox').click();
await page.getByRole('option', { name: 'Manager' }).click();
await page.getByRole('button', { name: 'Create Groups' }).click();
await expect(page.getByText('Team members have been added').first()).toBeVisible();
// Update CUSTOM_GROUP_B
await page.getByRole('row', { name: 'CUSTOM_GROUP_B' }).getByRole('button').click();
await page.getByRole('menuitem', { name: 'Update role' }).click();
await page.getByRole('combobox').click();
await page.getByRole('option', { name: 'Team Admin' }).click();
await page.getByRole('button', { name: 'Update' }).click();
await expectTextToBeVisible(page, 'You have updated the team group');
await expect(page.getByText('Team Admin').first()).toBeVisible();
await page.reload();
// Delete CUSTOM_GROUP_B
await page.getByRole('row', { name: 'CUSTOM_GROUP_B' }).getByRole('button').click();
await page.getByRole('menuitem', { name: 'Remove' }).click();
await page.getByRole('button', { name: 'Delete' }).click();
await expectTextToBeVisible(page, 'You have successfully removed this group from the team.');
await expect(page.getByText('CUSTOM_GROUP_B')).not.toBeVisible();
// Navigate to team members and validate members are there.
await page.goto(`/t/${teamA}/settings/members`);
await expect(
page.getByRole('row').filter({ hasText: 'Team Manager' }).getByText(memberEmail1),
).toBeVisible();
await expect(
page.getByRole('row').filter({ hasText: 'Team Manager' }).getByText(memberEmail2),
).toBeVisible();
// Member 1 should see inherit team and teamA
await apiSignout({ page });
await apiSignin({ page, email: memberEmail1, redirectPath: `/o/${organisation.url}` });
await expectTextToBeVisible(page, teamInheritName);
await expectTextToBeVisible(page, teamAName);
await expectTextToNotBeVisible(page, teamBName);
// Member 3 should only see inherit team
await apiSignout({ page });
await apiSignin({ page, email: memberEmail3, redirectPath: `/o/${organisation.url}` });
await expectTextToBeVisible(page, teamInheritName);
await expectTextToNotBeVisible(page, teamAName);
await expectTextToNotBeVisible(page, teamBName);
// Admin 1 should see all teams.
await apiSignout({ page });
await apiSignin({ page, email: adminEmail1, redirectPath: `/o/${organisation.url}` });
await expectTextToBeVisible(page, teamInheritName);
await expectTextToBeVisible(page, teamAName);
await expectTextToBeVisible(page, teamBName);
});
test('[ORGANISATIONS]: member invites', async ({ page }) => {
const { user, organisation, team } = await seedUser({
inheritMembers: false,
});
const { user: user2 } = await seedUser({
isPersonalOrganisation: false,
});
const { user: user3 } = await seedUser({
isPersonalOrganisation: false,
});
await apiSignin({
page,
email: user.email,
redirectPath: `/o/${organisation.url}/settings/members`,
});
await page.getByRole('button', { name: 'Invite member' }).click();
await page.getByRole('textbox', { name: 'Email address *' }).click();
await page.getByRole('textbox', { name: 'Email address *' }).fill(user2.email);
await page.getByRole('button', { name: 'Add more' }).click();
await page.locator('input[name="invitations\\.1\\.email"]').fill(user3.email);
await page.getByRole('button', { name: 'Invite' }).click();
await page.getByRole('tab', { name: 'Pending' }).click();
await expect(page.getByText(user2.email)).toBeVisible();
await expect(page.getByText(user3.email)).toBeVisible();
await page.getByRole('row', { name: user3.email }).getByRole('button').click();
await page.getByRole('menuitem', { name: 'Remove' }).click();
await expect(page.getByText('Invitation has been deleted').first()).toBeVisible();
await expect(page.getByText(user3.email)).not.toBeVisible();
// Sign in as member and accept invite
await apiSignout({ page });
await apiSignin({ page, email: user2.email, redirectPath: `/settings/organisations` });
await page.getByRole('button', { name: 'View invites' }).click();
await page.getByRole('button', { name: 'Accept' }).click();
await expect(page.getByText('Invitation accepted').first()).toBeVisible();
// Sign back in as org owner.
await apiSignout({ page });
await apiSignin({ page, email: user.email, redirectPath: `/t/${team.url}/settings/members` });
// Expect 1 member in team.
await expect(page.getByText(user.email)).toBeVisible();
await expect(page.getByText(user2.email)).not.toBeVisible();
// Add member to team.
await page.getByRole('button', { name: 'Add members' }).click();
await page.getByRole('combobox').click();
await page.getByRole('option', { name: user2.name ?? '' }).click();
await page.getByRole('button', { name: 'Next' }).click();
await page.getByRole('button', { name: 'Add Members' }).click();
// Expect 2 members to be visible.
await expect(page.getByText(user.email)).toBeVisible();
await expect(page.getByText(user2.email)).toBeVisible();
await page.getByRole('row', { name: user2.email }).getByRole('button').click();
await page.getByRole('menuitem', { name: 'Remove' }).click();
await page.getByRole('button', { name: 'Remove' }).click();
await expect(page.getByText('You have successfully removed').first()).toBeVisible();
// Expect 1 member in team.
await expect(page.getByText(user.email)).toBeVisible();
await expect(page.getByText(user2.email)).not.toBeVisible();
// Expect 2 members in organisation.
await page.goto(`/o/${organisation.url}/settings/members`);
await expect(page.getByText(user.email)).toBeVisible();
await expect(page.getByText(user2.email)).toBeVisible();
await page.getByRole('row', { name: user2.email }).getByRole('button').click();
await page.getByRole('menuitem', { name: 'Remove' }).click();
await page.getByRole('button', { name: 'Delete' }).click();
await expect(page.getByText('You have successfully removed this user').first()).toBeVisible();
// Expect 1 member in organisation.
await expect(page.getByText(user.email)).toBeVisible();
await expect(page.getByText(user2.email)).not.toBeVisible();
});
test('[ORGANISATIONS]: leave organisation', async ({ page }) => {
const { organisation } = await seedUser({
isPersonalOrganisation: false,
});
const memberEmail = `member-${nanoid()}@test.documenso.com`;
await seedOrganisationMembers({
members: [
{
email: memberEmail,
name: 'Member 1',
organisationRole: 'MEMBER',
},
],
organisationId: organisation.id,
});
await apiSignin({
page,
email: memberEmail,
redirectPath: `/settings/organisations`,
});
await page.getByRole('button', { name: 'Leave' }).click();
await page.getByRole('button', { name: 'Leave' }).click();
await expect(
page.getByText('You have successfully left this organisation').first(),
).toBeVisible();
await expect(page.getByText('No results found').first()).toBeVisible();
});

View File

@ -0,0 +1,113 @@
import { expect, test } from '@playwright/test';
import { getTeamSettings } from '@documenso/lib/server-only/team/get-team-settings';
import { prisma } from '@documenso/prisma';
import { DocumentVisibility } from '@documenso/prisma/client';
import { seedTeamDocumentWithMeta } from '@documenso/prisma/seed/documents';
import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
test('[ORGANISATIONS]: manage preferences', async ({ page }) => {
const { user, organisation, team } = await seedUser({
isPersonalOrganisation: false,
});
await apiSignin({
page,
email: user.email,
redirectPath: `/o/${organisation.url}/settings/preferences`,
});
// Update document preferences.
await page.getByRole('combobox').filter({ hasText: 'Everyone can access and view' }).click();
await page.getByRole('option', { name: 'Only managers and above can' }).click();
await page.getByRole('combobox').filter({ hasText: 'English' }).click();
await page.getByRole('option', { name: 'German' }).click();
await page.getByTestId('signature-types-combobox').click();
await page.getByRole('option', { name: 'Draw' }).click();
await page.getByRole('option', { name: 'Upload' }).click();
await page.getByRole('combobox').nth(3).click();
await page.getByRole('option', { name: 'No' }).click();
await page.getByRole('combobox').filter({ hasText: 'Yes' }).click();
await page.getByRole('option', { name: 'No' }).click();
await page.getByRole('button', { name: 'Update' }).first().click();
await expect(page.getByText('Your document preferences have been updated').first()).toBeVisible();
// Update branding.
await page.getByTestId('enable-branding').click();
await page.getByRole('option', { name: 'Yes' }).click();
await page.getByRole('textbox', { name: 'Brand Website' }).click();
await page.getByRole('textbox', { name: 'Brand Website' }).fill('https://documenso.com');
await page.getByRole('textbox', { name: 'Brand Details' }).click();
await page.getByRole('textbox', { name: 'Brand Details' }).fill('BrandDetails');
await page.getByRole('button', { name: 'Update' }).nth(1).click();
await expect(page.getByText('Your branding preferences have been updated').first()).toBeVisible();
const teamSettings = await getTeamSettings({
teamId: team.id,
});
// Check that the team settings have inherited these values.
expect(teamSettings.documentVisibility).toEqual(DocumentVisibility.MANAGER_AND_ABOVE);
expect(teamSettings.documentLanguage).toEqual('de');
expect(teamSettings.includeSenderDetails).toEqual(false);
expect(teamSettings.includeSigningCertificate).toEqual(false);
expect(teamSettings.typedSignatureEnabled).toEqual(true);
expect(teamSettings.uploadSignatureEnabled).toEqual(false);
expect(teamSettings.drawSignatureEnabled).toEqual(false);
expect(teamSettings.brandingEnabled).toEqual(true);
expect(teamSettings.brandingUrl).toEqual('https://documenso.com');
expect(teamSettings.brandingCompanyDetails).toEqual('BrandDetails');
// Edit the team settings
await page.goto(`/t/${team.url}/settings/preferences`);
await page
.getByRole('group')
.locator('div')
.filter({
hasText: 'Default Document Visibility',
})
.getByRole('combobox')
.click();
await page.getByRole('option', { name: 'Everyone can access and view' }).click();
await page
.getByRole('group')
.locator('div')
.filter({ hasText: 'Default Document Language' })
.getByRole('combobox')
.click();
await page.getByRole('option', { name: 'Polish' }).click();
await page.getByRole('button', { name: 'Update' }).first().click();
await expect(page.getByText('Your document preferences have been updated').first()).toBeVisible();
const updatedTeamSettings = await getTeamSettings({
teamId: team.id,
});
// Check that the team settings have inherited/overriden the correct values.
expect(updatedTeamSettings.documentVisibility).toEqual(DocumentVisibility.EVERYONE);
expect(updatedTeamSettings.documentLanguage).toEqual('pl');
expect(updatedTeamSettings.includeSenderDetails).toEqual(false);
expect(updatedTeamSettings.includeSigningCertificate).toEqual(false);
expect(updatedTeamSettings.typedSignatureEnabled).toEqual(true);
expect(updatedTeamSettings.uploadSignatureEnabled).toEqual(false);
expect(updatedTeamSettings.drawSignatureEnabled).toEqual(false);
const document = await seedTeamDocumentWithMeta(team);
const documentMeta = await prisma.documentMeta.findFirstOrThrow({
where: {
documentId: document.id,
},
});
// Confirm the settings have been applied to a newly created document.
expect(document.visibility).toEqual(DocumentVisibility.EVERYONE);
expect(documentMeta.typedSignatureEnabled).toEqual(true);
expect(documentMeta.uploadSignatureEnabled).toEqual(false);
expect(documentMeta.drawSignatureEnabled).toEqual(false);
expect(documentMeta.language).toEqual('pl');
});

View File

@ -1,82 +1,13 @@
import { expect, test } from '@playwright/test';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { seedTeam } from '@documenso/prisma/seed/teams';
import { seedDirectTemplate } from '@documenso/prisma/seed/templates';
import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
test('[PUBLIC_PROFILE]: create profile', async ({ page }) => {
const user = await seedUser();
// Create direct template.
const directTemplate = await seedDirectTemplate({
userId: user.id,
});
await apiSignin({
page,
email: user.email,
redirectPath: '/settings/public-profile',
});
const publicProfileUrl = Date.now().toString();
const publicProfileBio = `public-profile-bio`;
await page.getByRole('textbox', { name: 'Public profile URL' }).click();
await page.getByRole('textbox', { name: 'Public profile URL' }).fill(publicProfileUrl);
await page.getByRole('textbox', { name: 'Bio' }).click();
await page.getByRole('textbox', { name: 'Bio' }).fill(publicProfileBio);
await page.getByRole('button', { name: 'Update' }).click();
await expect(page.getByRole('status').first()).toContainText(
'Your public profile has been updated.',
);
// Link direct template to public profile.
await page.getByRole('button', { name: 'Link template' }).click();
await page.getByRole('cell', { name: directTemplate.title }).click();
await page.getByRole('button', { name: 'Continue' }).click();
await page.getByRole('textbox', { name: 'Title *' }).fill('public-direct-template-title');
await page
.getByRole('textbox', { name: 'Description *' })
.fill('public-direct-template-description');
await page.getByRole('button', { name: 'Update' }).click();
// Check that public profile is disabled.
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/p/${publicProfileUrl}`);
await expect(page.locator('body')).toContainText('404 Profile not found');
// Go back to public profile page.
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/settings/public-profile`);
await page.getByRole('switch').click();
await page.waitForTimeout(1000);
// Assert values.
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/p/${publicProfileUrl}`);
await expect(page.getByRole('main')).toContainText(publicProfileBio);
await expect(page.locator('body')).toContainText('public-direct-template-title');
await expect(page.locator('body')).toContainText('public-direct-template-description');
await page.getByRole('link', { name: 'Sign' }).click();
await page.getByRole('button', { name: 'Continue' }).click();
await page.getByRole('button', { name: 'Complete' }).click();
await page.getByRole('button', { name: 'Sign' }).click();
await expect(page.getByRole('heading', { name: 'Document Signed' })).toBeVisible();
await expect(page.getByRole('heading')).toContainText('Document Signed');
});
test('[PUBLIC_PROFILE]: create team profile', async ({ page }) => {
const team = await seedTeam({
createTeamMembers: 1,
});
const user = team.owner;
const { user, team } = await seedUser();
// Create direct template.
const directTemplate = await seedDirectTemplate({
@ -84,12 +15,6 @@ test('[PUBLIC_PROFILE]: create team profile', async ({ page }) => {
teamId: team.id,
});
// Create non team template to make sure you can only see the team one.
// Will be indirectly asserted because test should fail when 2 elements appear.
await seedDirectTemplate({
userId: user.id,
});
await apiSignin({
page,
email: user.email,

View File

@ -1,13 +1,13 @@
import { test } from '@playwright/test';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { seedTeam } from '@documenso/prisma/seed/teams';
import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
import { expectTextToBeVisible } from '../fixtures/generic';
test('[TEAMS]: create team', async ({ page }) => {
const user = await seedUser();
const { user, organisation } = await seedUser();
test.skip(
process.env.NEXT_PUBLIC_FEATURE_BILLING_ENABLED === 'true',
@ -17,7 +17,7 @@ test('[TEAMS]: create team', async ({ page }) => {
await apiSignin({
page,
email: user.email,
redirectPath: '/settings/teams',
redirectPath: `/o/${organisation.url}/settings/teams`,
});
const teamId = `team-${Date.now()}`;
@ -34,38 +34,32 @@ test('[TEAMS]: create team', async ({ page }) => {
});
test('[TEAMS]: delete team', async ({ page }) => {
const team = await seedTeam();
const { user, team, organisation } = await seedUser();
await apiSignin({
page,
email: team.owner.email,
email: user.email,
redirectPath: `/t/${team.url}/settings`,
});
// Delete team.
await page.getByRole('button', { name: 'Delete team' }).click();
await page.getByLabel(`Confirm by typing delete ${team.url}`).fill(`delete ${team.url}`);
await page.getByRole('button', { name: 'Delete' }).click();
await page.getByLabel(`Confirm by typing delete ${team.name}`).fill(`delete ${team.name}`);
await page.getByRole('button', { name: 'Delete' }).click();
// Check that we have been redirected to the teams page.
await page.waitForURL(`${NEXT_PUBLIC_WEBAPP_URL()}/settings/teams`);
// Your team has been successfully deleted
await expectTextToBeVisible(page, 'Your team has been successfully deleted');
});
test('[TEAMS]: update team', async ({ page }) => {
const team = await seedTeam();
const { user, team } = await seedUser();
await apiSignin({
page,
email: team.owner.email,
email: user.email,
redirectPath: `/t/${team.url}/settings`,
});
// Navigate to create team page.
await page.getByTestId('menu-switcher').click();
await page.getByRole('menuitem', { name: 'Manage teams' }).click();
// Goto team settings page.
await page.getByRole('row').filter({ hasText: team.url }).getByRole('link').nth(1).click();
const updatedTeamId = `team-${Date.now()}`;
// Update team.

View File

@ -1,7 +1,10 @@
import { expect, test } from '@playwright/test';
import { DocumentStatus, TeamMemberRole } from '@prisma/client';
import { DocumentStatus, OrganisationMemberRole, TeamMemberRole } from '@prisma/client';
import { generateDatabaseId } from '@documenso/lib/universal/id';
import { prisma } from '@documenso/prisma';
import { seedDocuments, seedTeamDocuments } from '@documenso/prisma/seed/documents';
import { seedOrganisationMembers } from '@documenso/prisma/seed/organisations';
import { seedTeam, seedTeamMember } from '@documenso/prisma/seed/teams';
import { seedUser } from '@documenso/prisma/seed/users';
@ -9,38 +12,75 @@ import { apiSignin, apiSignout } from '../fixtures/authentication';
import { checkDocumentTabCount } from '../fixtures/documents';
test('[TEAMS]: search respects team document visibility', async ({ page }) => {
const team = await seedTeam();
const adminUser = await seedTeamMember({ teamId: team.id, role: TeamMemberRole.ADMIN });
const managerUser = await seedTeamMember({ teamId: team.id, role: TeamMemberRole.MANAGER });
const memberUser = await seedTeamMember({ teamId: team.id, role: TeamMemberRole.MEMBER });
const { user: owner, organisation, team } = await seedUser();
const [adminUser, managerUser, memberUser] = await seedOrganisationMembers({
organisationId: organisation.id,
members: [
{
organisationRole: OrganisationMemberRole.ADMIN,
},
{
organisationRole: OrganisationMemberRole.MEMBER, // Org managers = team admins so need to workaround this.
},
{
organisationRole: OrganisationMemberRole.MEMBER,
},
],
});
const managerTeamGroup = await prisma.teamGroup.findFirstOrThrow({
where: {
teamId: team.id,
teamRole: TeamMemberRole.MANAGER,
},
include: {
organisationGroup: true,
},
});
const managerOrganisationMember = await prisma.organisationMember.findFirstOrThrow({
where: {
organisationId: organisation.id,
userId: managerUser.id,
},
});
await prisma.organisationGroupMember.create({
data: {
id: generateDatabaseId('group_member'),
groupId: managerTeamGroup.organisationGroupId,
organisationMemberId: managerOrganisationMember.id,
},
});
await seedDocuments([
{
sender: team.owner,
sender: owner,
teamId: team.id,
recipients: [],
type: DocumentStatus.COMPLETED,
documentOptions: {
teamId: team.id,
visibility: 'EVERYONE',
title: 'Searchable Document for Everyone',
},
},
{
sender: team.owner,
sender: owner,
teamId: team.id,
recipients: [],
type: DocumentStatus.COMPLETED,
documentOptions: {
teamId: team.id,
visibility: 'MANAGER_AND_ABOVE',
title: 'Searchable Document for Managers',
},
},
{
sender: team.owner,
sender: owner,
teamId: team.id,
recipients: [],
type: DocumentStatus.COMPLETED,
documentOptions: {
teamId: team.id,
visibility: 'ADMIN',
title: 'Searchable Document for Admins',
},
@ -70,26 +110,30 @@ test('[TEAMS]: search respects team document visibility', async ({ page }) => {
});
test('[TEAMS]: search does not reveal documents from other teams', async ({ page }) => {
const { team: teamA, teamMember2: teamAMember } = await seedTeamDocuments();
const { team: teamB } = await seedTeamDocuments();
const {
team: teamA,
teamOwner: teamAOwner,
teamMember2: teamAMember,
} = await seedTeamDocuments();
const { team: teamB, teamOwner: teamBOwner } = await seedTeamDocuments();
await seedDocuments([
{
sender: teamA.owner,
sender: teamAOwner,
recipients: [],
type: DocumentStatus.COMPLETED,
teamId: teamA.id,
documentOptions: {
teamId: teamA.id,
visibility: 'EVERYONE',
title: 'Unique Team A Document',
},
},
{
sender: teamB.owner,
sender: teamBOwner,
recipients: [],
type: DocumentStatus.COMPLETED,
teamId: teamB.id,
documentOptions: {
teamId: teamB.id,
visibility: 'EVERYONE',
title: 'Unique Team B Document',
},
@ -112,60 +156,19 @@ test('[TEAMS]: search does not reveal documents from other teams', async ({ page
await apiSignout({ page });
});
test('[PERSONAL]: search does not reveal team documents in personal account', async ({ page }) => {
const { team, teamMember2 } = await seedTeamDocuments();
await seedDocuments([
{
sender: teamMember2,
recipients: [],
type: DocumentStatus.COMPLETED,
documentOptions: {
teamId: null,
title: 'Personal Unique Document',
},
},
{
sender: team.owner,
recipients: [],
type: DocumentStatus.COMPLETED,
documentOptions: {
teamId: team.id,
visibility: 'EVERYONE',
title: 'Team Unique Document',
},
},
]);
await apiSignin({
page,
email: teamMember2.email,
redirectPath: '/documents',
});
await page.getByPlaceholder('Search documents...').fill('Unique');
await page.waitForURL(/query=Unique/);
await checkDocumentTabCount(page, 'All', 1);
await expect(page.getByRole('link', { name: 'Personal Unique Document' })).toBeVisible();
await expect(page.getByRole('link', { name: 'Team Unique Document' })).not.toBeVisible();
await apiSignout({ page });
});
test('[TEAMS]: search respects recipient visibility regardless of team visibility', async ({
page,
}) => {
const team = await seedTeam();
const { team, owner } = await seedTeam();
const memberUser = await seedTeamMember({ teamId: team.id, role: TeamMemberRole.MEMBER });
await seedDocuments([
{
sender: team.owner,
sender: owner,
teamId: team.id,
recipients: [memberUser],
type: DocumentStatus.COMPLETED,
documentOptions: {
teamId: team.id,
visibility: 'ADMIN',
title: 'Admin Document with Member Recipient',
},
@ -190,7 +193,7 @@ test('[TEAMS]: search respects recipient visibility regardless of team visibilit
});
test('[TEAMS]: search by recipient name respects visibility', async ({ page }) => {
const team = await seedTeam();
const { team, owner } = await seedTeam();
const adminUser = await seedTeamMember({ teamId: team.id, role: TeamMemberRole.ADMIN });
const memberUser = await seedTeamMember({
teamId: team.id,
@ -198,15 +201,15 @@ test('[TEAMS]: search by recipient name respects visibility', async ({ page }) =
name: 'Team Member',
});
const uniqueRecipient = await seedUser();
const { user: uniqueRecipient } = await seedUser();
await seedDocuments([
{
sender: team.owner,
sender: owner,
recipients: [uniqueRecipient],
type: DocumentStatus.COMPLETED,
teamId: team.id,
documentOptions: {
teamId: team.id,
visibility: 'ADMIN',
title: 'Admin Document for Unique Recipient',
},

View File

@ -8,12 +8,13 @@ import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin, apiSignout } from '../fixtures/authentication';
import { checkDocumentTabCount } from '../fixtures/documents';
import { expectTextToBeVisible } from '../fixtures/generic';
test('[TEAMS]: check team documents count', async ({ page }) => {
const { team, teamMember2 } = await seedTeamDocuments();
const { team, teamOwner, teamMember2 } = await seedTeamDocuments();
// Run the test twice, once with the team owner and once with a team member to ensure the counts are the same.
for (const user of [team.owner, teamMember2]) {
for (const user of [teamOwner, teamMember2]) {
await apiSignin({
page,
email: user.email,
@ -44,8 +45,12 @@ test('[TEAMS]: check team documents count', async ({ page }) => {
});
test('[TEAMS]: check team documents count with internal team email', async ({ page }) => {
const { team, teamMember2, teamMember4 } = await seedTeamDocuments();
const { team: team2, teamMember2: team2Member2 } = await seedTeamDocuments();
const { team, teamOwner, teamMember2, teamMember4 } = await seedTeamDocuments();
const {
team: team2,
teamOwner: team2Owner,
teamMember2: team2Member2,
} = await seedTeamDocuments();
const teamEmailMember = teamMember4;
@ -54,7 +59,7 @@ test('[TEAMS]: check team documents count with internal team email', async ({ pa
teamId: team.id,
});
const testUser1 = await seedUser();
const { user: testUser1, team: testUser1Team } = await seedUser();
await seedDocuments([
// Documents sent from the team email account.
@ -62,52 +67,53 @@ test('[TEAMS]: check team documents count with internal team email', async ({ pa
sender: teamEmailMember,
recipients: [testUser1],
type: DocumentStatus.COMPLETED,
documentOptions: {
teamId: team.id,
},
teamId: team.id,
documentOptions: {},
},
{
sender: teamEmailMember,
recipients: [testUser1],
type: DocumentStatus.PENDING,
documentOptions: {
teamId: team.id,
},
teamId: team.id,
documentOptions: {},
},
{
sender: teamMember4,
recipients: [testUser1],
type: DocumentStatus.DRAFT,
teamId: team.id,
},
// Documents sent to the team email account.
{
sender: testUser1,
recipients: [teamEmailMember],
type: DocumentStatus.COMPLETED,
teamId: testUser1Team.id,
},
{
sender: testUser1,
recipients: [teamEmailMember],
type: DocumentStatus.PENDING,
teamId: testUser1Team.id,
},
{
sender: testUser1,
recipients: [teamEmailMember],
type: DocumentStatus.DRAFT,
teamId: testUser1Team.id,
},
// Document sent to the team email account from another team.
{
sender: team2Member2,
recipients: [teamEmailMember],
type: DocumentStatus.PENDING,
documentOptions: {
teamId: team2.id,
},
teamId: team2.id,
documentOptions: {},
},
]);
// Run the test twice, one with the team owner and once with the team member email to ensure the counts are the same.
for (const user of [team.owner, teamEmailMember]) {
for (const user of [teamOwner, teamEmailMember]) {
await apiSignin({
page,
email: user.email,
@ -138,7 +144,8 @@ test('[TEAMS]: check team documents count with internal team email', async ({ pa
});
test('[TEAMS]: check team documents count with external team email', async ({ page }) => {
const { team, teamMember2 } = await seedTeamDocuments();
const { team, teamOwner, teamMember2 } = await seedTeamDocuments();
const { team: team2, teamMember2: team2Member2 } = await seedTeamDocuments();
const teamEmail = `external-team-email-${team.id}@test.documenso.com`;
@ -148,7 +155,9 @@ test('[TEAMS]: check team documents count with external team email', async ({ pa
teamId: team.id,
});
const testUser1 = await seedUser();
const { user: testUser1, team: testUser1Team } = await seedUser({
isPersonalOrganisation: true,
});
await seedDocuments([
// Documents sent to the team email account.
@ -156,42 +165,39 @@ test('[TEAMS]: check team documents count with external team email', async ({ pa
sender: testUser1,
recipients: [teamEmail],
type: DocumentStatus.COMPLETED,
teamId: testUser1Team.id,
},
{
sender: testUser1,
recipients: [teamEmail],
type: DocumentStatus.PENDING,
teamId: testUser1Team.id,
},
{
sender: testUser1,
recipients: [teamEmail],
type: DocumentStatus.DRAFT,
teamId: testUser1Team.id,
},
// Document sent to the team email account from another team.
{
sender: team2Member2,
recipients: [teamEmail],
type: DocumentStatus.PENDING,
documentOptions: {
teamId: team2.id,
},
teamId: team2.id,
},
// Document sent to the team email account from an individual user.
{
sender: testUser1,
recipients: [teamEmail],
type: DocumentStatus.PENDING,
documentOptions: {
teamId: team2.id,
},
teamId: testUser1Team.id,
},
{
sender: testUser1,
recipients: [teamEmail],
type: DocumentStatus.DRAFT,
documentOptions: {
teamId: team2.id,
},
teamId: testUser1Team.id,
},
]);
@ -222,7 +228,7 @@ test('[TEAMS]: check team documents count with external team email', async ({ pa
});
test('[TEAMS]: resend pending team document', async ({ page }) => {
const { team, teamMember2: currentUser } = await seedTeamDocuments();
const { team, teamOwner, teamMember2: currentUser } = await seedTeamDocuments();
await apiSignin({
page,
@ -248,7 +254,7 @@ test('[TEAMS]: resend pending team document', async ({ page }) => {
});
test('[TEAMS]: delete draft team document', async ({ page }) => {
const { team, teamMember2: teamEmailMember, teamMember3 } = await seedTeamDocuments();
const { team, teamOwner, teamMember2: teamEmailMember, teamMember3 } = await seedTeamDocuments();
await apiSignin({
page,
@ -273,7 +279,7 @@ test('[TEAMS]: delete draft team document', async ({ page }) => {
await apiSignout({ page });
// Run the test twice, one with the team owner and once with the team member email to ensure the counts are the same.
for (const user of [team.owner, teamEmailMember]) {
for (const user of [teamOwner, teamEmailMember]) {
await apiSignin({
page,
email: user.email,
@ -292,7 +298,7 @@ test('[TEAMS]: delete draft team document', async ({ page }) => {
});
test('[TEAMS]: delete pending team document', async ({ page }) => {
const { team, teamMember2: teamEmailMember, teamMember3 } = await seedTeamDocuments();
const { team, teamOwner, teamMember2: teamEmailMember, teamMember3 } = await seedTeamDocuments();
await apiSignin({
page,
@ -318,7 +324,7 @@ test('[TEAMS]: delete pending team document', async ({ page }) => {
await apiSignout({ page });
// Run the test twice, one with the team owner and once with the team member email to ensure the counts are the same.
for (const user of [team.owner, teamEmailMember]) {
for (const user of [teamOwner, teamEmailMember]) {
await apiSignin({
page,
email: user.email,
@ -337,7 +343,7 @@ test('[TEAMS]: delete pending team document', async ({ page }) => {
});
test('[TEAMS]: delete completed team document', async ({ page }) => {
const { team, teamMember2: teamEmailMember, teamMember3 } = await seedTeamDocuments();
const { team, teamOwner, teamMember2: teamEmailMember, teamMember3 } = await seedTeamDocuments();
await apiSignin({
page,
@ -363,7 +369,7 @@ test('[TEAMS]: delete completed team document', async ({ page }) => {
await apiSignout({ page });
// Run the test twice, one with the team owner and once with the team member email to ensure the counts are the same.
for (const user of [team.owner, teamEmailMember]) {
for (const user of [teamOwner, teamEmailMember]) {
await apiSignin({
page,
email: user.email,
@ -382,7 +388,7 @@ test('[TEAMS]: delete completed team document', async ({ page }) => {
});
test('[TEAMS]: check document visibility based on team member role', async ({ page }) => {
const team = await seedTeam();
const { team, owner } = await seedTeam();
// Seed users with different roles
const adminUser = await seedTeamMember({
@ -400,46 +406,48 @@ test('[TEAMS]: check document visibility based on team member role', async ({ pa
role: TeamMemberRole.MEMBER,
});
const outsideUser = await seedUser();
const { user: outsideUser, team: outsideUserTeam } = await seedUser({
isPersonalOrganisation: true,
});
// Seed documents with different visibility levels
await seedDocuments([
{
sender: team.owner,
sender: owner,
recipients: [],
type: DocumentStatus.COMPLETED,
teamId: team.id,
documentOptions: {
teamId: team.id,
visibility: 'EVERYONE',
title: 'Document Visible to Everyone',
},
},
{
sender: team.owner,
sender: owner,
recipients: [],
type: DocumentStatus.COMPLETED,
teamId: team.id,
documentOptions: {
teamId: team.id,
visibility: 'MANAGER_AND_ABOVE',
title: 'Document Visible to Manager and Above',
},
},
{
sender: team.owner,
sender: owner,
recipients: [],
type: DocumentStatus.COMPLETED,
teamId: team.id,
documentOptions: {
teamId: team.id,
visibility: 'ADMIN',
title: 'Document Visible to Admin',
},
},
{
sender: team.owner,
sender: owner,
recipients: [outsideUser],
type: DocumentStatus.COMPLETED,
teamId: team.id,
documentOptions: {
teamId: team.id,
visibility: 'ADMIN',
title: 'Document Visible to Admin with Recipient',
},
@ -470,11 +478,6 @@ test('[TEAMS]: check document visibility based on team member role', async ({ pa
path: teamUrlRedirect,
expectedDocuments: ['Document Visible to Everyone'],
},
{
user: outsideUser,
path: '/documents',
expectedDocuments: ['Document Visible to Admin with Recipient'],
},
];
for (const testCase of testCases) {
@ -491,12 +494,20 @@ test('[TEAMS]: check document visibility based on team member role', async ({ pa
await apiSignout({ page });
}
await apiSignin({
page,
email: outsideUser.email,
redirectPath: '/inbox',
});
await expectTextToBeVisible(page, 'Document Visible to Admin with Recipient');
});
test('[TEAMS]: ensure document owner can see document regardless of visibility', async ({
page,
}) => {
const team = await seedTeam();
const { team, owner } = await seedTeam();
// Seed a member user
const memberUser = await seedTeamMember({
@ -510,8 +521,8 @@ test('[TEAMS]: ensure document owner can see document regardless of visibility',
sender: memberUser,
recipients: [],
type: DocumentStatus.COMPLETED,
teamId: team.id,
documentOptions: {
teamId: team.id,
visibility: 'ADMIN',
title: 'Admin Document with Member Document Owner',
},
@ -533,7 +544,7 @@ test('[TEAMS]: ensure document owner can see document regardless of visibility',
});
test('[TEAMS]: ensure recipient can see document regardless of visibility', async ({ page }) => {
const team = await seedTeam();
const { team, owner } = await seedTeam();
// Seed a member user
const memberUser = await seedTeamMember({
@ -544,11 +555,11 @@ test('[TEAMS]: ensure recipient can see document regardless of visibility', asyn
// Seed a document with ADMIN visibility but make the member user a recipient
await seedDocuments([
{
sender: team.owner,
sender: owner,
recipients: [memberUser],
type: DocumentStatus.COMPLETED,
teamId: team.id,
documentOptions: {
teamId: team.id,
visibility: 'ADMIN',
title: 'Admin Document with Member Recipient',
},
@ -570,7 +581,7 @@ test('[TEAMS]: ensure recipient can see document regardless of visibility', asyn
});
test('[TEAMS]: check that MEMBER role cannot see ADMIN-only documents', async ({ page }) => {
const team = await seedTeam();
const { team, owner } = await seedTeam();
// Seed a member user
const memberUser = await seedTeamMember({
@ -581,11 +592,11 @@ test('[TEAMS]: check that MEMBER role cannot see ADMIN-only documents', async ({
// Seed an ADMIN-only document
await seedDocuments([
{
sender: team.owner,
sender: owner,
recipients: [],
type: DocumentStatus.COMPLETED,
teamId: team.id,
documentOptions: {
teamId: team.id,
visibility: 'ADMIN',
title: 'Admin Only Document',
},
@ -609,7 +620,7 @@ test('[TEAMS]: check that MEMBER role cannot see ADMIN-only documents', async ({
test('[TEAMS]: check that MEMBER role cannot see MANAGER_AND_ABOVE-only documents', async ({
page,
}) => {
const team = await seedTeam();
const { team, owner } = await seedTeam();
// Seed a member user
const memberUser = await seedTeamMember({
@ -620,11 +631,11 @@ test('[TEAMS]: check that MEMBER role cannot see MANAGER_AND_ABOVE-only document
// Seed an ADMIN-only document
await seedDocuments([
{
sender: team.owner,
sender: owner,
recipients: [],
type: DocumentStatus.COMPLETED,
teamId: team.id,
documentOptions: {
teamId: team.id,
visibility: 'MANAGER_AND_ABOVE',
title: 'Manager and Above Only Document',
},
@ -646,7 +657,7 @@ test('[TEAMS]: check that MEMBER role cannot see MANAGER_AND_ABOVE-only document
});
test('[TEAMS]: check that MANAGER role cannot see ADMIN-only documents', async ({ page }) => {
const team = await seedTeam();
const { team, owner } = await seedTeam();
// Seed a manager user
const managerUser = await seedTeamMember({
@ -657,11 +668,11 @@ test('[TEAMS]: check that MANAGER role cannot see ADMIN-only documents', async (
// Seed an ADMIN-only document
await seedDocuments([
{
sender: team.owner,
sender: owner,
recipients: [],
type: DocumentStatus.COMPLETED,
teamId: team.id,
documentOptions: {
teamId: team.id,
visibility: 'ADMIN',
title: 'Admin Only Document',
},
@ -683,7 +694,7 @@ test('[TEAMS]: check that MANAGER role cannot see ADMIN-only documents', async (
});
test('[TEAMS]: check that ADMIN role can see MANAGER_AND_ABOVE documents', async ({ page }) => {
const team = await seedTeam();
const { team, owner } = await seedTeam();
// Seed an admin user
const adminUser = await seedTeamMember({
@ -694,11 +705,11 @@ test('[TEAMS]: check that ADMIN role can see MANAGER_AND_ABOVE documents', async
// Seed a MANAGER_AND_ABOVE document
await seedDocuments([
{
sender: team.owner,
sender: owner,
recipients: [],
type: DocumentStatus.COMPLETED,
teamId: team.id,
documentOptions: {
teamId: team.id,
visibility: 'MANAGER_AND_ABOVE',
title: 'Manager and Above Document',
},
@ -720,25 +731,16 @@ test('[TEAMS]: check that ADMIN role can see MANAGER_AND_ABOVE documents', async
});
test('[TEAMS]: check that ADMIN role can change document visibility', async ({ page }) => {
const team = await seedTeam({
createTeamOptions: {
teamGlobalSettings: {
create: {
documentVisibility: DocumentVisibility.MANAGER_AND_ABOVE,
},
},
},
});
const { team, owner } = await seedTeam();
const adminUser = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.ADMIN,
});
const document = await seedBlankDocument(adminUser, {
const document = await seedBlankDocument(adminUser, team.id, {
createDocumentOptions: {
teamId: team.id,
visibility: team.teamGlobalSettings?.documentVisibility,
visibility: DocumentVisibility.MANAGER_AND_ABOVE,
},
});
@ -763,25 +765,16 @@ test('[TEAMS]: check that ADMIN role can change document visibility', async ({ p
test('[TEAMS]: check that MEMBER role cannot change visibility of EVERYONE documents', async ({
page,
}) => {
const team = await seedTeam({
createTeamOptions: {
teamGlobalSettings: {
create: {
documentVisibility: DocumentVisibility.EVERYONE,
},
},
},
});
const { team, owner } = await seedTeam();
const teamMember = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MEMBER,
});
const document = await seedBlankDocument(teamMember, {
const document = await seedBlankDocument(teamMember, team.id, {
createDocumentOptions: {
teamId: team.id,
visibility: team.teamGlobalSettings?.documentVisibility,
visibility: DocumentVisibility.EVERYONE,
},
});
@ -798,25 +791,16 @@ test('[TEAMS]: check that MEMBER role cannot change visibility of EVERYONE docum
test('[TEAMS]: check that MEMBER role cannot change visibility of MANAGER_AND_ABOVE documents', async ({
page,
}) => {
const team = await seedTeam({
createTeamOptions: {
teamGlobalSettings: {
create: {
documentVisibility: DocumentVisibility.MANAGER_AND_ABOVE,
},
},
},
});
const { team, owner } = await seedTeam();
const teamMember = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MEMBER,
});
const document = await seedBlankDocument(teamMember, {
const document = await seedBlankDocument(teamMember, team.id, {
createDocumentOptions: {
teamId: team.id,
visibility: team.teamGlobalSettings?.documentVisibility,
visibility: DocumentVisibility.MANAGER_AND_ABOVE,
},
});
@ -833,25 +817,16 @@ test('[TEAMS]: check that MEMBER role cannot change visibility of MANAGER_AND_AB
test('[TEAMS]: check that MEMBER role cannot change visibility of ADMIN documents', async ({
page,
}) => {
const team = await seedTeam({
createTeamOptions: {
teamGlobalSettings: {
create: {
documentVisibility: DocumentVisibility.ADMIN,
},
},
},
});
const { team, owner } = await seedTeam();
const teamMember = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MEMBER,
});
const document = await seedBlankDocument(teamMember, {
const document = await seedBlankDocument(teamMember, team.id, {
createDocumentOptions: {
teamId: team.id,
visibility: team.teamGlobalSettings?.documentVisibility,
visibility: DocumentVisibility.ADMIN,
},
});
@ -868,25 +843,16 @@ test('[TEAMS]: check that MEMBER role cannot change visibility of ADMIN document
test('[TEAMS]: check that MANAGER role cannot change visibility of ADMIN documents', async ({
page,
}) => {
const team = await seedTeam({
createTeamOptions: {
teamGlobalSettings: {
create: {
documentVisibility: DocumentVisibility.ADMIN,
},
},
},
});
const { team, owner } = await seedTeam();
const teamManager = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MANAGER,
});
const document = await seedBlankDocument(teamManager, {
const document = await seedBlankDocument(teamManager, team.id, {
createDocumentOptions: {
teamId: team.id,
visibility: team.teamGlobalSettings?.documentVisibility,
visibility: DocumentVisibility.ADMIN,
},
});
@ -902,17 +868,25 @@ test('[TEAMS]: check that MANAGER role cannot change visibility of ADMIN documen
test('[TEAMS]: users cannot see documents from other teams', async ({ page }) => {
// Seed two teams with documents
const { team: teamA, teamMember2: teamAMember } = await seedTeamDocuments();
const { team: teamB, teamMember2: teamBMember } = await seedTeamDocuments();
const {
team: teamA,
teamOwner: teamAOwner,
teamMember2: teamAMember,
} = await seedTeamDocuments();
const {
team: teamB,
teamOwner: teamBOwner,
teamMember2: teamBMember,
} = await seedTeamDocuments();
// Seed a document in team B
await seedDocuments([
{
sender: teamB.owner,
sender: teamBOwner,
recipients: [],
type: DocumentStatus.COMPLETED,
teamId: teamB.id,
documentOptions: {
teamId: teamB.id,
visibility: 'EVERYONE',
title: 'Team B Document',
},
@ -934,8 +908,8 @@ test('[TEAMS]: users cannot see documents from other teams', async ({ page }) =>
test('[TEAMS]: personal documents are not visible in team context', async ({ page }) => {
// Seed a team and a user with personal documents
const { team, teamMember2 } = await seedTeamDocuments();
const personalUser = await seedUser();
const { team, teamOwner, teamMember2 } = await seedTeamDocuments();
const { user: personalUser, team: personalUserTeam } = await seedUser();
// Seed a personal document for teamMember2
await seedDocuments([
@ -943,8 +917,8 @@ test('[TEAMS]: personal documents are not visible in team context', async ({ pag
sender: teamMember2,
recipients: [],
type: DocumentStatus.COMPLETED,
teamId: personalUserTeam.id,
documentOptions: {
teamId: null, // Indicates a personal document
visibility: 'EVERYONE',
title: 'Personal Document',
},
@ -965,34 +939,3 @@ test('[TEAMS]: personal documents are not visible in team context', async ({ pag
await apiSignout({ page });
});
test('[PERSONAL]: team documents are not visible in personal account', async ({ page }) => {
// Seed a team and a user with personal documents
const { team, teamMember2 } = await seedTeamDocuments();
// Seed a team document
await seedDocuments([
{
sender: teamMember2,
recipients: [],
type: DocumentStatus.COMPLETED,
documentOptions: {
teamId: team.id,
visibility: 'EVERYONE',
title: 'Team Document',
},
},
]);
// Sign in as teamMember2 in the personal context
await apiSignin({
page,
email: teamMember2.email,
redirectPath: `/documents?status=COMPLETED`,
});
// Verify that the team document is not visible in the personal context
await expect(page.getByRole('link', { name: 'Team Document', exact: true })).not.toBeVisible();
await apiSignout({ page });
});

View File

@ -1,17 +1,17 @@
import { expect, test } from '@playwright/test';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { seedTeam, seedTeamEmailVerification } from '@documenso/prisma/seed/teams';
import { seedTeamEmailVerification } from '@documenso/prisma/seed/teams';
import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
test('[TEAMS]: send team email request', async ({ page }) => {
const team = await seedTeam();
const { user, team } = await seedUser();
await apiSignin({
page,
email: team.owner.email,
email: user.email,
password: 'password',
redirectPath: `/t/${team.url}/settings`,
});
@ -32,9 +32,7 @@ test('[TEAMS]: send team email request', async ({ page }) => {
});
test('[TEAMS]: accept team email request', async ({ page }) => {
const team = await seedTeam({
createTeamMembers: 1,
});
const { user, team } = await seedUser();
const teamEmailVerification = await seedTeamEmailVerification({
email: `team-email-verification--${team.url}@test.documenso.com`,
@ -46,14 +44,13 @@ test('[TEAMS]: accept team email request', async ({ page }) => {
});
test('[TEAMS]: delete team email', async ({ page }) => {
const team = await seedTeam({
createTeamMembers: 1,
createTeamEmail: true,
const { user, team } = await seedUser({
setTeamEmailAsOwner: true,
});
await apiSignin({
page,
email: team.owner.email,
email: user.email,
redirectPath: `/t/${team.url}/settings`,
});
@ -66,23 +63,16 @@ test('[TEAMS]: delete team email', async ({ page }) => {
});
test('[TEAMS]: team email owner removes access', async ({ page }) => {
const team = await seedTeam({
createTeamMembers: 1,
createTeamEmail: true,
});
const teamEmailOwner = await seedUser();
if (!team.teamEmail) {
throw new Error('Not possible');
}
const teamEmailOwner = await seedUser({
email: team.teamEmail.email,
const { user: secondUser } = await seedUser({
teamEmail: teamEmailOwner.user.email,
});
await apiSignin({
page,
email: teamEmailOwner.email,
redirectPath: `/settings/teams`,
email: teamEmailOwner.user.email,
redirectPath: `/settings/profile`,
});
await page.getByRole('button', { name: 'Revoke access' }).click();

View File

@ -1,55 +0,0 @@
import { expect, test } from '@playwright/test';
import { seedTeam } from '@documenso/prisma/seed/teams';
import { apiSignin } from '../fixtures/authentication';
test('[TEAMS]: update the default document visibility in the team global settings', async ({
page,
}) => {
const team = await seedTeam({
createTeamMembers: 1,
});
await apiSignin({
page,
email: team.owner.email,
password: 'password',
redirectPath: `/t/${team.url}/settings/preferences`,
});
// !: Brittle selector
await page.getByRole('combobox').first().click();
await page.getByRole('option', { name: 'Admin' }).click();
await page.getByRole('button', { name: 'Update' }).first().click();
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();
});
test('[TEAMS]: update the sender details in the team global settings', async ({ page }) => {
const team = await seedTeam({
createTeamMembers: 1,
});
await apiSignin({
page,
email: team.owner.email,
password: 'password',
redirectPath: `/t/${team.url}/settings/preferences`,
});
const checkbox = page.getByLabel('Send on Behalf of Team');
await checkbox.check();
await expect(checkbox).toBeChecked();
await page.getByRole('button', { name: 'Update' }).first().click();
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();
await expect(checkbox).toBeChecked();
});

View File

@ -1,101 +0,0 @@
import { expect, test } from '@playwright/test';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { seedTeam, seedTeamInvite } from '@documenso/prisma/seed/teams';
import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
test('[TEAMS]: update team member role', async ({ page }) => {
const team = await seedTeam({
createTeamMembers: 1,
});
await apiSignin({
page,
email: team.owner.email,
password: 'password',
redirectPath: `/t/${team.url}/settings/members`,
});
const teamMemberToUpdate = team.members[1];
await page
.getByRole('row')
.filter({ hasText: teamMemberToUpdate.user.email })
.getByRole('button')
.click();
await page.getByRole('menuitem', { name: 'Update role' }).click();
await page.getByRole('combobox').click();
await page.getByLabel('Manager').click();
await page.getByRole('button', { name: 'Update' }).click();
await page.reload();
await expect(
page.getByRole('row').filter({ hasText: teamMemberToUpdate.user.email }).first(),
).toContainText('Manager');
});
test('[TEAMS]: accept team invitation without account', async ({ page }) => {
const team = await seedTeam();
const teamInvite = await seedTeamInvite({
email: `team-invite-test-${Date.now()}@test.documenso.com`,
teamId: team.id,
});
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/team/invite/${teamInvite.token}`);
await expect(page.getByRole('heading')).toContainText('Team invitation');
});
test('[TEAMS]: accept team invitation with account', async ({ page }) => {
const team = await seedTeam();
const user = await seedUser();
const teamInvite = await seedTeamInvite({
email: user.email,
teamId: team.id,
});
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/team/invite/${teamInvite.token}`);
await expect(page.getByRole('heading')).toContainText('Invitation accepted!');
});
test('[TEAMS]: member can leave team', async ({ page }) => {
const team = await seedTeam({
createTeamMembers: 1,
});
const teamMember = team.members[1];
await apiSignin({
page,
email: teamMember.user.email,
password: 'password',
redirectPath: `/settings/teams`,
});
await page.getByRole('button', { name: 'Leave' }).click();
await page.getByRole('button', { name: 'Leave' }).click();
await expect(page.getByRole('status').first()).toContainText(
'You have successfully left this team.',
);
});
test('[TEAMS]: owner cannot leave team', async ({ page }) => {
const team = await seedTeam({
createTeamMembers: 1,
});
await apiSignin({
page,
email: team.owner.email,
password: 'password',
redirectPath: `/settings/teams`,
});
await expect(page.getByRole('button').getByText('Leave')).toBeDisabled();
});

View File

@ -3,27 +3,21 @@ import { expect, test } from '@playwright/test';
import { prisma } from '@documenso/prisma';
import {
seedTeamDocumentWithMeta,
seedTeamDocuments,
seedTeamTemplateWithMeta,
} from '@documenso/prisma/seed/documents';
import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
test('[TEAMS]: check that default team signature settings are all enabled', async ({ page }) => {
const { team } = await seedTeamDocuments();
const { user, team } = await seedUser();
await apiSignin({
page,
email: team.owner.email,
password: 'password',
email: user.email,
redirectPath: `/t/${team.url}/settings/preferences`,
});
// Verify that the default created team settings has all signatures enabled
await expect(page.getByRole('combobox').filter({ hasText: 'Type' })).toBeVisible();
await expect(page.getByRole('combobox').filter({ hasText: 'Upload' })).toBeVisible();
await expect(page.getByRole('combobox').filter({ hasText: 'Draw' })).toBeVisible();
const document = await seedTeamDocumentWithMeta(team);
// Create a document and check the settings
@ -46,12 +40,11 @@ test('[TEAMS]: check that default team signature settings are all enabled', asyn
});
test('[TEAMS]: check signature modes can be disabled', async ({ page }) => {
const { team } = await seedTeamDocuments();
const { user, team } = await seedUser();
await apiSignin({
page,
email: team.owner.email,
password: 'password',
email: user.email,
redirectPath: `/t/${team.url}/settings/preferences`,
});
@ -105,12 +98,11 @@ test('[TEAMS]: check signature modes can be disabled', async ({ page }) => {
});
test('[TEAMS]: check signature modes work for templates', async ({ page }) => {
const { team } = await seedTeamDocuments();
const { user, team } = await seedUser();
await apiSignin({
page,
email: team.owner.email,
password: 'password',
email: user.email,
redirectPath: `/t/${team.url}/settings/preferences`,
});

View File

@ -1,63 +0,0 @@
import { expect, test } from '@playwright/test';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { seedTeam, seedTeamTransfer } from '@documenso/prisma/seed/teams';
import { apiSignin } from '../fixtures/authentication';
test('[TEAMS]: initiate and cancel team transfer', async ({ page }) => {
const team = await seedTeam({
createTeamMembers: 1,
});
const teamMember = team.members[1];
await apiSignin({
page,
email: team.owner.email,
password: 'password',
redirectPath: `/t/${team.url}/settings`,
});
await page.getByRole('button', { name: 'Transfer team' }).click();
await page.getByRole('combobox').click();
await page.getByLabel(teamMember.user.name ?? '').click();
await page.getByLabel('Confirm by typing transfer').click();
await page.getByLabel('Confirm by typing transfer').fill('transfer');
await page.getByRole('button', { name: 'Transfer' }).click();
await expect(page.locator('[id*="form-item-message"]').first()).toContainText(
`You must enter 'transfer ${team.name}' to proceed`,
);
await page.getByLabel('Confirm by typing transfer').click();
await page.getByLabel('Confirm by typing transfer').fill(`transfer ${team.name}`);
await page.getByRole('button', { name: 'Transfer' }).click();
await expect(page.getByRole('heading', { name: 'Team transfer in progress' })).toBeVisible();
await page.getByRole('button', { name: 'Cancel' }).click();
await expect(page.getByRole('status').first()).toContainText(
'The team transfer invitation has been successfully deleted.',
);
});
/**
* Current skipped until we disable billing during tests.
*/
test.skip('[TEAMS]: accept team transfer', async ({ page }) => {
const team = await seedTeam({
createTeamMembers: 1,
});
const newOwnerMember = team.members[1];
const teamTransferRequest = await seedTeamTransfer({
teamId: team.id,
newOwnerUserId: newOwnerMember.userId,
});
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/team/verify/transfer/${teamTransferRequest.token}`);
await expect(page.getByRole('heading')).toContainText('Team ownership transferred!');
});

View File

@ -1,138 +1,20 @@
import { expect, test } from '@playwright/test';
import { TeamMemberRole } from '@prisma/client';
import { prisma } from '@documenso/prisma';
import { seedUserSubscription } from '@documenso/prisma/seed/subscriptions';
import { seedTeam } from '@documenso/prisma/seed/teams';
import { seedTeam, seedTeamMember } from '@documenso/prisma/seed/teams';
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
test.describe('[EE_ONLY]', () => {
const enterprisePriceId = '';
test.beforeEach(() => {
test.skip(
process.env.NEXT_PUBLIC_FEATURE_BILLING_ENABLED !== 'true' || !enterprisePriceId,
'Billing required for this test',
);
});
test('[TEMPLATE_FLOW] add action auth settings', async ({ page }) => {
const user = await seedUser();
await seedUserSubscription({
userId: user.id,
priceId: enterprisePriceId,
});
const template = await seedBlankTemplate(user);
await apiSignin({
page,
email: user.email,
redirectPath: `/templates/${template.id}/edit`,
});
// Set EE action auth.
await page.getByTestId('documentActionSelectValue').click();
await page.getByRole('option').filter({ hasText: 'Require passkey' }).click();
await expect(page.getByTestId('documentActionSelectValue')).toContainText('Require passkey');
// Save the settings by going to the next step.
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Placeholders' })).toBeVisible();
// Return to the settings step to check that the results are saved correctly.
await page.getByRole('button', { name: 'Go Back' }).click();
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
await expect(page.getByTestId('documentActionSelectValue')).toContainText('Require passkey');
});
test('[TEMPLATE_FLOW] enterprise team member can add action auth settings', async ({ page }) => {
const team = await seedTeam({
createTeamMembers: 1,
});
const owner = team.owner;
const teamMemberUser = team.members[1].user;
// Make the team enterprise by giving the owner the enterprise subscription.
await seedUserSubscription({
userId: team.ownerUserId,
priceId: enterprisePriceId,
});
const template = await seedBlankTemplate(owner, {
createTemplateOptions: {
teamId: team.id,
},
});
await apiSignin({
page,
email: teamMemberUser.email,
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
});
// Set EE action auth.
await page.getByTestId('documentActionSelectValue').click();
await page.getByRole('option').filter({ hasText: 'Require passkey' }).click();
await expect(page.getByTestId('documentActionSelectValue')).toContainText('Require passkey');
// Save the settings by going to the next step.
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Placeholders' })).toBeVisible();
// Advanced settings should be visible.
await expect(page.getByLabel('Show advanced settings')).toBeVisible();
});
test('[TEMPLATE_FLOW] enterprise team member should not have access to enterprise on personal account', async ({
page,
}) => {
const team = await seedTeam({
createTeamMembers: 1,
});
const teamMemberUser = team.members[1].user;
// Make the team enterprise by giving the owner the enterprise subscription.
await seedUserSubscription({
userId: team.ownerUserId,
priceId: enterprisePriceId,
});
const template = await seedBlankTemplate(teamMemberUser);
await apiSignin({
page,
email: teamMemberUser.email,
redirectPath: `/templates/${template.id}/edit`,
});
// Global action auth should not be visible.
await expect(page.getByTestId('documentActionSelectValue')).not.toBeVisible();
// Next step.
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Placeholders' })).toBeVisible();
// Advanced settings should not be visible.
await expect(page.getByLabel('Show advanced settings')).not.toBeVisible();
});
});
test('[TEMPLATE_FLOW]: add settings', async ({ page }) => {
const user = await seedUser();
const template = await seedBlankTemplate(user);
const { user, team } = await seedUser();
const template = await seedBlankTemplate(user, team.id);
await apiSignin({
page,
email: user.email,
redirectPath: `/templates/${template.id}/edit`,
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
});
// Set title.
@ -159,19 +41,13 @@ test('[TEMPLATE_FLOW]: add settings', async ({ page }) => {
});
test('[TEMPLATE_FLOW] add document visibility settings', async ({ page }) => {
const { owner, ...team } = await seedTeam({
createTeamMembers: 1,
});
const { user, team } = await seedUser();
const template = await seedBlankTemplate(owner, {
createTemplateOptions: {
teamId: team.id,
},
});
const template = await seedBlankTemplate(user, team.id);
await apiSignin({
page,
email: owner.email,
email: user.email,
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
});
@ -196,24 +72,21 @@ test('[TEMPLATE_FLOW] add document visibility settings', async ({ page }) => {
});
test('[TEMPLATE_FLOW] team member visibility permissions', async ({ page }) => {
const team = await seedTeam({
const { team, owner, organisation } = await seedTeam({
createTeamMembers: 2, // Create an additional member to test different roles
});
await prisma.teamMember.update({
where: {
id: team.members[1].id,
},
data: {
role: TeamMemberRole.MANAGER,
},
const memberUser = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MEMBER,
});
const owner = team.owner;
const managerUser = team.members[1].user;
const memberUser = team.members[2].user;
const managerUser = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MANAGER,
});
const template = await seedBlankTemplate(owner, {
const template = await seedBlankTemplate(owner, team.id, {
createTemplateOptions: {
teamId: team.id,
},
@ -249,7 +122,7 @@ test('[TEMPLATE_FLOW] team member visibility permissions', async ({ page }) => {
await expect(page.getByTestId('documentVisibilitySelectValue')).toBeDisabled();
// Create a new template with 'everyone' visibility
const everyoneTemplate = await seedBlankTemplate(owner, {
const everyoneTemplate = await seedBlankTemplate(owner, team.id, {
createTemplateOptions: {
teamId: team.id,
visibility: 'EVERYONE',

View File

@ -1,86 +1,85 @@
import { expect, test } from '@playwright/test';
import { seedUserSubscription } from '@documenso/prisma/seed/subscriptions';
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
test.describe('[EE_ONLY]', () => {
// eslint-disable-next-line turbo/no-undeclared-env-vars
const enterprisePriceId = process.env.NEXT_PUBLIC_STRIPE_ENTERPRISE_PLAN_MONTHLY_PRICE_ID || '';
// test.describe('[EE_ONLY]', () => {
// // eslint-disable-next-line turbo/no-undeclared-env-vars
// const enterprisePriceId = process.env.NEXT_PUBLIC_STRIPE_ENTERPRISE_PLAN_MONTHLY_PRICE_ID || '';
test.beforeEach(() => {
test.skip(
process.env.NEXT_PUBLIC_FEATURE_BILLING_ENABLED !== 'true' || !enterprisePriceId,
'Billing required for this test',
);
});
// test.beforeEach(() => {
// test.skip(
// process.env.NEXT_PUBLIC_FEATURE_BILLING_ENABLED !== 'true' || !enterprisePriceId,
// 'Billing required for this test',
// );
// });
test('[TEMPLATE_FLOW] add EE settings', async ({ page }) => {
const user = await seedUser();
// test('[TEMPLATE_FLOW] add EE settings', async ({ page }) => {
// const user = await seedUser();
await seedUserSubscription({
userId: user.id,
priceId: enterprisePriceId,
});
// await seedUserSubscription({
// userId: user.id,
// priceId: enterprisePriceId,
// });
const template = await seedBlankTemplate(user);
// const template = await seedBlankTemplate(user);
await apiSignin({
page,
email: user.email,
redirectPath: `/templates/${template.id}/edit`,
});
// await apiSignin({
// page,
// email: user.email,
// redirectPath: `/templates/${template.id}/edit`,
// });
// Save the settings by going to the next step.
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Placeholder' })).toBeVisible();
// // Save the settings by going to the next step.
// await page.getByRole('button', { name: 'Continue' }).click();
// await expect(page.getByRole('heading', { name: 'Add Placeholder' })).toBeVisible();
// Add 2 signers.
await page.getByPlaceholder('Email').fill('recipient1@documenso.com');
await page.getByPlaceholder('Name').fill('Recipient 1');
await page.getByRole('button', { name: 'Add Placeholder Recipient' }).click();
await page.getByPlaceholder('Email').nth(1).fill('recipient2@documenso.com');
await page.getByPlaceholder('Name').nth(1).fill('Recipient 2');
// // Add 2 signers.
// await page.getByPlaceholder('Email').fill('recipient1@documenso.com');
// await page.getByPlaceholder('Name').fill('Recipient 1');
// await page.getByRole('button', { name: 'Add Placeholder Recipient' }).click();
// await page.getByPlaceholder('Email').nth(1).fill('recipient2@documenso.com');
// await page.getByPlaceholder('Name').nth(1).fill('Recipient 2');
// Display advanced settings.
await page.getByLabel('Show advanced settings').check();
// // Display advanced settings.
// await page.getByLabel('Show advanced settings').check();
// Navigate to the next step and back.
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
await page.getByRole('button', { name: 'Go Back' }).click();
await expect(page.getByRole('heading', { name: 'Add Placeholder' })).toBeVisible();
// // Navigate to the next step and back.
// await page.getByRole('button', { name: 'Continue' }).click();
// await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
// await page.getByRole('button', { name: 'Go Back' }).click();
// await expect(page.getByRole('heading', { name: 'Add Placeholder' })).toBeVisible();
// Expect that the advanced settings is unchecked, since no advanced settings were applied.
await expect(page.getByLabel('Show advanced settings')).toBeChecked({ checked: false });
// // Expect that the advanced settings is unchecked, since no advanced settings were applied.
// await expect(page.getByLabel('Show advanced settings')).toBeChecked({ checked: false });
// Add advanced settings for a single recipient.
await page.getByLabel('Show advanced settings').check();
await page.getByTestId('documentActionSelectValue').click();
await page.getByRole('option').filter({ hasText: 'Require passkey' }).click();
// // Add advanced settings for a single recipient.
// await page.getByLabel('Show advanced settings').check();
// await page.getByRole('combobox').first().click();
// await page.getByLabel('Require passkey').click();
// Navigate to the next step and back.
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
await page.getByRole('button', { name: 'Go Back' }).click();
await expect(page.getByRole('heading', { name: 'Add Placeholder' })).toBeVisible();
// // Navigate to the next step and back.
// await page.getByRole('button', { name: 'Continue' }).click();
// await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
// await page.getByRole('button', { name: 'Go Back' }).click();
// await expect(page.getByRole('heading', { name: 'Add Placeholder' })).toBeVisible();
// Expect that the advanced settings is visible, and the checkbox is hidden. Since advanced
// settings were applied.
await expect(page.getByLabel('Show advanced settings')).toBeHidden();
});
});
// // Expect that the advanced settings is visible, and the checkbox is hidden. Since advanced
// // settings were applied.
// await expect(page.getByLabel('Show advanced settings')).toBeHidden();
// });
// });
test('[TEMPLATE_FLOW]: add placeholder', async ({ page }) => {
const user = await seedUser();
const template = await seedBlankTemplate(user);
const { user, team } = await seedUser();
const template = await seedBlankTemplate(user, team.id);
await apiSignin({
page,
email: user.email,
redirectPath: `/templates/${template.id}/edit`,
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
});
// Save the settings by going to the next step.

View File

@ -5,15 +5,12 @@ import path from 'path';
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
import { prisma } from '@documenso/prisma';
import { seedUserSubscription } from '@documenso/prisma/seed/subscriptions';
import { seedTeam } from '@documenso/prisma/seed/teams';
import { seedTeam, seedTeamMember } from '@documenso/prisma/seed/teams';
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
const enterprisePriceId = '';
const EXAMPLE_PDF_PATH = path.join(__dirname, '../../../../assets/example.pdf');
/**
@ -26,21 +23,13 @@ const EXAMPLE_PDF_PATH = path.join(__dirname, '../../../../assets/example.pdf');
* If you update this test please update that test as well.
*/
test('[TEMPLATE]: should create a document from a template', async ({ page }) => {
const user = await seedUser();
const template = await seedBlankTemplate(user);
const isBillingEnabled =
process.env.NEXT_PUBLIC_FEATURE_BILLING_ENABLED === 'true' && enterprisePriceId;
await seedUserSubscription({
userId: user.id,
priceId: enterprisePriceId,
});
const { user, team } = await seedUser();
const template = await seedBlankTemplate(user, team.id);
await apiSignin({
page,
email: user.email,
redirectPath: `/templates/${template.id}/edit`,
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
});
// Set template title.
@ -51,13 +40,6 @@ test('[TEMPLATE]: should create a document from a template', async ({ page }) =>
await page.getByRole('option').filter({ hasText: 'Require account' }).click();
await expect(page.getByTestId('documentAccessSelectValue')).toContainText('Require account');
// Set EE action auth.
if (isBillingEnabled) {
await page.getByTestId('documentActionSelectValue').click();
await page.getByRole('option').filter({ hasText: 'Require passkey' }).click();
await expect(page.getByTestId('documentActionSelectValue')).toContainText('Require passkey');
}
// Set email options.
await page.getByRole('button', { name: 'Email Options' }).click();
await page.getByLabel('Subject (Optional)').fill('SUBJECT');
@ -82,25 +64,18 @@ test('[TEMPLATE]: should create a document from a template', async ({ page }) =>
await page.getByPlaceholder('Email').nth(1).fill('recipient2@documenso.com');
await page.getByPlaceholder('Name').nth(1).fill('Recipient 2');
// Apply require passkey for Recipient 1.
if (isBillingEnabled) {
await page.getByLabel('Show advanced settings').check();
await page.getByTestId('documentActionSelectValue').click();
await page.getByRole('option').filter({ hasText: 'Require passkey' }).click();
}
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
await page.getByRole('button', { name: 'Save template' }).click();
// Use template
await page.waitForURL('/templates');
await page.waitForURL(`/t/${team.url}/templates`);
await page.getByRole('button', { name: 'Use Template' }).click();
await page.getByRole('button', { name: 'Create as draft' }).click();
// Review that the document was created with the correct values.
await page.waitForURL(/documents/);
await page.waitForURL(new RegExp(`/t/${team.url}/documents/\\d+`));
const documentId = Number(page.url().split('/').pop());
@ -121,10 +96,6 @@ test('[TEMPLATE]: should create a document from a template', async ({ page }) =>
expect(document.title).toEqual('TEMPLATE_TITLE');
expect(documentAuth.documentAuthOption.globalAccessAuth).toContain('ACCOUNT');
if (isBillingEnabled) {
expect(documentAuth.documentAuthOption.globalActionAuth).toContain('PASSKEY');
}
expect(document.documentMeta?.dateFormat).toEqual('dd/MM/yyyy hh:mm a');
expect(document.documentMeta?.message).toEqual('MESSAGE');
expect(document.documentMeta?.redirectUrl).toEqual('https://documenso.com');
@ -144,10 +115,6 @@ test('[TEMPLATE]: should create a document from a template', async ({ page }) =>
recipientAuth: recipientTwo.authOptions,
});
if (isBillingEnabled) {
expect(recipientOneAuth.derivedRecipientActionAuth).toContain('PASSKEY');
}
expect(recipientOneAuth.derivedRecipientAccessAuth).toContain('ACCOUNT');
expect(recipientTwoAuth.derivedRecipientAccessAuth).toContain('ACCOUNT');
});
@ -156,23 +123,11 @@ test('[TEMPLATE]: should create a document from a template', async ({ page }) =>
* This is a direct copy paste of the above test but for teams.
*/
test('[TEMPLATE]: should create a team document from a team template', async ({ page }) => {
const { owner, ...team } = await seedTeam({
const { team, owner, organisation } = await seedTeam({
createTeamMembers: 2,
});
const template = await seedBlankTemplate(owner, {
createTemplateOptions: {
teamId: team.id,
},
});
const isBillingEnabled =
process.env.NEXT_PUBLIC_FEATURE_BILLING_ENABLED === 'true' && enterprisePriceId;
await seedUserSubscription({
userId: owner.id,
priceId: enterprisePriceId,
});
const template = await seedBlankTemplate(owner, team.id);
await apiSignin({
page,
@ -188,13 +143,6 @@ test('[TEMPLATE]: should create a team document from a team template', async ({
await page.getByRole('option').filter({ hasText: 'Require account' }).click();
await expect(page.getByTestId('documentAccessSelectValue')).toContainText('Require account');
// Set EE action auth.
if (isBillingEnabled) {
await page.getByTestId('documentActionSelectValue').click();
await page.getByRole('option').filter({ hasText: 'Require passkey' }).click();
await expect(page.getByTestId('documentActionSelectValue')).toContainText('Require passkey');
}
// Set email options.
await page.getByRole('button', { name: 'Email Options' }).click();
await page.getByLabel('Subject (Optional)').fill('SUBJECT');
@ -219,13 +167,6 @@ test('[TEMPLATE]: should create a team document from a team template', async ({
await page.getByPlaceholder('Email').nth(1).fill('recipient2@documenso.com');
await page.getByPlaceholder('Name').nth(1).fill('Recipient 2');
// Apply require passkey for Recipient 1.
if (isBillingEnabled) {
await page.getByLabel('Show advanced settings').check();
await page.getByTestId('documentActionSelectValue').click();
await page.getByRole('option').filter({ hasText: 'Require passkey' }).click();
}
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
@ -237,7 +178,7 @@ test('[TEMPLATE]: should create a team document from a team template', async ({
await page.getByRole('button', { name: 'Create as draft' }).click();
// Review that the document was created with the correct values.
await page.waitForURL(/documents/);
await page.waitForURL(new RegExp(`/t/${team.url}/documents/\\d+`));
const documentId = Number(page.url().split('/').pop());
@ -259,11 +200,6 @@ test('[TEMPLATE]: should create a team document from a team template', async ({
expect(document.title).toEqual('TEMPLATE_TITLE');
expect(documentAuth.documentAuthOption.globalAccessAuth).toContain('ACCOUNT');
if (isBillingEnabled) {
expect(documentAuth.documentAuthOption.globalActionAuth).toContain('PASSKEY');
}
expect(document.documentMeta?.dateFormat).toEqual('dd/MM/yyyy hh:mm a');
expect(document.documentMeta?.message).toEqual('MESSAGE');
expect(document.documentMeta?.redirectUrl).toEqual('https://documenso.com');
@ -283,10 +219,6 @@ test('[TEMPLATE]: should create a team document from a team template', async ({
recipientAuth: recipientTwo.authOptions,
});
if (isBillingEnabled) {
expect(recipientOneAuth.derivedRecipientActionAuth).toContain('PASSKEY');
}
expect(recipientOneAuth.derivedRecipientAccessAuth).toContain('ACCOUNT');
expect(recipientTwoAuth.derivedRecipientAccessAuth).toContain('ACCOUNT');
});
@ -298,8 +230,8 @@ test('[TEMPLATE]: should create a team document from a team template', async ({
test('[TEMPLATE]: should create a document from a template with custom document', async ({
page,
}) => {
const user = await seedUser();
const template = await seedBlankTemplate(user);
const { user, team } = await seedUser();
const template = await seedBlankTemplate(user, team.id);
// Create a temporary PDF file for upload
@ -308,7 +240,7 @@ test('[TEMPLATE]: should create a document from a template with custom document'
await apiSignin({
page,
email: user.email,
redirectPath: `/templates/${template.id}/edit`,
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
});
// Set template title
@ -327,7 +259,7 @@ test('[TEMPLATE]: should create a document from a template with custom document'
await page.getByRole('button', { name: 'Save template' }).click();
// Use template with custom document
await page.waitForURL('/templates');
await page.waitForURL(`/t/${team.url}/templates`);
await page.getByRole('button', { name: 'Use Template' }).click();
// Enable custom document upload and upload file
@ -352,7 +284,7 @@ test('[TEMPLATE]: should create a document from a template with custom document'
await page.getByRole('button', { name: 'Create as draft' }).click();
// Review that the document was created with the custom document data
await page.waitForURL(/documents/);
await page.waitForURL(new RegExp(`/t/${team.url}/documents/\\d+`));
const documentId = Number(page.url().split('/').pop());
@ -378,15 +310,11 @@ test('[TEMPLATE]: should create a document from a template with custom document'
test('[TEMPLATE]: should create a team document from a template with custom document', async ({
page,
}) => {
const { owner, ...team } = await seedTeam({
const { team, owner, organisation } = await seedTeam({
createTeamMembers: 2,
});
const template = await seedBlankTemplate(owner, {
createTemplateOptions: {
teamId: team.id,
},
});
const template = await seedBlankTemplate(owner, team.id);
const pdfContent = fs.readFileSync(EXAMPLE_PDF_PATH).toString('base64');
@ -437,7 +365,7 @@ test('[TEMPLATE]: should create a team document from a template with custom docu
await page.getByRole('button', { name: 'Create as draft' }).click();
// Review that the document was created with the custom document data
await page.waitForURL(/documents/);
await page.waitForURL(new RegExp(`/t/${team.url}/documents/\\d+`));
const documentId = Number(page.url().split('/').pop());
@ -464,13 +392,13 @@ test('[TEMPLATE]: should create a team document from a template with custom docu
test('[TEMPLATE]: should create a document from a template using template document when custom document is not enabled', async ({
page,
}) => {
const user = await seedUser();
const template = await seedBlankTemplate(user);
const { user, team } = await seedUser();
const template = await seedBlankTemplate(user, team.id);
await apiSignin({
page,
email: user.email,
redirectPath: `/templates/${template.id}/edit`,
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
});
// Set template title
@ -489,7 +417,7 @@ test('[TEMPLATE]: should create a document from a template using template docume
await page.getByRole('button', { name: 'Save template' }).click();
// Use template without custom document
await page.waitForURL('/templates');
await page.waitForURL(`/t/${team.url}/templates`);
await page.getByRole('button', { name: 'Use Template' }).click();
// Verify custom document upload is not checked by default
@ -499,7 +427,7 @@ test('[TEMPLATE]: should create a document from a template using template docume
await page.getByRole('button', { name: 'Create as draft' }).click();
// Review that the document was created with the template's document data
await page.waitForURL(/documents/);
await page.waitForURL(new RegExp(`/t/${team.url}/documents/\\d+`));
const documentId = Number(page.url().split('/').pop());
@ -532,15 +460,11 @@ test('[TEMPLATE]: should create a document from a template using template docume
test('[TEMPLATE]: should persist document visibility when creating from template', async ({
page,
}) => {
const { owner, ...team } = await seedTeam({
const { team, owner, organisation } = await seedTeam({
createTeamMembers: 2,
});
const template = await seedBlankTemplate(owner, {
createTemplateOptions: {
teamId: team.id,
},
});
const template = await seedBlankTemplate(owner, team.id);
await apiSignin({
page,
@ -569,17 +493,11 @@ test('[TEMPLATE]: should persist document visibility when creating from template
await page.getByRole('button', { name: 'Save template' }).click();
// Test creating document as team manager
await prisma.teamMember.update({
where: {
id: team.members[1].id,
},
data: {
role: TeamMemberRole.MANAGER,
},
const managerUser = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MANAGER,
});
const managerUser = team.members[1].user;
await apiSignin({
page,
email: managerUser.email,
@ -590,7 +508,7 @@ test('[TEMPLATE]: should persist document visibility when creating from template
await page.getByRole('button', { name: 'Create as draft' }).click();
// Review that the document was created with the correct visibility
await page.waitForURL(/documents/);
await page.waitForURL(new RegExp(`/t/${team.url}/documents/\\d+`));
const documentId = Number(page.url().split('/').pop());
@ -605,7 +523,11 @@ test('[TEMPLATE]: should persist document visibility when creating from template
expect(document.teamId).toEqual(team.id);
// Test that regular member cannot create document from restricted template
const memberUser = team.members[2].user;
const memberUser = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MEMBER,
});
await apiSignin({
page,
email: memberUser.email,

View File

@ -2,10 +2,6 @@ import { expect, test } from '@playwright/test';
import { customAlphabet } from 'nanoid';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import {
DIRECT_TEMPLATE_RECIPIENT_EMAIL,
DIRECT_TEMPLATE_RECIPIENT_NAME,
} from '@documenso/lib/constants/direct-templates';
import { createDocumentAuthOptions } from '@documenso/lib/utils/document-auth';
import { formatDirectTemplatePath } from '@documenso/lib/utils/templates';
import { seedTeam } from '@documenso/prisma/seed/teams';
@ -13,29 +9,18 @@ import { seedDirectTemplate, seedTemplate } from '@documenso/prisma/seed/templat
import { seedTestEmail, seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
import { checkDocumentTabCount } from '../fixtures/documents';
// Duped from `packages/lib/utils/teams.ts` due to errors when importing that file.
const formatDocumentsPath = (teamUrl?: string) =>
teamUrl ? `/t/${teamUrl}/documents` : '/documents';
const formatTemplatesPath = (teamUrl?: string) =>
teamUrl ? `/t/${teamUrl}/templates` : '/templates';
const formatDocumentsPath = (teamUrl: string) => `/t/${teamUrl}/documents`;
const formatTemplatesPath = (teamUrl: string) => `/t/${teamUrl}/templates`;
const nanoid = customAlphabet('1234567890abcdef', 10);
test('[DIRECT_TEMPLATES]: create direct link for template', async ({ page }) => {
const team = await seedTeam({
const { team, owner, organisation } = await seedTeam({
createTeamMembers: 1,
});
const owner = team.owner;
// Should only be visible to the owner in personal templates.
const personalTemplate = await seedTemplate({
title: 'Personal template',
userId: owner.id,
});
// Should be visible to team members.
const teamTemplate = await seedTemplate({
title: 'Team template 1',
@ -46,49 +31,35 @@ test('[DIRECT_TEMPLATES]: create direct link for template', async ({ page }) =>
await apiSignin({
page,
email: owner.email,
redirectPath: '/templates',
redirectPath: `/t/${team.url}/templates`,
});
const urls = [
`${NEXT_PUBLIC_WEBAPP_URL()}/t/${team.url}/templates/${teamTemplate.id}`,
`${NEXT_PUBLIC_WEBAPP_URL()}/templates/${personalTemplate.id}`,
];
const url = `${NEXT_PUBLIC_WEBAPP_URL()}/t/${team.url}/templates/${teamTemplate.id}`;
// Run test for personal and team templates.
for (const url of urls) {
// Owner should see list of templates with no direct link badge.
await page.goto(url);
await expect(page.getByRole('button', { name: 'direct link' })).toHaveCount(1);
// Owner should see list of templates with no direct link badge.
await page.goto(url);
await expect(page.getByRole('button', { name: 'direct link' })).toHaveCount(1);
// Create direct link.
await page.getByRole('button', { name: 'Create Direct Link' }).click();
await page.getByRole('button', { name: 'Enable direct link signing' }).click();
await page.getByRole('button', { name: 'Create one automatically' }).click();
await expect(page.getByRole('heading', { name: 'Direct Link Signing' })).toBeVisible();
// Create direct link.
await page.getByRole('button', { name: 'Create Direct Link' }).click();
await page.getByRole('button', { name: 'Enable direct link signing' }).click();
await page.getByRole('button', { name: 'Create one automatically' }).click();
await expect(page.getByRole('heading', { name: 'Direct Link Signing' })).toBeVisible();
await page.waitForTimeout(1000);
await page.getByTestId('btn-dialog-close').click();
await page.waitForTimeout(1000);
await page.getByTestId('btn-dialog-close').click();
// Expect badge to appear.
await expect(page.getByRole('button', { name: 'direct link' })).toHaveCount(2);
}
// Expect badge to appear.
await expect(page.getByRole('button', { name: 'direct link' })).toHaveCount(2);
});
test('[DIRECT_TEMPLATES]: toggle direct template link', async ({ page }) => {
const team = await seedTeam({
const { team, owner, organisation } = await seedTeam({
createTeamMembers: 1,
});
const owner = team.owner;
// Should only be visible to the owner in personal templates.
const personalDirectTemplate = await seedDirectTemplate({
title: 'Personal direct template link',
userId: owner.id,
});
// Should be visible to team members.
const teamDirectTemplate = await seedDirectTemplate({
const template = await seedDirectTemplate({
title: 'Team direct template link 1',
userId: owner.id,
teamId: team.id,
@ -99,41 +70,30 @@ test('[DIRECT_TEMPLATES]: toggle direct template link', async ({ page }) => {
email: owner.email,
});
// Run test for personal and team templates.
for (const template of [personalDirectTemplate, teamDirectTemplate]) {
// Check that the direct template link is accessible.
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
// Check that the direct template link is accessible.
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
// Navigate to template settings and disable access.
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}${formatTemplatesPath(template.team?.url)}`);
await page.getByRole('cell', { name: 'Use Template' }).getByRole('button').nth(1).click();
await page.getByRole('menuitem', { name: 'Direct link' }).click();
await page.getByRole('switch').click();
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByText('Direct link signing has been').first()).toBeVisible();
// Navigate to template settings and disable access.
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}${formatTemplatesPath(template.team?.url)}`);
await page.getByRole('cell', { name: 'Use Template' }).getByRole('button').nth(1).click();
await page.getByRole('menuitem', { name: 'Direct link' }).click();
await page.getByRole('switch').click();
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByText('Direct link signing has been').first()).toBeVisible();
// Check that the direct template link is no longer accessible.
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
await expect(page.getByText('404 not found')).toBeVisible();
}
// Check that the direct template link is no longer accessible.
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
await expect(page.getByText('404 not found')).toBeVisible();
});
test('[DIRECT_TEMPLATES]: delete direct template link', async ({ page }) => {
const team = await seedTeam({
const { team, owner, organisation } = await seedTeam({
createTeamMembers: 1,
});
const owner = team.owner;
// Should only be visible to the owner in personal templates.
const personalDirectTemplate = await seedDirectTemplate({
title: 'Personal direct template link',
userId: owner.id,
});
// Should be visible to team members.
const teamDirectTemplate = await seedDirectTemplate({
const template = await seedDirectTemplate({
title: 'Team direct template link 1',
userId: owner.id,
teamId: team.id,
@ -144,32 +104,30 @@ test('[DIRECT_TEMPLATES]: delete direct template link', async ({ page }) => {
email: owner.email,
});
// Run test for personal and team templates.
for (const template of [personalDirectTemplate, teamDirectTemplate]) {
// Check that the direct template link is accessible.
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
// Check that the direct template link is accessible.
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
// Navigate to template settings and delete the access.
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}${formatTemplatesPath(template.team?.url)}`);
await page.getByRole('cell', { name: 'Use Template' }).getByRole('button').nth(1).click();
await page.getByRole('menuitem', { name: 'Direct link' }).click();
await page.getByRole('button', { name: 'Remove' }).click();
await page.getByRole('button', { name: 'Confirm' }).click();
await expect(page.getByText('Direct template link deleted').first()).toBeVisible();
// Navigate to template settings and delete the access.
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}${formatTemplatesPath(template.team?.url)}`);
await page.getByRole('cell', { name: 'Use Template' }).getByRole('button').nth(1).click();
await page.getByRole('menuitem', { name: 'Direct link' }).click();
await page.getByRole('button', { name: 'Remove' }).click();
await page.getByRole('button', { name: 'Confirm' }).click();
await expect(page.getByText('Direct template link deleted').first()).toBeVisible();
// Check that the direct template link is no longer accessible.
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
await expect(page.getByText('404 not found')).toBeVisible();
}
// Check that the direct template link is no longer accessible.
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
await expect(page.getByText('404 not found')).toBeVisible();
});
test('[DIRECT_TEMPLATES]: direct template link auth access', async ({ page }) => {
const user = await seedUser();
const { user, team } = await seedUser();
const directTemplateWithAuth = await seedDirectTemplate({
title: 'Personal direct template link',
userId: user.id,
teamId: team.id,
createTemplateOptions: {
authOptions: createDocumentAuthOptions({
globalAccessAuth: ['ACCOUNT'],
@ -198,136 +156,26 @@ test('[DIRECT_TEMPLATES]: direct template link auth access', async ({ page }) =>
});
test('[DIRECT_TEMPLATES]: use direct template link with 1 recipient', async ({ page }) => {
const team = await seedTeam({
const { team, owner, organisation } = await seedTeam({
createTeamMembers: 1,
});
const owner = team.owner;
// Should only be visible to the owner in personal templates.
const personalDirectTemplate = await seedDirectTemplate({
title: 'Personal direct template link',
userId: owner.id,
});
// Should be visible to team members.
const teamDirectTemplate = await seedDirectTemplate({
const template = await seedDirectTemplate({
title: 'Team direct template link 1',
userId: owner.id,
teamId: team.id,
});
// Run test for personal and team templates.
for (const template of [personalDirectTemplate, teamDirectTemplate]) {
// Check that the direct template link is accessible.
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
// Check that the direct template link is accessible.
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
await page.getByPlaceholder('recipient@documenso.com').fill(seedTestEmail());
await page.getByPlaceholder('recipient@documenso.com').fill(seedTestEmail());
await page.getByRole('button', { name: 'Continue' }).click();
await page.getByRole('button', { name: 'Complete' }).click();
await page.getByRole('button', { name: 'Sign' }).click();
await page.waitForURL(/\/sign/);
await expect(page.getByRole('heading', { name: 'Document Signed' })).toBeVisible();
}
await apiSignin({
page,
email: owner.email,
});
// Check that the owner has the documents.
for (const template of [personalDirectTemplate, teamDirectTemplate]) {
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}${formatDocumentsPath(template.team?.url)}`);
await expect(async () => {
// Check that the document is in the 'All' tab.
await checkDocumentTabCount(page, 'Completed', 1);
}).toPass();
}
});
test('[DIRECT_TEMPLATES]: use direct template link with 2 recipients', async ({ page }) => {
const team = await seedTeam({
createTeamMembers: 1,
});
const owner = team.owner;
const secondRecipient = await seedUser();
const createTemplateOptions = {
recipients: {
createMany: {
data: [
{
email: DIRECT_TEMPLATE_RECIPIENT_EMAIL,
name: DIRECT_TEMPLATE_RECIPIENT_NAME,
token: nanoid(),
},
{
email: secondRecipient.email,
token: nanoid(),
},
],
},
},
};
// Should only be visible to the owner in personal templates.
const personalDirectTemplate = await seedDirectTemplate({
title: 'Personal direct template link',
userId: owner.id,
createTemplateOptions,
});
// Should be visible to team members.
const teamDirectTemplate = await seedDirectTemplate({
title: 'Team direct template link 1',
userId: owner.id,
teamId: team.id,
createTemplateOptions,
});
// Run test for personal and team templates.
for (const template of [personalDirectTemplate, teamDirectTemplate]) {
// Check that the direct template link is accessible.
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
await page.waitForTimeout(1000);
await page.getByPlaceholder('recipient@documenso.com').fill(seedTestEmail());
await page.getByRole('button', { name: 'Continue' }).click();
await page.getByRole('button', { name: 'Complete' }).click();
await page.getByRole('button', { name: 'Sign' }).click();
await page.waitForURL(/\/sign/);
await expect(page.getByText('Waiting for others to sign')).toBeVisible();
}
await apiSignin({
page,
email: owner.email,
});
// Check that the owner has the documents.
for (const template of [personalDirectTemplate, teamDirectTemplate]) {
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}${formatDocumentsPath(template.team?.url)}`);
// Check that the document is in the 'All' tab.
await checkDocumentTabCount(page, 'All', 1);
await checkDocumentTabCount(page, 'Pending', 1);
}
// Check that the second recipient has the 2 pending documents.
await apiSignin({
page,
email: secondRecipient.email,
});
await page.goto('/documents');
await checkDocumentTabCount(page, 'All', 2);
await checkDocumentTabCount(page, 'Inbox', 2);
await page.getByRole('button', { name: 'Continue' }).click();
await page.getByRole('button', { name: 'Complete' }).click();
await page.getByRole('button', { name: 'Sign' }).click();
await page.waitForURL(/\/sign/);
await expect(page.getByRole('heading', { name: 'Document Signed' })).toBeVisible();
});

View File

@ -1,23 +1,19 @@
import { expect, test } from '@playwright/test';
import { TeamMemberRole } from '@prisma/client';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { seedTeam } from '@documenso/prisma/seed/teams';
import { seedTeam, seedTeamMember } from '@documenso/prisma/seed/teams';
import { seedTemplate } from '@documenso/prisma/seed/templates';
import { apiSignin } from '../fixtures/authentication';
test('[TEMPLATES]: view templates', async ({ page }) => {
const team = await seedTeam({
const { team, owner, organisation } = await seedTeam({
createTeamMembers: 1,
});
const owner = team.owner;
const teamMemberUser = team.members[1].user;
// Should only be visible to the owner in personal templates.
await seedTemplate({
title: 'Personal template',
userId: owner.id,
const teamMemberUser = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MEMBER,
});
// Should be visible to team members.
@ -37,29 +33,21 @@ test('[TEMPLATES]: view templates', async ({ page }) => {
await apiSignin({
page,
email: owner.email,
redirectPath: '/templates',
redirectPath: `/t/${team.url}/templates`,
});
// Only should only see their personal template.
await expect(page.getByTestId('data-table-count')).toContainText('Showing 1 result');
// Owner should see both team templates.
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/t/${team.url}/templates`);
await expect(page.getByTestId('data-table-count')).toContainText('Showing 2 results');
});
test('[TEMPLATES]: delete template', async ({ page }) => {
const team = await seedTeam({
const { team, owner, organisation } = await seedTeam({
createTeamMembers: 1,
});
const owner = team.owner;
const teamMemberUser = team.members[1].user;
// Should only be visible to the owner in personal templates.
await seedTemplate({
title: 'Personal template',
userId: owner.id,
const teamMemberUser = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MEMBER,
});
// Should be visible to team members.
@ -79,18 +67,9 @@ test('[TEMPLATES]: delete template', async ({ page }) => {
await apiSignin({
page,
email: owner.email,
redirectPath: '/templates',
redirectPath: `/t/${team.url}/templates`,
});
// Owner should be able to delete their personal template.
await page.getByRole('cell', { name: 'Use Template' }).getByRole('button').nth(1).click();
await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByRole('button', { name: 'Delete' }).click();
await expect(page.getByText('Template deleted').first()).toBeVisible();
// Team member should be able to delete all templates.
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/t/${team.url}/templates`);
for (const template of ['Team template 1', 'Team template 2']) {
await page
.getByRole('row', { name: template })
@ -108,17 +87,13 @@ test('[TEMPLATES]: delete template', async ({ page }) => {
});
test('[TEMPLATES]: duplicate template', async ({ page }) => {
const team = await seedTeam({
const { team, owner, organisation } = await seedTeam({
createTeamMembers: 1,
});
const owner = team.owner;
const teamMemberUser = team.members[1].user;
// Should only be visible to the owner in personal templates.
await seedTemplate({
title: 'Personal template',
userId: owner.id,
const teamMemberUser = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MEMBER,
});
// Should be visible to team members.
@ -131,18 +106,9 @@ test('[TEMPLATES]: duplicate template', async ({ page }) => {
await apiSignin({
page,
email: owner.email,
redirectPath: '/templates',
redirectPath: `/t/${team.url}/templates`,
});
// Duplicate personal template.
await page.getByRole('cell', { name: 'Use Template' }).getByRole('button').nth(1).click();
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
await page.getByRole('button', { name: 'Duplicate' }).click();
await expect(page.getByText('Template duplicated').first()).toBeVisible();
await expect(page.getByTestId('data-table-count')).toContainText('Showing 2 results');
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/t/${team.url}/templates`);
// Duplicate team template.
await page.getByRole('cell', { name: 'Use Template' }).getByRole('button').nth(1).click();
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
@ -152,17 +118,13 @@ test('[TEMPLATES]: duplicate template', async ({ page }) => {
});
test('[TEMPLATES]: use template', async ({ page }) => {
const team = await seedTeam({
const { team, owner, organisation } = await seedTeam({
createTeamMembers: 1,
});
const owner = team.owner;
const teamMemberUser = team.members[1].user;
// Should only be visible to the owner in personal templates.
await seedTemplate({
title: 'Personal template',
userId: owner.id,
const teamMemberUser = await seedTeamMember({
teamId: team.id,
role: TeamMemberRole.MEMBER,
});
// Should be visible to team members.
@ -175,27 +137,9 @@ test('[TEMPLATES]: use template', async ({ page }) => {
await apiSignin({
page,
email: owner.email,
redirectPath: '/templates',
redirectPath: `/t/${team.url}/templates`,
});
// Use personal template.
await page.getByRole('button', { name: 'Use Template' }).click();
// Enter template values.
await page.getByPlaceholder('recipient.1@documenso.com').click();
await page.getByPlaceholder('recipient.1@documenso.com').fill(teamMemberUser.email);
await page.getByPlaceholder('Recipient 1').click();
await page.getByPlaceholder('Recipient 1').fill('name');
await page.getByRole('button', { name: 'Create as draft' }).click();
await page.waitForURL(/documents/);
await page.getByRole('main').getByRole('link', { name: 'Documents' }).click();
await page.waitForURL('/documents');
await expect(page.getByTestId('data-table-count')).toContainText('Showing 1 result');
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/t/${team.url}/templates`);
await page.waitForTimeout(1000);
// Use team template.
await page.getByRole('button', { name: 'Use Template' }).click();

View File

@ -1,6 +1,6 @@
import { type Page, expect, test } from '@playwright/test';
import { alphaid } from '@documenso/lib/universal/id';
import { prisma } from '@documenso/prisma';
import {
extractUserVerificationToken,
seedTestEmail,
@ -23,17 +23,26 @@ test('[USER] can sign up with email and password', async ({ page }: { page: Page
await signSignaturePad(page);
await page.getByRole('button', { name: 'Next', exact: true }).click();
await page.getByLabel('Public profile username').fill(alphaid(10));
await page.getByLabel('Public profile username').blur();
await page.getByRole('button', { name: 'Complete' }).click();
await page.getByRole('button', { name: 'Complete', exact: true }).click();
await page.waitForURL('/unverified-account');
const { token } = await extractUserVerificationToken(email);
const team = await prisma.team.findFirstOrThrow({
where: {
organisation: {
members: {
some: {
user: {
email,
},
},
},
},
},
});
await page.goto(`/verify-email/${token}`);
await expect(page.getByRole('heading')).toContainText('Email Confirmed!');
@ -41,19 +50,19 @@ test('[USER] can sign up with email and password', async ({ page }: { page: Page
// We now automatically redirect to the home page
await page.getByRole('link', { name: 'Continue' }).click();
await page.waitForURL('/documents');
await expect(page).toHaveURL('/documents');
// Expect to be redirected to their only team.
await page.waitForURL(`/t/${team.url}/documents`);
await expect(page).toHaveURL(`/t/${team.url}/documents`);
});
test('[USER] can sign in using email and password', async ({ page }: { page: Page }) => {
const user = await seedUser();
const { user, team } = await seedUser();
await page.goto('/signin');
await page.getByLabel('Email').fill(user.email);
await page.getByLabel('Password', { exact: true }).fill('password');
await page.getByRole('button', { name: 'Sign In' }).click();
await page.waitForURL('/documents');
await expect(page).toHaveURL('/documents');
await page.waitForURL(`/t/${team.url}/documents`);
await expect(page).toHaveURL(`/t/${team.url}/documents`);
});

View File

@ -7,7 +7,7 @@ import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
test('[USER] delete account', async ({ page }) => {
const user = await seedUser();
const { user } = await seedUser();
await apiSignin({ page, email: user.email, redirectPath: '/settings' });

View File

@ -11,7 +11,7 @@ test('[USER] can reset password via forgot password', async ({ page }: { page: P
const oldPassword = 'Test123!';
const newPassword = 'Test124!';
const user = await seedUser({
const { user } = await seedUser({
password: oldPassword,
});
@ -51,17 +51,18 @@ test('[USER] can reset password via forgot password', async ({ page }: { page: P
page,
email: user.email,
password: newPassword,
redirectPath: '/settings/profile',
});
await page.waitForURL('/documents');
await expect(page).toHaveURL('/documents');
await page.waitForURL('/settings/profile');
await expect(page).toHaveURL('/settings/profile');
});
test('[USER] can reset password via user settings', async ({ page }: { page: Page }) => {
const oldPassword = 'Test123!';
const newPassword = 'Test124!';
const user = await seedUser({
const { user } = await seedUser({
password: oldPassword,
});
@ -87,8 +88,9 @@ test('[USER] can reset password via user settings', async ({ page }: { page: Pag
page,
email: user.email,
password: newPassword,
redirectPath: '/settings/profile',
});
await page.waitForURL('/documents');
await expect(page).toHaveURL('/documents');
await page.waitForURL('/settings/profile');
await expect(page).toHaveURL('/settings/profile');
});

View File

@ -7,7 +7,7 @@ import { apiSignin } from '../fixtures/authentication';
import { signSignaturePad } from '../fixtures/signature';
test('[USER] update full name', async ({ page }) => {
const user = await seedUser();
const { user } = await seedUser();
await apiSignin({ page, email: user.email, redirectPath: '/settings/profile' });