2
0

Add usage-based new pricing plans

This commit is contained in:
Baptiste Arnaud
2022-09-17 16:37:33 +02:00
committed by Baptiste Arnaud
parent 6a1eaea700
commit 898367a33b
144 changed files with 4631 additions and 1624 deletions

View File

@ -0,0 +1,15 @@
{
"cookies": [],
"origins": [
{
"origin": "http://localhost:3000",
"localStorage": [
{
"name": "typebot-20-modal",
"value": "hide"
},
{ "name": "workspaceId", "value": "proWorkspace" }
]
}
]
}

View File

@ -1,19 +0,0 @@
{
"cookies": [],
"origins": [
{
"origin": "http://localhost:3000",
"localStorage": [
{
"name": "authenticatedUser",
"value": "{\"id\":\"freeUser\",\"name\":\"Free user\",\"email\":\"free-user@email.com\",\"emailVerified\":null,\"image\":\"https://avatars.githubusercontent.com/u/16015833?v=4\",\"plan\":\"FREE\",\"stripeId\":null,\"graphNavigation\": \"TRACKPAD\"}"
},
{
"name": "typebot-20-modal",
"value": "hide"
},
{ "name": "workspaceId", "value": "freeWorkspace" }
]
}
]
}

View File

@ -1,18 +0,0 @@
{
"cookies": [],
"origins": [
{
"origin": "http://localhost:3000",
"localStorage": [
{
"name": "authenticatedUser",
"value": "{\"id\":\"proUser\",\"name\":\"Pro user\",\"email\":\"pro-user@email.com\",\"emailVerified\":null,\"image\":\"https://avatars.githubusercontent.com/u/16015833?v=4\",\"plan\":\"PRO\",\"stripeId\":null,\"graphNavigation\": \"TRACKPAD\"}"
},
{
"name": "typebot-20-modal",
"value": "hide"
}
]
}
]
}

View File

@ -0,0 +1,15 @@
{
"cookies": [],
"origins": [
{
"origin": "http://localhost:3000",
"localStorage": [
{
"name": "typebot-20-modal",
"value": "hide"
},
{ "name": "workspaceId", "value": "freeWorkspace" }
]
}
]
}

View File

@ -6,12 +6,12 @@ export const refreshUser = async () => {
document.dispatchEvent(event)
}
export const mockSessionApiCalls = (page: Page) =>
export const connectedAsOtherUser = async (page: Page) =>
page.route('/api/auth/session', (route) => {
if (route.request().method() === 'GET') {
return route.fulfill({
status: 200,
body: '{"user":{"id":"proUser","name":"Pro user","email":"pro-user@email.com","emailVerified":null,"image":"https://avatars.githubusercontent.com/u/16015833?v=4","stripeId":null,"graphNavigation": "TRACKPAD"}}',
body: '{"user":{"id":"otherUserId","name":"James Doe","email":"other-user@email.com","emailVerified":null,"image":"https://avatars.githubusercontent.com/u/16015833?v=4","stripeId":null,"graphNavigation": "TRACKPAD"}}',
})
}
return route.continue()

View File

@ -15,52 +15,129 @@ import {
PrismaClient,
User,
WorkspaceRole,
Workspace,
} from 'db'
import { readFileSync } from 'fs'
import { encrypt } from 'utils'
import { encrypt, createFakeResults } from 'utils'
import Stripe from 'stripe'
const prisma = new PrismaClient()
const proWorkspaceId = 'proWorkspace'
const stripe = new Stripe(process.env.STRIPE_TEST_SECRET_KEY ?? '', {
apiVersion: '2022-08-01',
})
const userId = 'userId'
const otherUserId = 'otherUserId'
export const freeWorkspaceId = 'freeWorkspace'
export const sharedWorkspaceId = 'sharedWorkspace'
export const guestWorkspaceId = 'guestWorkspace'
export const starterWorkspaceId = 'starterWorkspace'
export const proWorkspaceId = 'proWorkspace'
const lifetimeWorkspaceId = 'lifetimeWorkspaceId'
export const teardownDatabase = async () => {
const ownerFilter = {
where: {
workspace: {
members: { some: { userId: { in: ['freeUser', 'proUser'] } } },
},
},
}
await prisma.workspace.deleteMany({
where: {
members: {
some: { userId: { in: ['freeUser', 'proUser'] } },
some: { userId: { in: [userId, otherUserId] } },
},
},
})
await prisma.user.deleteMany({
where: { id: { in: ['freeUser', 'proUser'] } },
where: { id: { in: [userId, otherUserId] } },
})
return prisma.webhook.deleteMany()
}
export const addSubscriptionToWorkspace = async (
workspaceId: string,
items: Stripe.SubscriptionCreateParams.Item[],
metadata: Pick<
Workspace,
'additionalChatsIndex' | 'additionalStorageIndex' | 'plan'
>
) => {
const { id: stripeId } = await stripe.customers.create({
email: 'test-user@gmail.com',
name: 'Test User',
})
const { id: paymentId } = await stripe.paymentMethods.create({
card: {
number: '4242424242424242',
exp_month: 12,
exp_year: 2022,
cvc: '123',
},
type: 'card',
})
await stripe.paymentMethods.attach(paymentId, { customer: stripeId })
await stripe.subscriptions.create({
customer: stripeId,
items,
default_payment_method: paymentId,
currency: 'usd',
})
await prisma.workspace.update({
where: { id: workspaceId },
data: {
stripeId,
...metadata,
},
})
await prisma.webhook.deleteMany()
await prisma.credentials.deleteMany(ownerFilter)
await prisma.dashboardFolder.deleteMany(ownerFilter)
return prisma.typebot.deleteMany(ownerFilter)
}
export const setupDatabase = async () => {
await createWorkspaces()
await createUsers()
return createCredentials()
}
export const createWorkspaces = async () =>
prisma.workspace.createMany({
data: [
{
id: freeWorkspaceId,
name: 'Free workspace',
plan: Plan.FREE,
},
{
id: starterWorkspaceId,
name: 'Starter workspace',
stripeId: 'cus_LnPDugJfa18N41',
plan: Plan.STARTER,
},
{
id: proWorkspaceId,
name: 'Pro workspace',
plan: Plan.PRO,
},
{
id: lifetimeWorkspaceId,
name: 'Lifetime workspace',
plan: Plan.LIFETIME,
},
],
})
export const createWorkspace = async (workspace: Partial<Workspace>) => {
const { id: workspaceId } = await prisma.workspace.create({
data: {
name: 'Free workspace',
plan: Plan.FREE,
...workspace,
},
})
await prisma.memberInWorkspace.create({
data: { userId, workspaceId, role: WorkspaceRole.ADMIN },
})
return workspaceId
}
export const createUsers = async () => {
await prisma.user.create({
data: {
id: 'proUser',
email: 'pro-user@email.com',
name: 'Pro user',
id: userId,
email: 'user@email.com',
name: 'John Doe',
graphNavigation: GraphNavigation.TRACKPAD,
apiTokens: {
createMany: {
@ -83,69 +160,34 @@ export const createUsers = async () => {
],
},
},
workspaces: {
create: {
role: WorkspaceRole.ADMIN,
workspace: {
create: {
id: proWorkspaceId,
name: "Pro user's workspace",
plan: Plan.TEAM,
},
},
},
},
},
})
await prisma.user.create({
data: {
id: 'freeUser',
email: 'free-user@email.com',
name: 'Free user',
graphNavigation: GraphNavigation.TRACKPAD,
workspaces: {
create: {
role: WorkspaceRole.ADMIN,
workspace: {
create: {
id: 'free',
name: "Free user's workspace",
plan: Plan.FREE,
},
},
},
},
},
data: { id: otherUserId, email: 'other-user@email.com', name: 'James Doe' },
})
await prisma.workspace.create({
data: {
id: freeWorkspaceId,
name: 'Free Shared workspace',
plan: Plan.FREE,
members: {
createMany: {
data: [
{ role: WorkspaceRole.MEMBER, userId: 'proUser' },
{ role: WorkspaceRole.ADMIN, userId: 'freeUser' },
],
},
return prisma.memberInWorkspace.createMany({
data: [
{
role: WorkspaceRole.ADMIN,
userId,
workspaceId: freeWorkspaceId,
},
},
})
return prisma.workspace.create({
data: {
id: sharedWorkspaceId,
name: 'Shared workspace',
plan: Plan.TEAM,
members: {
createMany: {
data: [
{ role: WorkspaceRole.MEMBER, userId: 'proUser' },
{ role: WorkspaceRole.ADMIN, userId: 'freeUser' },
],
},
{
role: WorkspaceRole.ADMIN,
userId,
workspaceId: starterWorkspaceId,
},
},
{
role: WorkspaceRole.ADMIN,
userId,
workspaceId: proWorkspaceId,
},
{
role: WorkspaceRole.ADMIN,
userId,
workspaceId: lifetimeWorkspaceId,
},
],
})
}
@ -173,12 +215,12 @@ export const getSignedInUser = (email: string) =>
export const createTypebots = async (partialTypebots: Partial<Typebot>[]) => {
await prisma.typebot.createMany({
data: partialTypebots.map(parseTestTypebot) as any[],
data: partialTypebots.map(parseTestTypebot),
})
return prisma.publicTypebot.createMany({
data: partialTypebots.map((t) =>
parseTypebotToPublicTypebot(t.id + '-public', parseTestTypebot(t))
) as any[],
),
})
}
@ -217,43 +259,11 @@ export const updateUser = (data: Partial<User>) =>
prisma.user.update({
data,
where: {
id: 'proUser',
id: userId,
},
})
export const createResults = async ({ typebotId }: { typebotId: string }) => {
await prisma.result.deleteMany()
await prisma.result.createMany({
data: [
...Array.from(Array(200)).map((_, idx) => {
const today = new Date()
const rand = Math.random()
return {
id: `result${idx}`,
typebotId,
createdAt: new Date(
today.setTime(today.getTime() + 1000 * 60 * 60 * 24 * idx)
),
isCompleted: rand > 0.5,
}
}),
],
})
return createAnswers()
}
const createAnswers = () => {
return prisma.answer.createMany({
data: [
...Array.from(Array(200)).map((_, idx) => ({
resultId: `result${idx}`,
content: `content${idx}`,
blockId: 'block1',
groupId: 'block1',
})),
],
})
}
export const createResults = createFakeResults(prisma)
export const createFolder = (workspaceId: string, name: string) =>
prisma.dashboardFolder.create({
@ -352,6 +362,6 @@ export const importTypebotInDatabase = async (
data: parseTypebotToPublicTypebot(
updates?.id ? `${updates?.id}-public` : 'publicBot',
typebot
) as any,
),
})
}

View File

@ -1,10 +1,6 @@
import test, { expect } from '@playwright/test'
import path from 'path'
import { mockSessionApiCalls } from 'playwright/services/browser'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
// 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')

View File

@ -0,0 +1,175 @@
import test, { expect } from '@playwright/test'
import cuid from 'cuid'
import { Plan } from 'db'
import {
addSubscriptionToWorkspace,
createResults,
createTypebots,
createWorkspace,
starterWorkspaceId,
} from '../services/database'
test('should display valid usage', async ({ page }) => {
const starterTypebotId = cuid()
createTypebots([{ id: starterTypebotId, workspaceId: starterWorkspaceId }])
await page.goto('/typebots')
await page.click('text=Settings & Members')
await page.click('text=Billing & Usage')
await expect(page.locator('text="/ 10,000"')).toBeVisible()
await expect(page.locator('text="/ 10 GB"')).toBeVisible()
await page.click('text=Pro workspace', { force: true })
await page.click('text=Pro workspace')
await page.click('text="Free workspace"')
await page.click('text=Settings & Members')
await page.click('text=Billing & Usage')
await expect(page.locator('text="/ 300"')).toBeVisible()
await page.click('text=Free workspace', { force: true })
await createResults({
idPrefix: 'usage',
count: 10,
typebotId: starterTypebotId,
isChronological: false,
fakeStorage: 1100 * 1024 * 1024,
})
await page.click('text=Free workspace')
await page.click('text="Starter workspace"')
await page.click('text=Settings & Members')
await page.click('text=Billing & Usage')
await expect(page.locator('text="/ 2,000"')).toBeVisible()
await expect(page.locator('text="/ 2 GB"')).toBeVisible()
await expect(page.locator('text="1.07 GB"')).toBeVisible()
await expect(page.locator('text="200"')).toBeVisible()
await expect(page.locator('[role="progressbar"] >> nth=0')).toHaveAttribute(
'aria-valuenow',
'10'
)
await expect(page.locator('[role="progressbar"] >> nth=1')).toHaveAttribute(
'aria-valuenow',
'54'
)
await createResults({
idPrefix: 'usage2',
typebotId: starterTypebotId,
isChronological: false,
count: 900,
fakeStorage: 1200 * 1024 * 1024,
})
await page.click('text="Settings"')
await page.click('text="Billing & Usage"')
await expect(page.locator('text="/ 2,000"')).toBeVisible()
await expect(page.locator('text="1,100"')).toBeVisible()
await expect(page.locator('text="/ 2 GB"')).toBeVisible()
await expect(page.locator('text="2.25 GB"')).toBeVisible()
await expect(page.locator('[aria-valuenow="55"]')).toBeVisible()
await expect(page.locator('[aria-valuenow="112"]')).toBeVisible()
})
test('plan changes should work', async ({ page }) => {
const workspaceId = await createWorkspace({ name: 'Awesome workspace' })
// Upgrade to STARTER
await page.goto('/typebots')
await page.click('text=Pro workspace')
await page.click('text=Awesome workspace')
await page.click('text=Settings & Members')
await page.click('text=Billing & Usage')
await page.click('button >> text="2,000"')
await page.click('button >> text="3,500"')
await page.click('button >> text="2"')
await page.click('button >> text="4"')
await expect(page.locator('text="$73"')).toBeVisible()
await page.click('button >> text=Upgrade >> nth=0')
await page.waitForNavigation()
expect(page.url()).toContain('https://checkout.stripe.com')
await expect(page.locator('text=$73.00 >> nth=0')).toBeVisible()
await expect(page.locator('text=$30.00 >> nth=0')).toBeVisible()
await expect(page.locator('text=$4.00 >> nth=0')).toBeVisible()
await expect(page.locator('text=user@email.com')).toBeVisible()
await addSubscriptionToWorkspace(
workspaceId,
[
{
price: process.env.STRIPE_STARTER_PRICE_ID,
quantity: 1,
},
{
price: process.env.STRIPE_ADDITIONAL_CHATS_PRICE_ID,
quantity: 3,
},
{
price: process.env.STRIPE_ADDITIONAL_STORAGE_PRICE_ID,
quantity: 2,
},
],
{ plan: Plan.STARTER, additionalChatsIndex: 3, additionalStorageIndex: 2 }
)
// Update plan with additional quotas
await page.goto('/typebots')
await page.click('text=Settings & Members')
await page.click('text=Billing & Usage')
await expect(page.locator('text="/ 3,500"')).toBeVisible()
await expect(page.locator('text="/ 4 GB"')).toBeVisible()
await expect(page.locator('button >> text="3,500"')).toBeVisible()
await expect(page.locator('button >> text="4"')).toBeVisible()
await expect(page.locator('text="$73"')).toBeVisible()
await page.click('button >> text="3,500"')
await page.click('button >> text="2,000"')
await page.click('button >> text="4"')
await page.click('button >> text="6"')
await expect(page.locator('text="$47"')).toBeVisible()
await page.click('button >> text=Update')
await expect(
page.locator(
'text="Workspace STARTER plan successfully updated 🎉" >> nth=0'
)
).toBeVisible()
// Upgrade to PRO
await page.click('button >> text="10,000"')
await page.click('button >> text="14,000"')
await page.click('button >> text="10"')
await page.click('button >> text="12"')
await expect(page.locator('text="$133"')).toBeVisible()
await page.click('button >> text=Upgrade')
await expect(
page.locator('text="Workspace PRO plan successfully updated 🎉" >> nth=0')
).toBeVisible()
// Go to customer portal
await Promise.all([
page.waitForNavigation(),
page.click('text="Billing Portal"'),
])
await expect(page.locator('text="Add payment method"')).toBeVisible()
// Cancel subscription
await page.goto('/typebots')
await page.click('text=Settings & Members')
await page.click('text=Billing & Usage')
await expect(page.locator('[data-testid="plan-tag"]')).toHaveText('Pro')
await page.click('button >> text="Cancel my subscription"')
await expect(page.locator('[data-testid="plan-tag"]')).toHaveText('Free')
})
test('should display invoices', async ({ page }) => {
await page.goto('/typebots')
await page.click('text=Settings & Members')
await page.click('text=Billing & Usage')
await expect(
page.locator('text="No invoices found for this workspace."')
).toBeVisible()
await page.click('text=Pro workspace', { force: true })
await page.click('text=Pro workspace')
await page.click('text=Starter workspace')
await page.click('text=Settings & Members')
await page.click('text=Billing & Usage')
await expect(page.locator('text="Invoices"')).toBeVisible()
await expect(page.locator('text="Wed Jun 01 2022"')).toBeVisible()
await expect(page.locator('text="74567541-0001"')).toBeVisible()
await expect(page.locator('text="€30.00" >> nth=0')).toBeVisible()
})

View File

@ -6,13 +6,10 @@ import {
import { BubbleBlockType, defaultEmbedBubbleContent } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
const pdfSrc = 'https://www.orimi.com/pdf-test.pdf'
const siteSrc = 'https://app.cal.com/baptistearno/15min'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe.parallel('Embed bubble block', () => {
test.describe('Content settings', () => {
test('should import and parse embed correctly', async ({ page }) => {

View File

@ -7,13 +7,10 @@ import { BubbleBlockType, defaultImageBubbleContent } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import path from 'path'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
const unsplashImageSrc =
'https://images.unsplash.com/photo-1504297050568-910d24c426d3?ixlib=rb-1.2.1&ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe.parallel('Image bubble block', () => {
test.describe('Content settings', () => {
test('should upload image file correctly', async ({ page }) => {

View File

@ -6,9 +6,6 @@ import {
import { BubbleBlockType, defaultTextBubbleContent } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe('Text bubble block', () => {
test('rich text features should work', async ({ page }) => {

View File

@ -10,15 +10,12 @@ import {
} from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
const videoSrc =
'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4'
const youtubeVideoSrc = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
const vimeoVideoSrc = 'https://vimeo.com/649301125'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe.parallel('Video bubble block', () => {
test.describe('Content settings', () => {
test('should import video url correctly', async ({ page }) => {

View File

@ -3,7 +3,6 @@ import cuid from 'cuid'
import { CollaborationType, Plan, WorkspaceRole } from 'db'
import prisma from 'libs/prisma'
import { InputBlockType, defaultTextInputOptions } from 'models'
import { mockSessionApiCalls } from 'playwright/services/browser'
import {
createFolder,
createResults,
@ -11,8 +10,6 @@ import {
parseDefaultGroupWithBlock,
} from '../services/database'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe('Typebot owner', () => {
test('Can invite collaborators', async ({ page }) => {
const typebotId = cuid()
@ -101,7 +98,7 @@ test.describe('Collaborator', () => {
},
})
await createFolder(guestWorkspaceId, 'Guest folder')
await createResults({ typebotId })
await createResults({ typebotId, count: 10 })
await page.goto(`/typebots`)
await page.click("text=Pro user's workspace")
await page.click('text=Guest workspace #2')

View File

@ -2,14 +2,10 @@ import test, { expect } from '@playwright/test'
import { InputBlockType, defaultTextInputOptions } from 'models'
import {
createTypebots,
freeWorkspaceId,
parseDefaultGroupWithBlock,
starterWorkspaceId,
} from '../services/database'
import path from 'path'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test('should be able to connect custom domain', async ({ page }) => {
const typebotId = cuid()
@ -47,16 +43,13 @@ test('should be able to connect custom domain', async ({ page }) => {
await expect(page.locator('[aria-label="Remove domain"]')).toBeHidden()
})
test.describe('Free workspace', () => {
test.use({
storageState: path.join(__dirname, '../freeUser.json'),
})
test.describe('Starter workspace', () => {
test("Add my domain shouldn't be available", async ({ page }) => {
const typebotId = cuid()
await createTypebots([
{
id: typebotId,
workspaceId: freeWorkspaceId,
workspaceId: starterWorkspaceId,
...parseDefaultGroupWithBlock({
type: InputBlockType.TEXT,
options: defaultTextInputOptions,

View File

@ -1,12 +1,9 @@
import test, { expect, Page } from '@playwright/test'
import cuid from 'cuid'
import path from 'path'
import { mockSessionApiCalls } from 'playwright/services/browser'
import { createFolders, createTypebots } from '../services/database'
import { deleteButtonInConfirmDialog } from '../services/selectorUtils'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe('Dashboard page', () => {
test('folders navigation should work', async ({ page }) => {
await page.goto('/typebots')
@ -79,7 +76,7 @@ test.describe('Dashboard page', () => {
test.describe('Free user', () => {
test.use({
storageState: path.join(__dirname, '../freeUser.json'),
storageState: path.join(__dirname, '../secondUser.json'),
})
test("create folder shouldn't be available", async ({ page }) => {
await page.goto('/typebots')

View File

@ -8,9 +8,6 @@ import { defaultTextInputOptions, InputBlockType } from 'models'
import path from 'path'
import cuid from 'cuid'
import { typebotViewer } from '../services/selectorUtils'
import { mockSessionApiCalls } from 'playwright/services/browser'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe.parallel('Editor', () => {
test('Edges connection should work', async ({ page }) => {

View File

@ -8,9 +8,6 @@ import { defaultChoiceInputOptions, InputBlockType, ItemType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import cuid from 'cuid'
import path from 'path'
import { mockSessionApiCalls } from 'playwright/services/browser'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe.parallel('Buttons input block', () => {
test('can edit button items', async ({ page }) => {

View File

@ -6,9 +6,6 @@ import {
import { defaultDateInputOptions, InputBlockType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe('Date input block', () => {
test('options should work', async ({ page }) => {

View File

@ -6,9 +6,6 @@ import {
import { defaultEmailInputOptions, InputBlockType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe('Email input block', () => {
test('options should work', async ({ page }) => {

View File

@ -8,9 +8,6 @@ import { defaultFileInputOptions, InputBlockType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import cuid from 'cuid'
import path from 'path'
import { mockSessionApiCalls } from 'playwright/services/browser'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe.configure({ mode: 'parallel' })
@ -61,9 +58,6 @@ test('options should work', async ({ page }) => {
})
test.describe('Free workspace', () => {
test.use({
storageState: path.join(__dirname, '../../freeUser.json'),
})
test("shouldn't be able to publish typebot", async ({ page }) => {
const typebotId = cuid()
await createTypebots([

View File

@ -6,9 +6,6 @@ import {
import { defaultNumberInputOptions, InputBlockType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe('Number input block', () => {
test('options should work', async ({ page }) => {

View File

@ -6,9 +6,6 @@ import {
import { defaultPaymentInputOptions, InputBlockType } from 'models'
import cuid from 'cuid'
import { stripePaymentForm, typebotViewer } from '../../services/selectorUtils'
import { mockSessionApiCalls } from 'playwright/services/browser'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe('Payment input block', () => {
test('Can configure Stripe account', async ({ page }) => {

View File

@ -6,9 +6,6 @@ import {
import { defaultPhoneInputOptions, InputBlockType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe('Phone input block', () => {
test('options should work', async ({ page }) => {

View File

@ -6,7 +6,6 @@ import {
import { defaultRatingInputOptions, InputBlockType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
const boxSvg = `<svg
xmlns="http://www.w3.org/2000/svg"
@ -21,8 +20,6 @@ const boxSvg = `<svg
<line x1="12" y1="22.08" x2="12" y2="12"></line>
</svg>`
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test('options should work', async ({ page }) => {
const typebotId = cuid()
await createTypebots([

View File

@ -6,9 +6,6 @@ import {
import { defaultTextInputOptions, InputBlockType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe.parallel('Text input block', () => {
test('options should work', async ({ page }) => {

View File

@ -6,9 +6,6 @@ import {
import { defaultUrlInputOptions, InputBlockType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe('Url input block', () => {
test('options should work', async ({ page }) => {

View File

@ -5,9 +5,6 @@ import {
} from '../../services/database'
import { defaultGoogleAnalyticsOptions, IntegrationBlockType } from 'models'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe('Google Analytics block', () => {
test('its configuration should work', async ({ page }) => {

View File

@ -3,9 +3,6 @@ import { importTypebotInDatabase } from '../../services/database'
import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe.parallel('Google sheets integration', () => {
test('Insert row should work', async ({ page }) => {

View File

@ -3,12 +3,9 @@ import { importTypebotInDatabase } from '../../services/database'
import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
const typebotId = cuid()
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe('Send email block', () => {
test('its configuration should work', async ({ page }) => {
if (

View File

@ -3,9 +3,6 @@ import { createWebhook, importTypebotInDatabase } from '../../services/database'
import path from 'path'
import { HttpMethod } from 'models'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe('Webhook block', () => {
test('easy configuration should work', async ({ page }) => {

View File

@ -3,12 +3,9 @@ import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
import { importTypebotInDatabase } from '../../services/database'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
const typebotId = cuid()
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe('Code block', () => {
test('code should trigger', async ({ page }) => {
await importTypebotInDatabase(

View File

@ -3,12 +3,9 @@ import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
import { importTypebotInDatabase } from '../../services/database'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
const typebotId = cuid()
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe('Condition block', () => {
test('its configuration should work', async ({ page }) => {
await importTypebotInDatabase(

View File

@ -3,12 +3,9 @@ import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
import { importTypebotInDatabase } from '../../services/database'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
const typebotId = cuid()
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe('Redirect block', () => {
test('its configuration should work', async ({ page, context }) => {
await importTypebotInDatabase(

View File

@ -3,12 +3,9 @@ import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
import { importTypebotInDatabase } from '../../services/database'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
const typebotId = cuid()
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe('Set variable block', () => {
test('its configuration should work', async ({ page }) => {
await importTypebotInDatabase(

View File

@ -3,9 +3,6 @@ import { typebotViewer } from '../../services/selectorUtils'
import { importTypebotInDatabase } from '../../services/database'
import path from 'path'
import cuid from 'cuid'
import { mockSessionApiCalls } from 'playwright/services/browser'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test('should be configurable', async ({ page }) => {
const typebotId = cuid()

View File

@ -4,7 +4,6 @@ import { readFileSync } from 'fs'
import { defaultTextInputOptions, InputBlockType } from 'models'
import { parse } from 'papaparse'
import path from 'path'
import { mockSessionApiCalls } from 'playwright/services/browser'
import {
createResults,
createTypebots,
@ -15,8 +14,6 @@ import { deleteButtonInConfirmDialog } from '../services/selectorUtils'
const typebotId = cuid()
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test('Submission table header should be parsed correctly', async ({ page }) => {
const typebotId = cuid()
await importTypebotInDatabase(
@ -46,7 +43,7 @@ test('results should be deletable', async ({ page }) => {
}),
},
])
await createResults({ typebotId })
await createResults({ typebotId, count: 200 })
await page.goto(`/typebots/${typebotId}/results`)
await selectFirstResults(page)
await page.click('text="Delete"')
@ -70,7 +67,7 @@ test('submissions table should have infinite scroll', async ({ page }) => {
tableWrapper.scrollTo(0, tableWrapper.scrollHeight)
})
await createResults({ typebotId })
await createResults({ typebotId, count: 200 })
await page.goto(`/typebots/${typebotId}/results`)
await expect(page.locator('text=content199')).toBeVisible()

View File

@ -2,12 +2,9 @@ import test, { expect } from '@playwright/test'
import cuid from 'cuid'
import { defaultTextInputOptions } from 'models'
import path from 'path'
import { mockSessionApiCalls } from 'playwright/services/browser'
import { freeWorkspaceId, importTypebotInDatabase } from '../services/database'
import { typebotViewer } from '../services/selectorUtils'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe.parallel('Settings page', () => {
test.describe('General', () => {
test('should reflect change in real-time', async ({ page }) => {
@ -123,10 +120,7 @@ test.describe.parallel('Settings page', () => {
})
})
test.describe('Free user', () => {
test.use({
storageState: path.join(__dirname, '../freeUser.json'),
})
test.describe('Free workspace', () => {
test("can't remove branding", async ({ page }) => {
const typebotId = 'free-branding-typebot'
await importTypebotInDatabase(

View File

@ -1,10 +1,7 @@
import test, { expect } from '@playwright/test'
import path from 'path'
import { mockSessionApiCalls } from 'playwright/services/browser'
import { typebotViewer } from '../services/selectorUtils'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe.parallel('Templates page', () => {
test('From scratch should create a blank typebot', async ({ page }) => {
await page.goto('/typebots/create')

View File

@ -1,7 +1,6 @@
import test, { expect } from '@playwright/test'
import cuid from 'cuid'
import path from 'path'
import { mockSessionApiCalls } from 'playwright/services/browser'
import { importTypebotInDatabase } from '../services/database'
import { typebotViewer } from '../services/selectorUtils'
@ -10,8 +9,6 @@ const hostAvatarUrl =
const guestAvatarUrl =
'https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80'
test.beforeEach(({ page }) => mockSessionApiCalls(page))
test.describe.parallel('Theme page', () => {
test.describe('General', () => {
test('should reflect change in real-time', async ({ page }) => {

View File

@ -1,25 +1,30 @@
import test, { expect } from '@playwright/test'
import cuid from 'cuid'
import { defaultTextInputOptions, InputBlockType } from 'models'
import { mockSessionApiCalls } from 'playwright/services/browser'
import { connectedAsOtherUser } from 'playwright/services/browser'
import {
createTypebots,
parseDefaultGroupWithBlock,
sharedWorkspaceId,
proWorkspaceId,
starterWorkspaceId,
} from '../services/database'
const proTypebotId = cuid()
const freeTypebotId = cuid()
test.beforeEach(({ page }) => mockSessionApiCalls(page))
const starterTypebotId = cuid()
test.beforeAll(async () => {
await createTypebots([{ id: proTypebotId, name: 'Pro typebot' }])
await createTypebots([
{
id: freeTypebotId,
name: 'Shared typebot',
workspaceId: sharedWorkspaceId,
id: proTypebotId,
name: 'Pro typebot',
workspaceId: proWorkspaceId,
},
])
await createTypebots([
{
id: starterTypebotId,
name: 'Starter typebot',
workspaceId: starterWorkspaceId,
...parseDefaultGroupWithBlock({
type: InputBlockType.TEXT,
options: {
@ -37,38 +42,34 @@ test.beforeAll(async () => {
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 page.click('text=Pro workspace')
await page.click('text="Starter workspace"')
await expect(page.locator('text="Pro typebot"')).toBeHidden()
await page.click('text="Shared typebot"')
await page.click('text="Starter typebot"')
await expect(page.locator('text="Hey there"')).toBeVisible()
})
test('can create and delete 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=Pro workspace')
await expect(page.locator('text="Pro 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()
await page.click("text=John Doe's workspace")
await expect(page.locator('text="Pro workspace"')).toBeVisible()
await page.click('text=Settings & Members')
await page.click('text="Settings"')
await page.click('text="Delete workspace"')
await expect(
page.locator(
"text=Are you sure you want to delete Pro user's workspace workspace?"
"text=Are you sure you want to delete John Doe's workspace workspace?"
)
).toBeVisible()
await page.click('text="Delete"')
await expect(page.locator('text=Pro typebot')).toBeVisible()
await page.click("text=Pro user's workspace")
await expect(page.locator('text=Free workspace')).toBeVisible()
await page.click('text=Free workspace')
await expect(
page.locator('text="Pro user\'s workspace" >> nth=1')
page.locator('text="John Doe\'s workspace" >> nth=1')
).toBeHidden()
})
@ -79,17 +80,14 @@ test('can update workspace info', async ({ page }) => {
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'
)
await page.fill('input[value="Pro 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('text="user@email.com"')).toBeVisible()
await expect(page.locator('button >> text="Invite"')).toBeEnabled()
await page.fill(
'input[placeholder="colleague@company.com"]',
@ -104,7 +102,7 @@ test('can manage members', async ({ page }) => {
await expect(page.locator('text="Pending"')).toBeVisible()
await page.fill(
'input[placeholder="colleague@company.com"]',
'free-user@email.com'
'other-user@email.com'
)
await page.click('text="Member" >> nth=0')
await page.click('text="Admin"')
@ -112,18 +110,15 @@ test('can manage members', async ({ page }) => {
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()
await expect(page.locator('text="other-user@email.com"')).toBeVisible()
await expect(page.locator('text="James Doe"')).toBeVisible()
// Downgrade admin to member
await page.click('text="free-user@email.com"')
await page.click('text="other-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="other-user@email.com"')
await page.click('text="guest@email.com"')
await page.click('text="Admin" >> nth=-1')
@ -133,19 +128,46 @@ test('can manage members', async ({ page }) => {
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 connectedAsOtherUser(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('text="other-user@email.com"')).toBeVisible()
await expect(
page.locator('input[placeholder="colleague@company.com"]')
).toBeHidden()
await page.click('text="free-user@email.com"')
await page.click('text="other-user@email.com"')
await expect(page.locator('button >> text="Remove"')).toBeHidden()
})
test("can't add new members when limit is reached", async ({ page }) => {
await page.goto('/typebots')
await page.click('text="Pro workspace"')
await page.click('text="Free workspace"')
await page.click('text=Settings & Members')
await page.click('text="Members"')
await expect(page.locator('button >> text="Invite"')).toBeDisabled()
await expect(
page.locator(
'text="Upgrade your plan to work with more team members, and unlock awesome power features 🚀"'
)
).toBeVisible()
await page.click('text="Free workspace"', { force: true })
await page.click('text="Free workspace"')
await page.click('text="Starter workspace"')
await page.click('text=Settings & Members')
await page.click('text="Members"')
await page.fill(
'input[placeholder="colleague@company.com"]',
'guest@email.com'
)
await page.click('button >> text="Invite"')
await expect(
page.locator(
'text="Upgrade your plan to work with more team members, and unlock awesome power features 🚀"'
)
).toBeVisible()
await expect(page.locator('button >> text="Invite"')).toBeDisabled()
})