feat(editor): ✨ Team workspaces
This commit is contained in:
28
apps/builder/playwright/tests/accountSettings.spec.ts
Normal file
28
apps/builder/playwright/tests/accountSettings.spec.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import test, { expect } from '@playwright/test'
|
||||
import path from 'path'
|
||||
|
||||
// Can't test the update features because of the auth mocking.
|
||||
test('should display user info properly', async ({ page }) => {
|
||||
await page.goto('/typebots')
|
||||
await page.click('text=Settings & Members')
|
||||
const saveButton = page.locator('button:has-text("Save")')
|
||||
await expect(saveButton).toBeHidden()
|
||||
await expect(
|
||||
page.locator('input[type="email"]').getAttribute('disabled')
|
||||
).toBeDefined()
|
||||
await page.fill('#name', 'John Doe')
|
||||
expect(saveButton).toBeVisible()
|
||||
await page.setInputFiles(
|
||||
'input[type="file"]',
|
||||
path.join(__dirname, '../fixtures/avatar.jpg')
|
||||
)
|
||||
await expect(page.locator('img >> nth=1')).toHaveAttribute(
|
||||
'src',
|
||||
new RegExp(
|
||||
`http://localhost:9000/typebot/public/users/proUser/avatar`,
|
||||
'gm'
|
||||
)
|
||||
)
|
||||
await page.click('text="Preferences"')
|
||||
await expect(page.locator('text=Trackpad')).toBeVisible()
|
||||
})
|
@ -1,35 +1,41 @@
|
||||
import test, { expect } from '@playwright/test'
|
||||
import cuid from 'cuid'
|
||||
import { CollaborationType, Plan, WorkspaceRole } from 'db'
|
||||
import prisma from 'libs/prisma'
|
||||
import { InputStepType, defaultTextInputOptions } from 'models'
|
||||
import path from 'path'
|
||||
import {
|
||||
createResults,
|
||||
createTypebots,
|
||||
parseDefaultBlockWithStep,
|
||||
} from '../services/database'
|
||||
|
||||
const typebotId = cuid()
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await createTypebots([
|
||||
{
|
||||
id: typebotId,
|
||||
name: 'Shared typebot',
|
||||
ownerId: 'freeUser',
|
||||
...parseDefaultBlockWithStep({
|
||||
type: InputStepType.TEXT,
|
||||
options: defaultTextInputOptions,
|
||||
}),
|
||||
},
|
||||
])
|
||||
await createResults({ typebotId })
|
||||
})
|
||||
|
||||
test.describe('Typebot owner', () => {
|
||||
test.use({
|
||||
storageState: path.join(__dirname, '../freeUser.json'),
|
||||
})
|
||||
test('Can invite collaborators', async ({ page }) => {
|
||||
const typebotId = cuid()
|
||||
const guestWorkspaceId = cuid()
|
||||
await prisma.workspace.create({
|
||||
data: {
|
||||
id: guestWorkspaceId,
|
||||
name: 'Guest Workspace',
|
||||
plan: Plan.FREE,
|
||||
members: {
|
||||
createMany: {
|
||||
data: [{ role: WorkspaceRole.ADMIN, userId: 'proUser' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
await createTypebots([
|
||||
{
|
||||
id: typebotId,
|
||||
name: 'Guest typebot',
|
||||
workspaceId: guestWorkspaceId,
|
||||
...parseDefaultBlockWithStep({
|
||||
type: InputStepType.TEXT,
|
||||
options: defaultTextInputOptions,
|
||||
}),
|
||||
},
|
||||
])
|
||||
await page.goto(`/typebots/${typebotId}/edit`)
|
||||
await page.click('button[aria-label="Show collaboration menu"]')
|
||||
await expect(page.locator('text=Free user')).toBeHidden()
|
||||
@ -44,13 +50,12 @@ test.describe('Typebot owner', () => {
|
||||
await expect(page.locator('text=Free user')).toBeHidden()
|
||||
await page.fill(
|
||||
'input[placeholder="colleague@company.com"]',
|
||||
'pro-user@email.com'
|
||||
'free-user@email.com'
|
||||
)
|
||||
await page.click('text=Can edit')
|
||||
await page.click('text=Can view')
|
||||
await page.click('text=Invite')
|
||||
await expect(page.locator('text=Free user')).toBeVisible()
|
||||
await expect(page.locator('text=Pro user')).toBeVisible()
|
||||
await page.click('text="guest@email.com"')
|
||||
await page.click('text="Remove"')
|
||||
await expect(page.locator('text="guest@email.com"')).toBeHidden()
|
||||
@ -59,17 +64,47 @@ test.describe('Typebot owner', () => {
|
||||
|
||||
test.describe('Collaborator', () => {
|
||||
test('should display shared typebots', async ({ page }) => {
|
||||
await page.goto('/typebots')
|
||||
await expect(page.locator('text=Shared')).toBeVisible()
|
||||
await page.click('text=Shared')
|
||||
await page.waitForNavigation()
|
||||
expect(page.url()).toMatch('/typebots/shared')
|
||||
await expect(page.locator('text="Shared typebot"')).toBeVisible()
|
||||
await page.click('text=Shared typebot')
|
||||
const typebotId = cuid()
|
||||
const guestWorkspaceId = cuid()
|
||||
await prisma.workspace.create({
|
||||
data: {
|
||||
id: guestWorkspaceId,
|
||||
name: 'Guest Workspace #2',
|
||||
plan: Plan.FREE,
|
||||
members: {
|
||||
createMany: {
|
||||
data: [{ role: WorkspaceRole.GUEST, userId: 'proUser' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
await createTypebots([
|
||||
{
|
||||
id: typebotId,
|
||||
name: 'Guest typebot',
|
||||
workspaceId: guestWorkspaceId,
|
||||
...parseDefaultBlockWithStep({
|
||||
type: InputStepType.TEXT,
|
||||
options: defaultTextInputOptions,
|
||||
}),
|
||||
},
|
||||
])
|
||||
await prisma.collaboratorsOnTypebots.create({
|
||||
data: {
|
||||
typebotId,
|
||||
userId: 'proUser',
|
||||
type: CollaborationType.READ,
|
||||
},
|
||||
})
|
||||
await createResults({ typebotId })
|
||||
await page.goto(`/typebots`)
|
||||
await page.click("text=Pro user's workspace")
|
||||
await page.click('text=Guest workspace #2')
|
||||
await page.click('text=Guest typebot')
|
||||
await page.click('button[aria-label="Show collaboration menu"]')
|
||||
await page.click('text=Pro user')
|
||||
await page.click('text=Everyone at Guest workspace')
|
||||
await expect(page.locator('text="Remove"')).toBeHidden()
|
||||
await expect(page.locator('text=Free user')).toBeVisible()
|
||||
await expect(page.locator('text=Pro user')).toBeVisible()
|
||||
await page.click('text=Block #1', { force: true })
|
||||
await expect(page.locator('input[value="Block #1"]')).toBeHidden()
|
||||
await page.goto(`/typebots/${typebotId}/results`)
|
||||
|
@ -1,63 +1,67 @@
|
||||
import test, { expect } from '@playwright/test'
|
||||
import { InputStepType, defaultTextInputOptions } from 'models'
|
||||
import { createTypebots, parseDefaultBlockWithStep } from '../services/database'
|
||||
import {
|
||||
createTypebots,
|
||||
freeWorkspaceId,
|
||||
parseDefaultBlockWithStep,
|
||||
} from '../services/database'
|
||||
import path from 'path'
|
||||
import cuid from 'cuid'
|
||||
|
||||
const typebotId = cuid()
|
||||
test.describe('Dashboard page', () => {
|
||||
test('should be able to connect custom domain', async ({ page }) => {
|
||||
test('should be able to connect custom domain', async ({ page }) => {
|
||||
const typebotId = cuid()
|
||||
await createTypebots([
|
||||
{
|
||||
id: typebotId,
|
||||
...parseDefaultBlockWithStep({
|
||||
type: InputStepType.TEXT,
|
||||
options: defaultTextInputOptions,
|
||||
}),
|
||||
},
|
||||
])
|
||||
await page.goto(`/typebots/${typebotId}/share`)
|
||||
await page.click('text=Add my domain')
|
||||
await page.click('text=Connect new')
|
||||
await page.fill('input[placeholder="bot.my-domain.com"]', 'test')
|
||||
await expect(page.locator('text=Save')).toBeDisabled()
|
||||
await page.fill('input[placeholder="bot.my-domain.com"]', 'yolozeeer.com')
|
||||
await expect(page.locator('text="A"')).toBeVisible()
|
||||
await page.fill('input[placeholder="bot.my-domain.com"]', 'sub.yolozeeer.com')
|
||||
await expect(page.locator('text="CNAME"')).toBeVisible()
|
||||
await page.click('text=Save')
|
||||
await expect(page.locator('text="https://sub.yolozeeer.com/"')).toBeVisible()
|
||||
await page.click('text="Edit" >> nth=1')
|
||||
await page.fill('text=https://sub.yolozeeer.com/Copy >> input', 'custom-path')
|
||||
await page.press(
|
||||
'text=https://sub.yolozeeer.com/custom-path >> input',
|
||||
'Enter'
|
||||
)
|
||||
await expect(page.locator('text="custom-path"')).toBeVisible()
|
||||
await page.click('[aria-label="Remove custom domain"]')
|
||||
await expect(page.locator('text=sub.yolozeeer.com')).toBeHidden()
|
||||
await page.click('button >> text=Add my domain')
|
||||
await page.click('[aria-label="Remove domain"]')
|
||||
await expect(page.locator('[aria-label="Remove domain"]')).toBeHidden()
|
||||
})
|
||||
|
||||
test.describe('Free workspace', () => {
|
||||
test.use({
|
||||
storageState: path.join(__dirname, '../freeUser.json'),
|
||||
})
|
||||
test("Add my domain shouldn't be available", async ({ page }) => {
|
||||
const typebotId = cuid()
|
||||
await createTypebots([
|
||||
{
|
||||
id: typebotId,
|
||||
workspaceId: freeWorkspaceId,
|
||||
...parseDefaultBlockWithStep({
|
||||
type: InputStepType.TEXT,
|
||||
options: defaultTextInputOptions,
|
||||
}),
|
||||
},
|
||||
])
|
||||
|
||||
await page.goto(`/typebots/${typebotId}/share`)
|
||||
await page.click('text=Add my domain')
|
||||
await page.click('text=Connect new')
|
||||
await page.fill('input[placeholder="bot.my-domain.com"]', 'test')
|
||||
await expect(page.locator('text=Save')).toBeDisabled()
|
||||
await page.fill('input[placeholder="bot.my-domain.com"]', 'yolozeeer.com')
|
||||
await expect(page.locator('text="A"')).toBeVisible()
|
||||
await page.fill(
|
||||
'input[placeholder="bot.my-domain.com"]',
|
||||
'sub.yolozeeer.com'
|
||||
)
|
||||
await expect(page.locator('text="CNAME"')).toBeVisible()
|
||||
await page.click('text=Save')
|
||||
await expect(
|
||||
page.locator('text="https://sub.yolozeeer.com/"')
|
||||
).toBeVisible()
|
||||
await page.click('text="Edit" >> nth=1')
|
||||
await page.fill(
|
||||
'text=https://sub.yolozeeer.com/Copy >> input',
|
||||
'custom-path'
|
||||
)
|
||||
await page.press(
|
||||
'text=https://sub.yolozeeer.com/custom-path >> input',
|
||||
'Enter'
|
||||
)
|
||||
await expect(page.locator('text="custom-path"')).toBeVisible()
|
||||
await page.click('[aria-label="Remove custom domain"]')
|
||||
await expect(page.locator('text=sub.yolozeeer.com')).toBeHidden()
|
||||
await page.click('button >> text=Add my domain')
|
||||
await page.click('[aria-label="Remove domain"]')
|
||||
await expect(page.locator('[aria-label="Remove domain"]')).toBeHidden()
|
||||
})
|
||||
|
||||
test.describe('Free user', () => {
|
||||
test.use({
|
||||
storageState: path.join(__dirname, '../freeUser.json'),
|
||||
})
|
||||
test("Add my domain shouldn't be available", async ({ page }) => {
|
||||
await page.goto(`/typebots/${typebotId}/share`)
|
||||
await page.click('text=Add my domain')
|
||||
await expect(page.locator('text=Upgrade now')).toBeVisible()
|
||||
})
|
||||
await expect(page.locator('text=For solo creator')).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
@ -80,11 +80,10 @@ test.describe('Dashboard page', () => {
|
||||
})
|
||||
test("create folder shouldn't be available", async ({ page }) => {
|
||||
await page.goto('/typebots')
|
||||
await page.click('text=Shared workspace')
|
||||
await page.click('text=Free workspace')
|
||||
await page.click('text=Create a folder')
|
||||
await expect(
|
||||
page.locator('text="You can\'t create folders with the basic plan"')
|
||||
).toBeVisible()
|
||||
await expect(page.locator('text=Upgrade now')).toBeVisible()
|
||||
await expect(page.locator('text=For solo creator')).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,6 +1,7 @@
|
||||
import test, { expect, Page } from '@playwright/test'
|
||||
import cuid from 'cuid'
|
||||
import { readFileSync } from 'fs'
|
||||
import prisma from 'libs/prisma'
|
||||
import { defaultTextInputOptions, InputStepType } from 'models'
|
||||
import { parse } from 'papaparse'
|
||||
import path from 'path'
|
||||
@ -113,14 +114,18 @@ test.describe('Results page', () => {
|
||||
validateExportAll(dataAll)
|
||||
})
|
||||
|
||||
test.describe('Free user', () => {
|
||||
test.describe('Free user', async () => {
|
||||
test.use({
|
||||
storageState: path.join(__dirname, '../freeUser.json'),
|
||||
})
|
||||
test("Incomplete results shouldn't be displayed", async ({ page }) => {
|
||||
await prisma.typebot.update({
|
||||
where: { id: typebotId },
|
||||
data: { workspaceId: 'free' },
|
||||
})
|
||||
await page.goto(`/typebots/${typebotId}/results`)
|
||||
await page.click('text=Unlock')
|
||||
await expect(page.locator('text=Upgrade now')).toBeVisible()
|
||||
await expect(page.locator('text=For solo creator')).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -124,13 +124,14 @@ test.describe.parallel('Settings page', () => {
|
||||
path.join(__dirname, '../fixtures/typebots/settings.json'),
|
||||
{
|
||||
id: typebotId,
|
||||
workspaceId: 'free',
|
||||
}
|
||||
)
|
||||
await page.goto(`/typebots/${typebotId}/settings`)
|
||||
await page.click('button:has-text("General")')
|
||||
await expect(page.locator('text=Pro')).toBeVisible()
|
||||
await page.click('text=Typebot.io branding')
|
||||
await expect(page.locator('text=Upgrade now')).toBeVisible()
|
||||
await expect(page.locator('text=For solo creator')).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
134
apps/builder/playwright/tests/workspaces.spec.ts
Normal file
134
apps/builder/playwright/tests/workspaces.spec.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import test, { expect } from '@playwright/test'
|
||||
import cuid from 'cuid'
|
||||
import { defaultTextInputOptions, InputStepType } from 'models'
|
||||
import {
|
||||
createTypebots,
|
||||
parseDefaultBlockWithStep,
|
||||
sharedWorkspaceId,
|
||||
} from '../services/database'
|
||||
|
||||
const proTypebotId = cuid()
|
||||
const freeTypebotId = cuid()
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await createTypebots([{ id: proTypebotId, name: 'Pro typebot' }])
|
||||
await createTypebots([
|
||||
{
|
||||
id: freeTypebotId,
|
||||
name: 'Shared typebot',
|
||||
workspaceId: sharedWorkspaceId,
|
||||
...parseDefaultBlockWithStep({
|
||||
type: InputStepType.TEXT,
|
||||
options: {
|
||||
...defaultTextInputOptions,
|
||||
labels: {
|
||||
...defaultTextInputOptions.labels,
|
||||
placeholder: 'Hey there',
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('can switch between workspaces and access typebot', async ({ page }) => {
|
||||
await page.goto('/typebots')
|
||||
await expect(page.locator('text="Pro typebot"')).toBeVisible()
|
||||
await page.click("text=Pro user's workspace")
|
||||
await page.click('text=Shared workspace')
|
||||
await expect(page.locator('text="Pro typebot"')).toBeHidden()
|
||||
await page.click('text="Shared typebot"')
|
||||
await expect(page.locator('text="Hey there"')).toBeVisible()
|
||||
})
|
||||
|
||||
test('can create a new workspace', async ({ page }) => {
|
||||
await page.goto('/typebots')
|
||||
await page.click("text=Pro user's workspace")
|
||||
await expect(
|
||||
page.locator('text="Pro user\'s workspace" >> nth=1')
|
||||
).toBeHidden()
|
||||
await page.click('text=New workspace')
|
||||
await expect(page.locator('text="Pro typebot"')).toBeHidden()
|
||||
await page.click("text=Pro user's workspace")
|
||||
await expect(
|
||||
page.locator('text="Pro user\'s workspace" >> nth=1')
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('can update workspace info', async ({ page }) => {
|
||||
await page.goto('/typebots')
|
||||
await page.click('text=Settings & Members')
|
||||
await page.click('text="Settings"')
|
||||
await page.click('[data-testid="editable-icon"]')
|
||||
await page.fill('input[placeholder="Search..."]', 'building')
|
||||
await page.click('text="🏦"')
|
||||
await page.fill(
|
||||
'input[value="Pro user\'s workspace"]',
|
||||
'My awesome workspace'
|
||||
)
|
||||
})
|
||||
|
||||
test('can manage members', async ({ page }) => {
|
||||
await page.goto('/typebots')
|
||||
await page.click('text=Settings & Members')
|
||||
await page.click('text="Members"')
|
||||
await expect(page.locator('text="pro-user@email.com"')).toBeVisible()
|
||||
await expect(page.locator('button >> text="Invite"')).toBeEnabled()
|
||||
await page.fill(
|
||||
'input[placeholder="colleague@company.com"]',
|
||||
'guest@email.com'
|
||||
)
|
||||
await page.click('button >> text="Invite"')
|
||||
await expect(page.locator('button >> text="Invite"')).toBeEnabled()
|
||||
await expect(
|
||||
page.locator('input[placeholder="colleague@company.com"]')
|
||||
).toHaveAttribute('value', '')
|
||||
await expect(page.locator('text="guest@email.com"')).toBeVisible()
|
||||
await expect(page.locator('text="Pending"')).toBeVisible()
|
||||
await page.fill(
|
||||
'input[placeholder="colleague@company.com"]',
|
||||
'free-user@email.com'
|
||||
)
|
||||
await page.click('text="Member" >> nth=0')
|
||||
await page.click('text="Admin"')
|
||||
await page.click('button >> text="Invite"')
|
||||
await expect(
|
||||
page.locator('input[placeholder="colleague@company.com"]')
|
||||
).toHaveAttribute('value', '')
|
||||
await expect(page.locator('text="free-user@email.com"')).toBeVisible()
|
||||
await expect(page.locator('text="Free user"')).toBeVisible()
|
||||
|
||||
// Downgrade admin to member
|
||||
await page.click('text="free-user@email.com"')
|
||||
await page.click('button >> text="Member"')
|
||||
await expect(page.locator('[data-testid="tag"] >> text="Admin"')).toHaveCount(
|
||||
1
|
||||
)
|
||||
await page.click('text="free-user@email.com"')
|
||||
await page.click('button >> text="Remove"')
|
||||
await expect(page.locator('text="free-user@email.com"')).toBeHidden()
|
||||
|
||||
await page.click('text="guest@email.com"')
|
||||
await page.click('text="Admin" >> nth=-1')
|
||||
await expect(page.locator('[data-testid="tag"] >> text="Admin"')).toHaveCount(
|
||||
2
|
||||
)
|
||||
await page.click('text="guest@email.com"')
|
||||
await page.click('button >> text="Remove"')
|
||||
await expect(page.locator('text="guest@email.com"')).toBeHidden()
|
||||
})
|
||||
|
||||
test("can't edit workspace as a member", async ({ page }) => {
|
||||
await page.goto('/typebots')
|
||||
await page.click("text=Pro user's workspace")
|
||||
await page.click('text=Shared workspace')
|
||||
await page.click('text=Settings & Members')
|
||||
await expect(page.locator('text="Settings"')).toBeHidden()
|
||||
await page.click('text="Members"')
|
||||
await expect(page.locator('text="free-user@email.com"')).toBeVisible()
|
||||
await expect(
|
||||
page.locator('input[placeholder="colleague@company.com"]')
|
||||
).toBeHidden()
|
||||
await page.click('text="free-user@email.com"')
|
||||
await expect(page.locator('button >> text="Remove"')).toBeHidden()
|
||||
})
|
Reference in New Issue
Block a user