2
0

refactor(editor): ♻️ Undo / Redo buttons + structure refacto

Yet another huge refacto... While implementing undo and redo features I understood that I updated the stored typebot too many times (i.e. on each key input) so I had to rethink it entirely. I also moved around some files.
This commit is contained in:
Baptiste Arnaud
2022-02-02 08:05:02 +01:00
parent fc1d654772
commit 8a350eee6c
153 changed files with 1512 additions and 1352 deletions

View File

@ -1,88 +1,95 @@
{
"id": "typebot4",
"createdAt": "2022-01-21T07:55:14.727Z",
"updatedAt": "2022-01-21T07:55:14.727Z",
"name": "My typebot",
"ownerId": "user2",
"id": "ckz478ggv1144eo1a5euf9twl",
"createdAt": "2022-02-01T14:11:20.287Z",
"updatedAt": "2022-02-01T14:11:20.286Z",
"name": "Webhook",
"ownerId": "ckz478eaj1091eo1amyo1me1z",
"publishedTypebotId": null,
"folderId": null,
"webhooks": { "byId": {}, "allIds": [] },
"blocks": {
"byId": {
"3kH2sUjVThQDWmqdoKnGk5": {
"id": "3kH2sUjVThQDWmqdoKnGk5",
"q7gjzJu7wBFycca5dNvZek": {
"id": "q7gjzJu7wBFycca5dNvZek",
"title": "Start",
"stepIds": ["oxTsU2C1RX5QHuyY8qjHAM"],
"graphCoordinates": { "x": 42, "y": 13 }
"stepIds": ["da1KErxMzczHwaM25vQtFP"],
"graphCoordinates": { "x": 0, "y": 0 }
},
"b9mSgu7RKmK4xuiTVQP5Me8": {
"id": "b9mSgu7RKmK4xuiTVQP5Me8",
"title": "Block #3",
"stepIds": ["ssLd2wjExS9qWRur4tZDU1Z"],
"graphCoordinates": { "x": 300, "y": 550 }
"bUUqjxyAFZkKzjByqEaEzV": {
"id": "bUUqjxyAFZkKzjByqEaEzV",
"graphCoordinates": { "x": 248, "y": 247 },
"title": "Block 1",
"stepIds": ["siAj9x5LZ8W5cviqznX82T3", "s5GToHCtqZhwpygDuTb3tu4"]
},
"bdFW2HHjMoEFmqHtFre9Xi8": {
"id": "bdFW2HHjMoEFmqHtFre9Xi8",
"title": "Block #2",
"stepIds": ["sgkADMK25y9P9V3vjwjBaac", "ssEiEECKSFkA44dGDceHxKw"],
"graphCoordinates": { "x": 121, "y": 227 }
"ifpYvoBnYU2X3B3RgwfeNJ": {
"id": "ifpYvoBnYU2X3B3RgwfeNJ",
"graphCoordinates": { "x": 690, "y": 504 },
"title": "Block 2",
"stepIds": ["sjDhaBWVLd2Ep7N3WryJGQJ"]
}
},
"allIds": [
"3kH2sUjVThQDWmqdoKnGk5",
"bdFW2HHjMoEFmqHtFre9Xi8",
"b9mSgu7RKmK4xuiTVQP5Me8"
"q7gjzJu7wBFycca5dNvZek",
"bUUqjxyAFZkKzjByqEaEzV",
"ifpYvoBnYU2X3B3RgwfeNJ"
]
},
"steps": {
"byId": {
"oxTsU2C1RX5QHuyY8qjHAM": {
"id": "oxTsU2C1RX5QHuyY8qjHAM",
"da1KErxMzczHwaM25vQtFP": {
"id": "da1KErxMzczHwaM25vQtFP",
"type": "start",
"label": "Start",
"edgeId": "25yX9DnQgdafpdAjfAu5Fp",
"blockId": "3kH2sUjVThQDWmqdoKnGk5"
"blockId": "q7gjzJu7wBFycca5dNvZek",
"edgeId": "mcxdssnDkbvJBZ6d51XDey"
},
"sgkADMK25y9P9V3vjwjBaac": {
"id": "sgkADMK25y9P9V3vjwjBaac",
"s5GToHCtqZhwpygDuTb3tu4": {
"id": "s5GToHCtqZhwpygDuTb3tu4",
"blockId": "bUUqjxyAFZkKzjByqEaEzV",
"type": "choice input",
"options": {
"buttonLabel": "Send",
"isMultipleChoice": false,
"itemIds": ["ddSjZkft27gQnZAEeXtQny"]
}
},
"siAj9x5LZ8W5cviqznX82T3": {
"id": "siAj9x5LZ8W5cviqznX82T3",
"blockId": "bUUqjxyAFZkKzjByqEaEzV",
"type": "text",
"blockId": "bdFW2HHjMoEFmqHtFre9Xi8",
"content": {
"html": "<div>Ready?</div>",
"richText": [{ "type": "p", "children": [{ "text": "Ready?" }] }],
"plainText": "Ready?"
}
},
"ssEiEECKSFkA44dGDceHxKw": {
"id": "ssEiEECKSFkA44dGDceHxKw",
"type": "choice input",
"edgeId": "oxEEtym3NfDf34NCipzjRQ",
"blockId": "bdFW2HHjMoEFmqHtFre9Xi8",
"options": { "itemIds": ["q69Ex7LacPrH9QUMeosRnB"] }
},
"ssLd2wjExS9qWRur4tZDU1Z": {
"id": "ssLd2wjExS9qWRur4tZDU1Z",
"sjDhaBWVLd2Ep7N3WryJGQJ": {
"id": "sjDhaBWVLd2Ep7N3WryJGQJ",
"blockId": "ifpYvoBnYU2X3B3RgwfeNJ",
"type": "Webhook",
"blockId": "b9mSgu7RKmK4xuiTVQP5Me8",
"options": { "webhookId": "4h4Kk3Q1qGy7gFzpZtWVpU" }
"options": {
"responseVariableMapping": { "byId": {}, "allIds": [] },
"variablesForTest": { "byId": {}, "allIds": [] },
"webhookId": "3nxQGoMMXpA6K5iuhGFW5S"
}
}
},
"allIds": [
"oxTsU2C1RX5QHuyY8qjHAM",
"sgkADMK25y9P9V3vjwjBaac",
"ssEiEECKSFkA44dGDceHxKw",
"ssLd2wjExS9qWRur4tZDU1Z"
"da1KErxMzczHwaM25vQtFP",
"s5GToHCtqZhwpygDuTb3tu4",
"siAj9x5LZ8W5cviqznX82T3",
"sjDhaBWVLd2Ep7N3WryJGQJ"
]
},
"choiceItems": {
"byId": {
"q69Ex7LacPrH9QUMeosRnB": {
"id": "q69Ex7LacPrH9QUMeosRnB",
"stepId": "ssEiEECKSFkA44dGDceHxKw",
"content": "Go"
"ddSjZkft27gQnZAEeXtQny": {
"id": "ddSjZkft27gQnZAEeXtQny",
"stepId": "s5GToHCtqZhwpygDuTb3tu4",
"content": "Go",
"edgeId": "x6cbRGrLAVYy4ymAg5tfp9"
}
},
"allIds": ["q69Ex7LacPrH9QUMeosRnB"]
"allIds": ["ddSjZkft27gQnZAEeXtQny"]
},
"variables": {
"byId": {
@ -112,30 +119,36 @@
},
"webhooks": {
"byId": {
"4h4Kk3Q1qGy7gFzpZtWVpU": { "id": "4h4Kk3Q1qGy7gFzpZtWVpU", "url": "" }
"3nxQGoMMXpA6K5iuhGFW5S": {
"id": "3nxQGoMMXpA6K5iuhGFW5S",
"method": "GET",
"headers": { "byId": {}, "allIds": [] },
"queryParams": { "byId": {}, "allIds": [] }
}
},
"allIds": ["4h4Kk3Q1qGy7gFzpZtWVpU"]
"allIds": ["3nxQGoMMXpA6K5iuhGFW5S"]
},
"edges": {
"byId": {
"25yX9DnQgdafpdAjfAu5Fp": {
"id": "25yX9DnQgdafpdAjfAu5Fp",
"to": { "blockId": "bdFW2HHjMoEFmqHtFre9Xi8" },
"mcxdssnDkbvJBZ6d51XDey": {
"from": {
"stepId": "oxTsU2C1RX5QHuyY8qjHAM",
"blockId": "3kH2sUjVThQDWmqdoKnGk5"
}
"blockId": "q7gjzJu7wBFycca5dNvZek",
"stepId": "da1KErxMzczHwaM25vQtFP"
},
"to": { "blockId": "bUUqjxyAFZkKzjByqEaEzV" },
"id": "mcxdssnDkbvJBZ6d51XDey"
},
"oxEEtym3NfDf34NCipzjRQ": {
"id": "oxEEtym3NfDf34NCipzjRQ",
"to": { "blockId": "b9mSgu7RKmK4xuiTVQP5Me8" },
"x6cbRGrLAVYy4ymAg5tfp9": {
"from": {
"stepId": "ssEiEECKSFkA44dGDceHxKw",
"blockId": "bdFW2HHjMoEFmqHtFre9Xi8"
}
"blockId": "bUUqjxyAFZkKzjByqEaEzV",
"stepId": "s5GToHCtqZhwpygDuTb3tu4",
"buttonId": "ddSjZkft27gQnZAEeXtQny"
},
"to": { "blockId": "ifpYvoBnYU2X3B3RgwfeNJ" },
"id": "x6cbRGrLAVYy4ymAg5tfp9"
}
},
"allIds": ["25yX9DnQgdafpdAjfAu5Fp", "oxEEtym3NfDf34NCipzjRQ"]
"allIds": ["mcxdssnDkbvJBZ6d51XDey", "x6cbRGrLAVYy4ymAg5tfp9"]
},
"theme": {
"chat": {

View File

@ -1,5 +1,5 @@
{
"id": "typebot4",
"id": "bdFW2HHjMoEFmqHtFre9Xi8",
"createdAt": "2022-01-21T07:55:14.727Z",
"updatedAt": "2022-01-21T07:55:14.727Z",
"name": "My typebot",

View File

@ -1,5 +1,5 @@
{
"id": "typebot4",
"id": "bdFW2HHjMoEFmqHtFre9Xi8",
"createdAt": "2022-01-21T07:55:14.727Z",
"updatedAt": "2022-01-21T07:55:14.727Z",
"name": "My typebot",

View File

@ -1,4 +1,5 @@
import { chromium, FullConfig, Page } from '@playwright/test'
import { existsSync } from 'fs'
import { setupDatabase, teardownDatabase } from './services/database'
// eslint-disable-next-line @typescript-eslint/no-var-requires
@ -10,12 +11,14 @@ async function globalSetup(config: FullConfig) {
await teardownDatabase()
const browser = await chromium.launch()
const page = await browser.newPage()
await signIn(page)
await page.context().storageState({
path: './playwright/authenticatedState.json',
})
if (!existsSync('./playwright/authenticatedState.json')) {
const browser = await chromium.launch()
const page = await browser.newPage()
await signIn(page)
await page.context().storageState({
path: './playwright/authenticatedState.json',
})
}
await setupDatabase(process.env.GITHUB_EMAIL as string)
}
@ -23,7 +26,7 @@ async function globalSetup(config: FullConfig) {
const signIn = async (page: Page) => {
if (!process.env.GITHUB_EMAIL || !process.env.GITHUB_PASSWORD)
throw new Error(
'GITHUB_USERNAME or GITHUB_PASSWORD are missing in the environment. They are required to log in.'
'GITHUB_EMAIL or GITHUB_PASSWORD are missing in the environment. They are required to log in.'
)
await page.goto(`${process.env.PLAYWRIGHT_BUILDER_TEST_BASE_URL}/signin`)
await page.click('text=Continue with GitHub')

View File

@ -10,7 +10,11 @@ import { readFileSync } from 'fs'
const prisma = new PrismaClient()
export const teardownDatabase = async () => prisma.user.deleteMany()
export const teardownDatabase = async () => {
await prisma.credentials.deleteMany()
await prisma.dashboardFolder.deleteMany()
return prisma.typebot.deleteMany()
}
export const setupDatabase = async (userEmail: string) => {
const createdUser = await getSignedInUser(userEmail)
@ -38,7 +42,6 @@ export const createFolders = (partialFolders: Partial<DashboardFolder>[]) =>
data: partialFolders.map((folder) => ({
ownerId: process.env.PLAYWRIGHT_USER_ID as string,
name: 'Folder #1',
id: 'folder',
...folder,
})),
})
@ -56,7 +59,7 @@ const createCredentials = () =>
'ya29.A0ARrdaM--PV_87ebjywDJpXKb77NBFJl16meVUapYdfNv6W6ZzqqC47fNaPaRjbDbOIIcp6f49cMaX5ndK9TAFnKwlVqz3nrK9nLKqgyDIhYsIq47smcAIZkK56SWPx3X3DwAFqRu2UPojpd2upWwo-3uJrod',
// This token is linked to a mock Google account (typebot.test.user@gmail.com)
refresh_token:
'1//03W5-TyIxXd7nCgYIARAAGAMSNwF-L9IrAGAmp5MG8RqVyk6YYmqDDn9x-4nHTkSUj4xZWuMs6mNeyjdS_bgO0CWuZEfJoAd_zIw',
'1//0379tIHBxszeXCgYIARAAGAMSNwF-L9Ir0zhkzhblwXqn3_jYqRP3pajcUpqkjRU3fKZZ_eQakOa28amUHSQ-Q9fMzk89MpRTvkc',
},
},
],
@ -117,15 +120,6 @@ const parseTypebotToPublicTypebot = (
edges: typebot.edges,
})
export const loadRawTypebotInDatabase = (typebot: Typebot) =>
prisma.typebot.create({
data: {
...typebot,
id: 'typebot4',
ownerId: process.env.PLAYWRIGHT_USER_ID,
} as any,
})
const parseTestTypebot = (partialTypebot: Partial<Typebot>): Typebot => ({
id: partialTypebot.id ?? 'typebot',
folderId: null,
@ -216,14 +210,12 @@ export const importTypebotInDatabase = (
path: string,
updates?: Partial<Typebot>
) => {
const typebot: Typebot = JSON.parse(readFileSync(path).toString())
try {
return prisma.typebot.create({
data: {
...typebot,
...updates,
ownerId: process.env.PLAYWRIGHT_USER_ID,
} as any,
})
} catch {}
const typebot: any = {
...JSON.parse(readFileSync(path).toString()),
...updates,
ownerId: process.env.PLAYWRIGHT_USER_ID,
}
return prisma.typebot.create({
data: typebot,
})
}

View File

@ -6,6 +6,7 @@ import { updateUser } from '../services/database'
test.describe('Account page', () => {
test('should edit user info properly', async ({ page }) => {
await updateUser({ name: 'Default Name' })
await page.goto('/account')
const saveButton = page.locator('button:has-text("Save")')
await expect(saveButton).toBeHidden()

View File

@ -6,6 +6,7 @@ import {
import { BubbleStepType, defaultImageBubbleContent } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import path from 'path'
import { generate } from 'short-uuid'
const unsplashImageSrc =
'https://images.unsplash.com/photo-1504297050568-910d24c426d3?ixlib=rb-1.2.1&ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80'
@ -13,7 +14,7 @@ const unsplashImageSrc =
test.describe.parallel('Image bubble step', () => {
test.describe('Content settings', () => {
test('should upload image file correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-image-upload'
const typebotId = generate()
await createTypebots([
{
id: typebotId,
@ -41,7 +42,7 @@ test.describe.parallel('Image bubble step', () => {
})
test('should import image link correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-image-link'
const typebotId = generate()
await createTypebots([
{
id: typebotId,
@ -64,7 +65,7 @@ test.describe.parallel('Image bubble step', () => {
})
test('should import gifs correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-gifs'
const typebotId = generate()
await createTypebots([
{
id: typebotId,
@ -89,7 +90,7 @@ test.describe.parallel('Image bubble step', () => {
test.describe('Preview', () => {
test('should display correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-preview'
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@ -5,11 +5,11 @@ import {
} from '../../services/database'
import { BubbleStepType, defaultTextBubbleContent } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'text-bubble-step'
import { generate } from 'short-uuid'
test.describe('Text bubble step', () => {
test('rich text features should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@ -9,6 +9,7 @@ import {
VideoBubbleContentType,
} from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import { generate } from 'short-uuid'
const videoSrc =
'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4'
@ -18,7 +19,7 @@ const vimeoVideoSrc = 'https://vimeo.com/649301125'
test.describe.parallel('Video bubble step', () => {
test.describe('Content settings', () => {
test('should import video url correctly', async ({ page }) => {
const typebotId = 'video-bubble-step-link'
const typebotId = generate()
await createTypebots([
{
id: typebotId,
@ -42,7 +43,7 @@ test.describe.parallel('Video bubble step', () => {
test.describe('Preview', () => {
test('should display video correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-preview-video'
const typebotId = generate()
await createTypebots([
{
id: typebotId,
@ -64,7 +65,7 @@ test.describe.parallel('Video bubble step', () => {
})
test('should display youtube video correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-preview-youtube'
const typebotId = generate()
await createTypebots([
{
id: typebotId,
@ -88,7 +89,7 @@ test.describe.parallel('Video bubble step', () => {
})
test('should display vimeo video correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-preview-vimeo'
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@ -1,4 +1,5 @@
import test, { expect, Page } from '@playwright/test'
import { generate } from 'short-uuid'
import { createFolders, createTypebots } from '../services/database'
import { deleteButtonInConfirmDialog } from '../services/selectorUtils'
@ -20,6 +21,7 @@ test.describe('Dashboard page', () => {
await page.click('text="New folder"')
await page.fill('input', 'My folder #2')
await page.press('input', 'Enter')
await waitForNextApiCall(page)
await page.click('li:has-text("My folder #2")')
await expect(page.locator('h1 >> text="My folder #2"')).toBeVisible()
@ -32,10 +34,7 @@ test.describe('Dashboard page', () => {
})
test('folders and typebots should be deletable', async ({ page }) => {
await createFolders([
{ id: 'folder1', name: 'Folder #1' },
{ id: 'folder2', name: 'Folder #2' },
])
await createFolders([{ name: 'Folder #1' }, { name: 'Folder #2' }])
await createTypebots([{ id: 'deletable-typebot', name: 'Typebot #1' }])
await page.goto('/typebots')
await page.click('button[aria-label="Show Folder #1 menu"]')
@ -49,10 +48,9 @@ test.describe('Dashboard page', () => {
})
test('folders and typebots should be movable', async ({ page }) => {
await createFolders([{ id: 'droppable-folder', name: 'Droppable folder' }])
await createTypebots([
{ id: 'draggable-typebot', name: 'Draggable typebot' },
])
const droppableFolderId = generate()
await createFolders([{ id: droppableFolderId, name: 'Droppable folder' }])
await createTypebots([{ name: 'Draggable typebot' }])
await page.goto('/typebots')
const typebotButton = page.locator('li:has-text("Draggable typebot")')
const folderButton = page.locator('li:has-text("Droppable folder")')
@ -63,7 +61,7 @@ test.describe('Dashboard page', () => {
await waitForNextApiCall(page)
await expect(typebotButton).toBeHidden()
await folderButton.click()
await expect(page).toHaveURL(/folders\/droppable-folder/)
await expect(page).toHaveURL(new RegExp(`/folders/${droppableFolderId}`))
await expect(typebotButton).toBeVisible()
await page.dragAndDrop(
'li:has-text("Draggable typebot")',

View File

@ -0,0 +1,28 @@
import test, { expect } from '@playwright/test'
import { createTypebots, parseDefaultBlockWithStep } from '../services/database'
import { defaultTextInputOptions, InputStepType } from 'models'
import { generate } from 'short-uuid'
test.describe('Editor', () => {
test('Undo / Redo buttons should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: InputStepType.TEXT,
options: defaultTextInputOptions,
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Block #1', { button: 'right' })
await page.click('text=Delete')
await expect(page.locator('text=Block #1')).toBeHidden()
await page.click('button[aria-label="Undo"]')
await expect(page.locator('text=Block #1')).toBeVisible()
await page.click('button[aria-label="Redo"]')
await expect(page.locator('text=Block #1')).toBeHidden()
})
})

View File

@ -5,11 +5,11 @@ import {
} from '../../services/database'
import { defaultChoiceInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'buttons-input-step'
import { generate } from 'short-uuid'
test.describe.parallel('Buttons input step', () => {
test('can edit button items', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@ -5,11 +5,11 @@ import {
} from '../../services/database'
import { defaultDateInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'date-input-step'
import { generate } from 'short-uuid'
test.describe('Date input step', () => {
test('options should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@ -5,11 +5,11 @@ import {
} from '../../services/database'
import { defaultEmailInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'email-input-step'
import { generate } from 'short-uuid'
test.describe('Email input step', () => {
test('options should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@ -5,11 +5,11 @@ import {
} from '../../services/database'
import { defaultNumberInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'number-input-step'
import { generate } from 'short-uuid'
test.describe('Number input step', () => {
test('options should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@ -5,11 +5,11 @@ import {
} from '../../services/database'
import { defaultPhoneInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'phone-input-step'
import { generate } from 'short-uuid'
test.describe('Phone input step', () => {
test('options should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@ -5,11 +5,11 @@ import {
} from '../../services/database'
import { defaultTextInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'text-input-step'
import { generate } from 'short-uuid'
test.describe('Text input step', () => {
test('options should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@ -5,11 +5,11 @@ import {
} from '../../services/database'
import { defaultUrlInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'url-input-step'
import { generate } from 'short-uuid'
test.describe('Url input step', () => {
test('options should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@ -4,11 +4,11 @@ import {
parseDefaultBlockWithStep,
} from '../../services/database'
import { defaultGoogleAnalyticsOptions, IntegrationStepType } from 'models'
const typebotId = 'google-analytics-step'
import { generate } from 'short-uuid'
test.describe('Google Analytics step', () => {
test('its configuration should work', async ({ page }) => {
const typebotId = generate()
await createTypebots([
{
id: typebotId,

View File

@ -2,10 +2,11 @@ import test, { expect, Page } from '@playwright/test'
import { importTypebotInDatabase } from '../../services/database'
import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
import { generate } from 'short-uuid'
test.describe.parallel('Google sheets integration', () => {
test('Insert row should work', async ({ page }) => {
const typebotId = 'google-sheets-insert'
const typebotId = generate()
await importTypebotInDatabase(
path.join(
__dirname,
@ -20,10 +21,11 @@ test.describe.parallel('Google sheets integration', () => {
await page.click('text=Select an operation')
await page.click('text=Insert a row')
await page.click('text=Add a value')
await page.click('text=Select a column')
await page.click('text="Email" >> nth = 1')
await page.click('button >> text="Email"')
await page.click('[aria-label="Insert a variable"]')
await page.click('text="Email" >> nth = 2')
await page.click('button >> text="Email" >> nth=1')
await page.click('text=Add a value')
await page.click('text=Select a column')
@ -54,7 +56,7 @@ test.describe.parallel('Google sheets integration', () => {
})
test('Update row should work', async ({ page }) => {
const typebotId = 'google-sheets-update'
const typebotId = generate()
await importTypebotInDatabase(
path.join(
__dirname,
@ -69,11 +71,13 @@ test.describe.parallel('Google sheets integration', () => {
await page.click('text=Select an operation')
await page.click('text=Update a row')
await page.click('text=Add a value')
await page.click('text=Select a column')
await page.click('text="Email" >> nth = 1')
await page.click('button >> text="Email"')
await page.click('[aria-label="Insert a variable"]')
await page.click('text="Email" >> nth = 2')
await page.click('button >> text="Email" >> nth=1')
await page.click('text=Add a value')
await page.click('text=Select a column')
await page.click('text=Last name')
await page.fill(
@ -102,7 +106,7 @@ test.describe.parallel('Google sheets integration', () => {
})
test('Get row should work', async ({ page }) => {
const typebotId = 'google-sheets-get'
const typebotId = generate()
await importTypebotInDatabase(
path.join(
__dirname,
@ -118,16 +122,16 @@ test.describe.parallel('Google sheets integration', () => {
await page.click('text=Get data from sheet')
await page.click('text=Select a column')
await page.click('text="Email" >> nth = 1')
await page.click('button >> text="Email"')
await page.click('[aria-label="Insert a variable"]')
await page.click('text="Email" >> nth = 2')
await page.click('button >> text="Email" >> nth=1')
await page.click('text=Add a value')
await page.click('text=Select a column')
await page.click('text="First name"')
await createNewVar(page, 'First name')
await page.click('text=Add a value')
await page.click('text=Select a column')
await page.click('text="Last name"')
await createNewVar(page, 'Last name')

View File

@ -1,8 +1,9 @@
import test, { expect, Page } from '@playwright/test'
import { importTypebotInDatabase } from '../../services/database'
import path from 'path'
import { generate } from 'short-uuid'
const typebotId = 'webhook-step'
const typebotId = generate()
test.describe('Webhook step', () => {
test('its configuration should work', async ({ page }) => {
@ -23,6 +24,7 @@ test.describe('Webhook step', () => {
)
await page.click('text=Query params')
await page.click('text=Add a param')
await page.fill('input[placeholder="e.g. email"]', 'firstParam')
await page.fill('input[placeholder="e.g. {{Email}}"]', '{{secret 1}}')
@ -34,6 +36,7 @@ test.describe('Webhook step', () => {
)
await page.click('text=Headers')
await page.click('text=Add a value')
await page.fill('input[placeholder="e.g. Content-Type"]', 'Custom-Typebot')
await page.fill(
'input[placeholder="e.g. application/json"]',
@ -45,11 +48,8 @@ test.describe('Webhook step', () => {
await page.click('text=Variable values for test')
await addTestVariable(page, 'secret 1', 'secret1')
await page.click('text=Add an entry')
await addTestVariable(page, 'secret 2', 'secret2')
await page.click('text=Add an entry')
await addTestVariable(page, 'secret 3', 'secret3')
await page.click('text=Add an entry')
await addTestVariable(page, 'secret 4', 'secret4')
await page.click('text=Test the request')
@ -58,12 +58,14 @@ test.describe('Webhook step', () => {
)
await page.click('text=Save in variables')
await page.click('text=Add an entry >> nth=-1')
await page.click('input[placeholder="Select the data"]')
await page.click('text=data[0].name')
})
})
const addTestVariable = async (page: Page, name: string, value: string) => {
await page.click('text=Add an entry')
await page.click('[data-testid="variables-input"] >> nth=-1')
await page.click(`text="${name}"`)
await page.fill('input >> nth=-1', value)

View File

@ -1,9 +1,10 @@
import test, { expect, Page } from '@playwright/test'
import test, { expect } from '@playwright/test'
import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
import { importTypebotInDatabase } from '../../services/database'
import { generate } from 'short-uuid'
const typebotId = 'condition-step'
const typebotId = generate()
test.describe('Condition step', () => {
test('its configuration should work', async ({ page }) => {
@ -16,6 +17,7 @@ test.describe('Condition step', () => {
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Configure...')
await page.click('button:has-text("Add a comparison")')
await page.fill(
'input[placeholder="Search for a variable"] >> nth=-1',
'Age'
@ -40,6 +42,7 @@ test.describe('Condition step', () => {
)
await page.click('text=Configure...')
await page.click('button:has-text("Add a comparison")')
await page.fill(
'input[placeholder="Search for a variable"] >> nth=-1',
'Age'

View File

@ -2,8 +2,9 @@ import test, { expect } from '@playwright/test'
import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
import { importTypebotInDatabase } from '../../services/database'
import { generate } from 'short-uuid'
const typebotId = 'redirect-step'
const typebotId = generate()
test.describe('Redirect step', () => {
test('its configuration should work', async ({ page, context }) => {

View File

@ -2,8 +2,9 @@ import test, { expect } from '@playwright/test'
import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
import { importTypebotInDatabase } from '../../services/database'
import { generate } from 'short-uuid'
const typebotId = 'set-variable-step'
const typebotId = generate()
test.describe('Set variable step', () => {
test('its configuration should work', async ({ page }) => {
@ -24,7 +25,7 @@ test.describe('Set variable step', () => {
await page.click('text=Create "Total"')
await page.fill('textarea', '1000 * {{Num}}')
await page.click('text=Click to edit...')
await page.click('text=Click to edit...', { force: true })
await page.fill(
'input[placeholder="Select a variable"] >> nth=-1',
'Custom var'

View File

@ -2,6 +2,7 @@ import test, { expect, Page } from '@playwright/test'
import { readFileSync } from 'fs'
import { InputStepType } from 'models'
import { parse } from 'papaparse'
import { generate } from 'short-uuid'
import {
createResults,
createTypebots,
@ -9,7 +10,7 @@ import {
} from '../services/database'
import { deleteButtonInConfirmDialog } from '../services/selectorUtils'
const typebotId = 'typebot-for-results'
const typebotId = generate()
test.describe('Results page', () => {
test('results should be deletable', async ({ page }) => {

View File

@ -1,12 +1,13 @@
import test, { expect } from '@playwright/test'
import path from 'path'
import { generate } from 'short-uuid'
import { importTypebotInDatabase } from '../services/database'
import { typebotViewer } from '../services/selectorUtils'
test.describe.parallel('Settings page', () => {
test.describe('General', () => {
test('should reflect change in real-time', async ({ page }) => {
const typebotId = 'general-settings-typebot'
const typebotId = generate()
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/theme.json'),
{
@ -27,7 +28,7 @@ test.describe.parallel('Settings page', () => {
test.describe('Typing emulation', () => {
test('should be fillable', async ({ page }) => {
const typebotId = 'typing-emulation-typebot'
const typebotId = generate()
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/theme.json'),
{
@ -75,7 +76,7 @@ test.describe.parallel('Settings page', () => {
// Website image
const websiteImg = page.locator(':nth-match(img, 2)')
await expect(websiteImg).toHaveAttribute('src', '/viewer-preview.png')
await websiteImg.click()
await websiteImg.click({ position: { x: 0, y: 180 } })
await expect(page.locator('text=Giphy')).toBeHidden()
await page.click('button:has-text("Embed link")')
await page.fill('input[placeholder="Paste the image link..."]', imageUrl)

View File

@ -1,12 +1,13 @@
import test, { expect } from '@playwright/test'
import path from 'path'
import { generate } from 'short-uuid'
import { importTypebotInDatabase } from '../services/database'
import { typebotViewer } from '../services/selectorUtils'
test.describe.parallel('Theme page', () => {
test.describe('General', () => {
test('should reflect change in real-time', async ({ page }) => {
const typebotId = 'general-theme-typebot'
const typebotId = generate()
const chatContainer = typebotViewer(page).locator(
'[data-testid="container"]'
)
@ -106,7 +107,7 @@ test.describe.parallel('Theme page', () => {
test.describe('Custom CSS', () => {
test('should reflect change in real-time', async ({ page }) => {
const typebotId = 'custom-css-theme-typebot'
const typebotId = generate()
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/theme.json'),
{