diff --git a/tests/affine-cloud/e2e/basic.spec.ts b/tests/affine-cloud/e2e/basic.spec.ts index 9c6ff19f9e..f04c9fc07d 100644 --- a/tests/affine-cloud/e2e/basic.spec.ts +++ b/tests/affine-cloud/e2e/basic.spec.ts @@ -20,7 +20,7 @@ test.beforeEach(async () => { }); test.beforeEach(async ({ page, context }) => { - await loginUser(page, user, { + await loginUser(page, user.email, { beforeLogin: async () => { expect(await getLoginCookie(context)).toBeUndefined(); }, diff --git a/tests/affine-cloud/e2e/collaboration.spec.ts b/tests/affine-cloud/e2e/collaboration.spec.ts index 47864a0456..7accc1e31b 100644 --- a/tests/affine-cloud/e2e/collaboration.spec.ts +++ b/tests/affine-cloud/e2e/collaboration.spec.ts @@ -1,15 +1,20 @@ import { test } from '@affine-test/kit/playwright'; -import { createRandomUser, loginUser } from '@affine-test/kit/utils/cloud'; +import { + addUserToWorkspace, + createRandomUser, + enableCloudWorkspace, + loginUser, +} from '@affine-test/kit/utils/cloud'; import { clickNewPageButton, getBlockSuiteEditorTitle, waitForEditorLoad, } from '@affine-test/kit/utils/page-logic'; -import { clickSideBarSettingButton } from '@affine-test/kit/utils/sidebar'; import { createLocalWorkspace } from '@affine-test/kit/utils/workspace'; import { expect } from '@playwright/test'; let user: { + id: string; name: string; email: string; password: string; @@ -20,7 +25,7 @@ test.beforeEach(async () => { }); test.beforeEach(async ({ page }) => { - await loginUser(page, user); + await loginUser(page, user.email); }); test.describe('collaboration', () => { @@ -33,14 +38,7 @@ test.describe('collaboration', () => { }, page ); - await clickSideBarSettingButton(page); - await page.getByTestId('current-workspace-label').click(); - await page.getByTestId('publish-enable-affine-cloud-button').click(); - await page.getByTestId('confirm-enable-affine-cloud-button').click(); - // wait for upload and delete local workspace - await page.waitForTimeout(2000); - await waitForEditorLoad(page); - await clickNewPageButton(page); + await enableCloudWorkspace(page); const title = getBlockSuiteEditorTitle(page); await title.type('TEST TITLE', { delay: 50, @@ -67,4 +65,39 @@ test.describe('collaboration', () => { ); } }); + + test('can collaborate with other user', async ({ page, browser }) => { + await page.reload(); + await waitForEditorLoad(page); + await createLocalWorkspace( + { + name: 'test', + }, + page + ); + await enableCloudWorkspace(page); + await clickNewPageButton(page); + const currentUrl = page.url(); + // format: http://localhost:8080/workspace/${workspaceId}/xxx + const workspaceId = currentUrl.split('/')[4]; + const userB = await createRandomUser(); + const context = await browser.newContext(); + const page2 = await context.newPage(); + await loginUser(page2, userB.email); + await addUserToWorkspace(workspaceId, userB.id, 1 /* READ */); + await page2.reload(); + await waitForEditorLoad(page2); + await page2.goto(currentUrl); + { + const title = getBlockSuiteEditorTitle(page); + await title.type('TEST TITLE', { + delay: 50, + }); + } + await page2.waitForTimeout(200); + { + const title = getBlockSuiteEditorTitle(page2); + expect(await title.innerText()).toBe('TEST TITLE'); + } + }); }); diff --git a/tests/kit/tsconfig.json b/tests/kit/tsconfig.json index cdcdf6248d..d661639f21 100644 --- a/tests/kit/tsconfig.json +++ b/tests/kit/tsconfig.json @@ -5,5 +5,10 @@ "noEmit": false, "outDir": "lib" }, - "include": ["./*.ts", "utils"] + "include": ["./*.ts", "utils"], + "references": [ + { + "path": "../../apps/server" + } + ] } diff --git a/tests/kit/utils/cloud.ts b/tests/kit/utils/cloud.ts index 18e9621a8f..c9600b116b 100644 --- a/tests/kit/utils/cloud.ts +++ b/tests/kit/utils/cloud.ts @@ -1,6 +1,12 @@ import { openHomePage } from '@affine-test/kit/utils/load-page'; -import { waitForEditorLoad } from '@affine-test/kit/utils/page-logic'; -import { clickSideBarCurrentWorkspaceBanner } from '@affine-test/kit/utils/sidebar'; +import { + clickNewPageButton, + waitForEditorLoad, +} from '@affine-test/kit/utils/page-logic'; +import { + clickSideBarCurrentWorkspaceBanner, + clickSideBarSettingButton, +} from '@affine-test/kit/utils/sidebar'; import { faker } from '@faker-js/faker'; import { hash } from '@node-rs/argon2'; import type { BrowserContext, Cookie, Page } from '@playwright/test'; @@ -15,61 +21,105 @@ export async function getLoginCookie( } const cloudUserSchema = z.object({ + id: z.string(), name: z.string(), email: z.string().email(), password: z.string(), }); -export type CloudUser = z.infer; +export const runPrisma = async ( + cb: ( + prisma: InstanceType< + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + typeof import('../../../apps/server/node_modules/@prisma/client').PrismaClient + > + ) => Promise +): Promise => { + const { + PrismaClient, + // eslint-disable-next-line @typescript-eslint/no-var-requires + } = require('../../../apps/server/node_modules/@prisma/client'); + const client = new PrismaClient(); + await client.$connect(); + try { + return await cb(client); + } finally { + await client.$disconnect(); + } +}; -export async function createRandomUser(): Promise { +export async function addUserToWorkspace( + workspaceId: string, + userId: string, + permission: number +) { + await runPrisma(async client => { + const workspace = await client.workspace.findUnique({ + where: { + id: workspaceId, + }, + }); + if (workspace == null) { + throw new Error(`workspace ${workspaceId} not found`); + } + await client.userWorkspacePermission.create({ + data: { + workspaceId: workspace.id, + subPageId: null, + userId, + accepted: true, + type: permission, + }, + }); + }); +} + +export async function createRandomUser(): Promise<{ + name: string; + email: string; + password: string; + id: string; +}> { const user = { name: faker.internet.userName(), email: faker.internet.email().toLowerCase(), password: '123456', - } satisfies CloudUser; - const { - PrismaClient, - // eslint-disable-next-line @typescript-eslint/no-var-requires - } = require('../../../apps/server/node_modules/@prisma/client'); - const client = new PrismaClient(); - await client.$connect(); - await client.user.create({ - data: { - ...user, - emailVerified: new Date(), - password: await hash(user.password), - }, - }); - await client.$disconnect(); + }; + const result = await runPrisma(async client => { + await client.user.create({ + data: { + ...user, + emailVerified: new Date(), + password: await hash(user.password), + }, + }); - const result = await client.user.findUnique({ - where: { - email: user.email, - }, + return await client.user.findUnique({ + where: { + email: user.email, + }, + }); }); cloudUserSchema.parse(result); - return result; + return { + ...result, + password: user.password, + } as any; } export async function deleteUser(email: string) { - const { - PrismaClient, - // eslint-disable-next-line @typescript-eslint/no-var-requires - } = require('../../../apps/server/node_modules/@prisma/client'); - const client = new PrismaClient(); - await client.$connect(); - await client.user.delete({ - where: { - email, - }, + await runPrisma(async client => { + await client.user.delete({ + where: { + email, + }, + }); }); - await client.$disconnect(); } export async function loginUser( page: Page, - user: CloudUser, + userEmail: string, config?: { beforeLogin?: () => Promise; afterLogin?: () => Promise; @@ -82,7 +132,7 @@ export async function loginUser( await page.getByTestId('cloud-signin-button').click({ delay: 200, }); - await page.getByPlaceholder('Enter your email address').type(user.email, { + await page.getByPlaceholder('Enter your email address').type(userEmail, { delay: 50, }); await page.getByTestId('continue-login-button').click({ @@ -104,3 +154,14 @@ export async function loginUser( await config.afterLogin(); } } + +export async function enableCloudWorkspace(page: Page) { + await clickSideBarSettingButton(page); + await page.getByTestId('current-workspace-label').click(); + await page.getByTestId('publish-enable-affine-cloud-button').click(); + await page.getByTestId('confirm-enable-affine-cloud-button').click(); + // wait for upload and delete local workspace + await page.waitForTimeout(2000); + await waitForEditorLoad(page); + await clickNewPageButton(page); +}