2
0

perf(e2e): ️ Migrate to Playwright

This commit is contained in:
Baptiste Arnaud
2022-01-28 09:42:31 +01:00
parent c5aaa323d1
commit 73f277fce7
145 changed files with 3104 additions and 2346 deletions

View File

@@ -0,0 +1,51 @@
import test, { expect } from '@playwright/test'
import { refreshUser } from '../services/browser'
import { Plan } from 'db'
import path from 'path'
import { updateUser, user } from '../services/database'
test.describe('Account page', () => {
test('should edit user info properly', async ({ page }) => {
await page.goto('/account')
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()
const avatarImg = page.locator('img')
await expect(page.locator('text=JD')).toBeVisible()
await expect(avatarImg).toBeHidden()
await page.setInputFiles(
'input[type="file"]',
path.join(__dirname, '../fixtures/avatar.jpg')
)
await expect(avatarImg).toHaveAttribute(
'src',
new RegExp(
`https://s3.eu-west-3.amazonaws.com/typebot/users/${user.id}/avatar`,
'gm'
)
)
await saveButton.click()
await expect(saveButton).toBeHidden()
})
test('should display valid plans', async ({ page }) => {
await updateUser({ plan: Plan.FREE, stripeId: null })
await page.goto('/account')
await expect(page.locator('text=Free plan')).toBeVisible()
await page.evaluate(refreshUser)
await page.reload()
const manageSubscriptionButton = page.locator(
'a:has-text("Manage my subscription")'
)
await expect(manageSubscriptionButton).toBeHidden()
await updateUser({ plan: Plan.PRO, stripeId: 'stripeId' })
await page.evaluate(refreshUser)
await page.reload()
await expect(page.locator('text=Pro plan')).toBeVisible()
await expect(manageSubscriptionButton).toBeVisible()
})
})

View File

@@ -0,0 +1,113 @@
import test, { expect } from '@playwright/test'
import {
createTypebots,
parseDefaultBlockWithStep,
} from '../../services/database'
import { BubbleStepType, defaultImageBubbleContent } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
import path from 'path'
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.describe.parallel('Image bubble step', () => {
test.describe('Content settings', () => {
test('should upload image file correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-image-upload'
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: BubbleStepType.IMAGE,
content: defaultImageBubbleContent,
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Click to edit...')
await page.setInputFiles(
'input[type="file"]',
path.join(__dirname, '../../fixtures/avatar.jpg')
)
await expect(page.locator('img')).toHaveAttribute(
'src',
new RegExp(
`https://s3.eu-west-3.amazonaws.com/typebot/typebots/${typebotId}/avatar.jpg`,
'gm'
)
)
})
test('should import image link correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-image-link'
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: BubbleStepType.IMAGE,
content: defaultImageBubbleContent,
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Click to edit...')
await page.click('text=Embed link')
await page.fill(
'input[placeholder="Paste the image link..."]',
unsplashImageSrc
)
await expect(page.locator('img')).toHaveAttribute('src', unsplashImageSrc)
})
test('should import gifs correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-gifs'
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: BubbleStepType.IMAGE,
content: defaultImageBubbleContent,
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Click to edit...')
await page.click('text=Giphy')
await page.click('img >> nth=3')
await expect(page.locator('img[alt="Step image"]')).toHaveAttribute(
'src',
new RegExp('giphy.com/media', 'gm')
)
})
})
test.describe('Preview', () => {
test('should display correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-preview'
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: BubbleStepType.IMAGE,
content: {
url: unsplashImageSrc,
},
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(typebotViewer(page).locator('img')).toHaveAttribute(
'src',
unsplashImageSrc
)
})
})
})

View File

@@ -0,0 +1,55 @@
import test, { expect } from '@playwright/test'
import {
createTypebots,
parseDefaultBlockWithStep,
} from '../../services/database'
import { BubbleStepType, defaultTextBubbleContent } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'text-bubble-step'
test.describe('Text bubble step', () => {
test('rich text features should work', async ({ page }) => {
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: BubbleStepType.TEXT,
content: defaultTextBubbleContent,
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('[data-testid="bold-button"]')
await page.type('div[role="textbox"]', 'Bold text')
await page.press('div[role="textbox"]', 'Enter')
await page.click('[data-testid="bold-button"]')
await page.click('[data-testid="italic-button"]')
await page.type('div[role="textbox"]', 'Italic text')
await page.press('div[role="textbox"]', 'Enter')
await page.click('[data-testid="underline-button"]')
await page.click('[data-testid="italic-button"]')
await page.type('div[role="textbox"]', 'Underlined text')
await page.press('div[role="textbox"]', 'Enter')
await page.click('[data-testid="bold-button"]')
await page.click('[data-testid="italic-button"]')
await page.type('div[role="textbox"]', 'Everything text')
await page.press('div[role="textbox"]', 'Enter')
await page.click('text=Preview')
await expect(
typebotViewer(page).locator('span.slate-bold >> nth=0')
).toHaveText('Bold text')
await expect(
typebotViewer(page).locator('span.slate-italic >> nth=0')
).toHaveText('Italic text')
await expect(
typebotViewer(page).locator('span.slate-underline >> nth=0')
).toHaveText('Underlined text')
})
})

View File

@@ -0,0 +1,114 @@
import test, { expect } from '@playwright/test'
import {
createTypebots,
parseDefaultBlockWithStep,
} from '../../services/database'
import {
BubbleStepType,
defaultVideoBubbleContent,
VideoBubbleContentType,
} from 'models'
import { typebotViewer } from '../../services/selectorUtils'
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.describe.parallel('Video bubble step', () => {
test.describe('Content settings', () => {
test('should import video url correctly', async ({ page }) => {
const typebotId = 'video-bubble-step-link'
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: BubbleStepType.VIDEO,
content: defaultVideoBubbleContent,
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Click to edit...')
await page.fill('input[placeholder="Paste the video link..."]', videoSrc)
await expect(page.locator('video > source')).toHaveAttribute(
'src',
videoSrc
)
})
})
test.describe('Preview', () => {
test('should display video correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-preview-video'
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: BubbleStepType.VIDEO,
content: {
type: VideoBubbleContentType.URL,
url: videoSrc,
},
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(
typebotViewer(page).locator('video > source')
).toHaveAttribute('src', videoSrc)
})
test('should display youtube video correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-preview-youtube'
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: BubbleStepType.VIDEO,
content: {
type: VideoBubbleContentType.YOUTUBE,
url: youtubeVideoSrc,
id: 'dQw4w9WgXcQ',
},
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(typebotViewer(page).locator('iframe')).toHaveAttribute(
'src',
'https://www.youtube.com/embed/dQw4w9WgXcQ'
)
})
test('should display vimeo video correctly', async ({ page }) => {
const typebotId = 'image-bubble-step-preview-vimeo'
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: BubbleStepType.VIDEO,
content: {
type: VideoBubbleContentType.VIMEO,
url: vimeoVideoSrc,
id: '649301125',
},
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(typebotViewer(page).locator('iframe')).toHaveAttribute(
'src',
'https://player.vimeo.com/video/649301125'
)
})
})
})

View File

@@ -0,0 +1,80 @@
import test, { expect, Page } from '@playwright/test'
import { createFolders, createTypebots } from '../services/database'
import { deleteButtonInConfirmDialog } from '../services/selectorUtils'
test.describe('Dashboard page', () => {
test('folders navigation should work', async ({ page }) => {
await page.goto('/typebots')
const createFolderButton = page.locator(
'button:has-text("Create a folder")'
)
await expect(createFolderButton).not.toBeDisabled()
await createFolderButton.click()
await page.click('text="New folder"')
await page.fill('input[value="New folder"]', 'My folder #1')
await page.press('input[value="My folder #1"]', 'Enter')
await waitForNextApiCall(page)
await page.click('li:has-text("My folder #1")')
await expect(page.locator('h1:has-text("My folder #1")')).toBeVisible()
await createFolderButton.click()
await page.click('text="New folder"')
await page.fill('input', 'My folder #2')
await page.press('input', 'Enter')
await page.click('li:has-text("My folder #2")')
await expect(page.locator('h1 >> text="My folder #2"')).toBeVisible()
await page.click('text="Back"')
await expect(page.locator('span >> text="My folder #2"')).toBeVisible()
await page.click('text="Back"')
await expect(page.locator('span >> text=My folder #1')).toBeVisible()
})
test('folders and typebots should be deletable', async ({ page }) => {
await createFolders([
{ id: 'folder1', name: 'Folder #1' },
{ id: 'folder2', 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"]')
await page.click('li:has-text("Folder #1") >> button:has-text("Delete")')
await deleteButtonInConfirmDialog(page).click()
await expect(page.locator('span >> text="Folder #1"')).not.toBeVisible()
await page.click('button[aria-label="Show Typebot #1 menu"]')
await page.click('li:has-text("Typebot #1") >> button:has-text("Delete")')
await deleteButtonInConfirmDialog(page).click()
await expect(page.locator('span >> text="Typebot #1"')).not.toBeVisible()
})
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' },
])
await page.goto('/typebots')
const typebotButton = page.locator('li:has-text("Draggable typebot")')
const folderButton = page.locator('li:has-text("Droppable folder")')
await page.dragAndDrop(
'li:has-text("Draggable typebot")',
'li:has-text("Droppable folder")'
)
await waitForNextApiCall(page)
await expect(typebotButton).toBeHidden()
await folderButton.click()
await expect(page).toHaveURL(/folders\/droppable-folder/)
await expect(typebotButton).toBeVisible()
await page.dragAndDrop(
'li:has-text("Draggable typebot")',
'a:has-text("Back")'
)
await waitForNextApiCall(page)
await expect(typebotButton).toBeHidden()
await page.click('a:has-text("Back")')
await expect(typebotButton).toBeVisible()
})
})
const waitForNextApiCall = (page: Page, path?: string) =>
page.waitForResponse((resp) => resp.url().includes(path ?? '/api'))

View File

@@ -0,0 +1,65 @@
import test, { expect } from '@playwright/test'
import {
createTypebots,
parseDefaultBlockWithStep,
} from '../../services/database'
import { defaultChoiceInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'buttons-input-step'
test.describe.parallel('Buttons input step', () => {
test('can edit button items', async ({ page }) => {
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: InputStepType.CHOICE,
options: { ...defaultChoiceInputOptions, itemIds: ['choice1'] },
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.fill('input[value="Click to edit"]', 'Item 1')
await page.press('input[value="Item 1"]', 'Enter')
await page.locator('text=Item 1').hover()
await page.click('[aria-label="Add item"]')
await page.fill('input[value="Click to edit"]', 'Item 2')
await page.press('input[value="Item 2"]', 'Enter')
await page.locator('text=Item 2').hover()
await page.click('[aria-label="Add item"]')
await page.fill('input[value="Click to edit"]', 'Item 3')
await page.press('input[value="Item 3"]', 'Enter')
await page.click('text=Item 2', { button: 'right' })
await page.click('text=Delete')
await expect(page.locator('text=Item 2')).toBeHidden()
await page.click('text=Preview')
const item3Button = typebotViewer(page).locator('button >> text=Item 3')
await item3Button.click()
await expect(item3Button).toBeHidden()
await expect(typebotViewer(page).locator('text=Item 3')).toBeVisible()
await page.click('button[aria-label="Close"]')
await page.click('[data-testid="step1-icon"]')
await page.click('text=Multiple choice?')
await page.fill('#button', 'Go')
await page.click('[data-testid="step1-icon"]')
await page.locator('text=Item 1').hover()
await page.click('[aria-label="Add item"]')
await page.fill('input[value="Click to edit"]', 'Item 2')
await page.press('input[value="Item 2"]', 'Enter')
await page.click('text=Preview')
await typebotViewer(page).locator('button >> text="Item 3"').click()
await typebotViewer(page).locator('button >> text="Item 1"').click()
await typebotViewer(page).locator('text=Go').click()
await expect(
typebotViewer(page).locator('text="Item 3, Item 1"')
).toBeVisible()
})
})

View File

@@ -0,0 +1,46 @@
import test, { expect } from '@playwright/test'
import {
createTypebots,
parseDefaultBlockWithStep,
} from '../../services/database'
import { defaultDateInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'date-input-step'
test.describe('Date input step', () => {
test('options should work', async ({ page }) => {
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: InputStepType.DATE,
options: defaultDateInputOptions,
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(
typebotViewer(page).locator('[data-testid="from-date"]')
).toHaveAttribute('type', 'date')
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
await page.click(`text=Pick a date...`)
await page.click('text=Is range?')
await page.click('text=With time?')
await page.fill('#from', 'Previous:')
await page.fill('#to', 'After:')
await page.fill('#button', 'Go')
await page.click('text=Restart')
await expect(
typebotViewer(page).locator(`[data-testid="from-date"]`)
).toHaveAttribute('type', 'datetime-local')
await expect(
typebotViewer(page).locator(`[data-testid="to-date"]`)
).toHaveAttribute('type', 'datetime-local')
})
})

View File

@@ -0,0 +1,43 @@
import test, { expect } from '@playwright/test'
import {
createTypebots,
parseDefaultBlockWithStep,
} from '../../services/database'
import { defaultEmailInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'email-input-step'
test.describe('Email input step', () => {
test('options should work', async ({ page }) => {
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: InputStepType.EMAIL,
options: defaultEmailInputOptions,
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(
typebotViewer(page).locator(
`input[placeholder="${defaultEmailInputOptions.labels.placeholder}"]`
)
).toHaveAttribute('type', 'email')
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
await page.click(`text=${defaultEmailInputOptions.labels.placeholder}`)
await page.fill('#placeholder', 'Your email...')
await expect(page.locator('text=Your email...')).toBeVisible()
await page.fill('#button', 'Go')
await page.click('text=Restart')
await expect(
typebotViewer(page).locator(`input[placeholder="Your email..."]`)
).toBeVisible()
})
})

View File

@@ -0,0 +1,53 @@
import test, { expect } from '@playwright/test'
import {
createTypebots,
parseDefaultBlockWithStep,
} from '../../services/database'
import { defaultNumberInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'number-input-step'
test.describe('Number input step', () => {
test('options should work', async ({ page }) => {
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: InputStepType.NUMBER,
options: defaultNumberInputOptions,
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(
typebotViewer(page).locator(
`input[placeholder="${defaultNumberInputOptions.labels.placeholder}"]`
)
).toHaveAttribute('type', 'number')
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
await page.click(`text=${defaultNumberInputOptions.labels.placeholder}`)
await page.fill('#placeholder', 'Your number...')
await expect(page.locator('text=Your number...')).toBeVisible()
await page.fill('#button', 'Go')
await page.fill('[role="spinbutton"] >> nth=0', '0')
await page.fill('[role="spinbutton"] >> nth=1', '100')
await page.fill('[role="spinbutton"] >> nth=2', '10')
await page.click('text=Restart')
const input = typebotViewer(page).locator(
`input[placeholder="Your number..."]`
)
await input.fill('-1')
await input.press('Enter')
await input.fill('150')
await input.press('Enter')
await input.fill('50')
await input.press('Enter')
await expect(typebotViewer(page).locator('text=50')).toBeVisible()
})
})

View File

@@ -0,0 +1,48 @@
import test, { expect } from '@playwright/test'
import {
createTypebots,
parseDefaultBlockWithStep,
} from '../../services/database'
import { defaultPhoneInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'phone-input-step'
test.describe('Phone input step', () => {
test('options should work', async ({ page }) => {
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: InputStepType.PHONE,
options: defaultPhoneInputOptions,
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(
typebotViewer(page).locator(
`input[placeholder="${defaultPhoneInputOptions.labels.placeholder}"]`
)
).toHaveAttribute('type', 'tel')
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
await page.click(`text=${defaultPhoneInputOptions.labels.placeholder}`)
await page.fill('#placeholder', '+33 XX XX XX XX')
await page.fill('#button', 'Go')
await page.click('text=Restart')
await typebotViewer(page)
.locator(`input[placeholder="+33 XX XX XX XX"]`)
.fill('+33 6 73 18 45 36')
await expect(typebotViewer(page).locator(`img`)).toHaveAttribute(
'alt',
'France'
)
await typebotViewer(page).locator('text="Go"').click()
await expect(typebotViewer(page).locator('text=+33673184536')).toBeVisible()
})
})

View File

@@ -0,0 +1,44 @@
import test, { expect } from '@playwright/test'
import {
createTypebots,
parseDefaultBlockWithStep,
} from '../../services/database'
import { defaultTextInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'text-input-step'
test.describe('Text input step', () => {
test('options should work', async ({ page }) => {
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: InputStepType.TEXT,
options: defaultTextInputOptions,
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(
typebotViewer(page).locator(
`input[placeholder="${defaultTextInputOptions.labels.placeholder}"]`
)
).toHaveAttribute('type', 'text')
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
await page.click(`text=${defaultTextInputOptions.labels.placeholder}`)
await page.fill('#placeholder', 'Your name...')
await page.fill('#button', 'Go')
await page.click('text=Long text?')
await page.click('text=Restart')
await expect(
typebotViewer(page).locator(`textarea[placeholder="Your name..."]`)
).toBeVisible()
await expect(typebotViewer(page).locator(`text=Go`)).toBeVisible()
})
})

View File

@@ -0,0 +1,43 @@
import test, { expect } from '@playwright/test'
import {
createTypebots,
parseDefaultBlockWithStep,
} from '../../services/database'
import { defaultUrlInputOptions, InputStepType } from 'models'
import { typebotViewer } from '../../services/selectorUtils'
const typebotId = 'url-input-step'
test.describe('Url input step', () => {
test('options should work', async ({ page }) => {
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: InputStepType.URL,
options: defaultUrlInputOptions,
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Preview')
await expect(
typebotViewer(page).locator(
`input[placeholder="${defaultUrlInputOptions.labels.placeholder}"]`
)
).toHaveAttribute('type', 'url')
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
await page.click(`text=${defaultUrlInputOptions.labels.placeholder}`)
await page.fill('#placeholder', 'Your URL...')
await expect(page.locator('text=Your URL...')).toBeVisible()
await page.fill('#button', 'Go')
await page.click('text=Restart')
await expect(
typebotViewer(page).locator(`input[placeholder="Your URL..."]`)
).toBeVisible()
})
})

View File

@@ -0,0 +1,34 @@
import test from '@playwright/test'
import {
createTypebots,
parseDefaultBlockWithStep,
} from '../../services/database'
import { defaultGoogleAnalyticsOptions, IntegrationStepType } from 'models'
const typebotId = 'google-analytics-step'
test.describe('Google Analytics step', () => {
test('its configuration should work', async ({ page }) => {
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({
type: IntegrationStepType.GOOGLE_ANALYTICS,
options: defaultGoogleAnalyticsOptions,
}),
},
])
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Configure...')
await page.fill('input[placeholder="G-123456..."]', 'G-VWX9WG1TNS')
await page.fill('input[placeholder="Example: Typebot"]', 'Typebot')
await page.fill(
'input[placeholder="Example: Submit email"]',
'Submit email'
)
await page.click('text=Advanced')
await page.fill('input[placeholder="Example: Campaign Z"]', 'Campaign Z')
await page.fill('input[placeholder="Example: 0"]', '0')
})
})

View File

@@ -0,0 +1,163 @@
import test, { expect, Page } from '@playwright/test'
import { importTypebotInDatabase } from '../../services/database'
import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
test.describe.parallel('Google sheets integration', () => {
test('Insert row should work', async ({ page }) => {
const typebotId = 'google-sheets-insert'
await importTypebotInDatabase(
path.join(
__dirname,
'../../fixtures/typebots/integrations/googleSheets.json'
),
{
id: typebotId,
}
)
await page.goto(`/typebots/${typebotId}/edit`)
await fillInSpreadsheetInfo(page)
await page.click('text=Select an operation')
await page.click('text=Insert a row')
await page.click('text=Select a column')
await page.click('text="Email" >> nth = 1')
await page.click('[aria-label="Insert a variable"]')
await page.click('text="Email" >> nth = 2')
await page.click('text=Add a value')
await page.click('text=Select a column')
await page.click('text=First name')
await page.fill(
'input[placeholder="Type a value..."] >> nth = 1',
'Georges'
)
await page.click('text=Preview')
await typebotViewer(page)
.locator('input[placeholder="Type your email..."]')
.fill('georges@gmail.com')
await typebotViewer(page)
.locator('input[placeholder="Type your email..."]')
.press('Enter')
await page.waitForResponse(
(resp) =>
resp
.request()
.url()
.includes(
'/api/integrations/google-sheets/spreadsheets/1k_pIDw3YHl9tlZusbBVSBRY0PeRPd2H6t4Nj7rwnOtM/sheets/0'
) &&
resp.status() === 200 &&
resp.request().method() === 'POST'
)
})
test('Update row should work', async ({ page }) => {
const typebotId = 'google-sheets-update'
await importTypebotInDatabase(
path.join(
__dirname,
'../../fixtures/typebots/integrations/googleSheets.json'
),
{
id: typebotId,
}
)
await page.goto(`/typebots/${typebotId}/edit`)
await fillInSpreadsheetInfo(page)
await page.click('text=Select an operation')
await page.click('text=Update a row')
await page.click('text=Select a column')
await page.click('text="Email" >> nth = 1')
await page.click('[aria-label="Insert a variable"]')
await page.click('text="Email" >> nth = 2')
await page.click('text=Select a column')
await page.click('text=Last name')
await page.fill(
'input[placeholder="Type a value..."] >> nth = 1',
'Last name'
)
await page.click('text=Preview')
await typebotViewer(page)
.locator('input[placeholder="Type your email..."]')
.fill('test@test.com')
await typebotViewer(page)
.locator('input[placeholder="Type your email..."]')
.press('Enter')
await page.waitForResponse(
(resp) =>
resp
.request()
.url()
.includes(
'/api/integrations/google-sheets/spreadsheets/1k_pIDw3YHl9tlZusbBVSBRY0PeRPd2H6t4Nj7rwnOtM/sheets/0'
) &&
resp.status() === 200 &&
resp.request().method() === 'PATCH'
)
})
test('Get row should work', async ({ page }) => {
const typebotId = 'google-sheets-get'
await importTypebotInDatabase(
path.join(
__dirname,
'../../fixtures/typebots/integrations/googleSheetsGet.json'
),
{
id: typebotId,
}
)
await page.goto(`/typebots/${typebotId}/edit`)
await fillInSpreadsheetInfo(page)
await page.click('text=Select an operation')
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('[aria-label="Insert a variable"]')
await page.click('text="Email" >> nth = 2')
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')
await page.click('text=Preview')
await typebotViewer(page)
.locator('input[placeholder="Type your email..."]')
.fill('test2@test.com')
await typebotViewer(page)
.locator('input[placeholder="Type your email..."]')
.press('Enter')
await expect(
typebotViewer(page).locator('text=Your name is: John Smith')
).toBeVisible()
})
})
const fillInSpreadsheetInfo = async (page: Page) => {
await page.click('text=Configure...')
await page.click('text=Select an account')
await page.click('text=test2@gmail.com')
await page.fill('input[placeholder="Search for spreadsheet"]', 'CR')
await page.click('text=CRM')
await page.fill('input[placeholder="Select the sheet"]', 'Sh')
await page.click('text=Sheet1')
}
const createNewVar = async (page: Page, name: string) => {
await page.fill('input[placeholder="Select a variable"] >> nth=-1', name)
await page.click(`text=Create "${name}"`)
}

View File

@@ -0,0 +1,70 @@
import test, { expect, Page } from '@playwright/test'
import { importTypebotInDatabase } from '../../services/database'
import path from 'path'
const typebotId = 'webhook-step'
test.describe('Webhook step', () => {
test('its configuration should work', async ({ page }) => {
await importTypebotInDatabase(
path.join(__dirname, '../../fixtures/typebots/integrations/webhook.json'),
{
id: typebotId,
}
)
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Configure...')
await page.click('text=GET')
await page.click('text=POST')
await page.fill(
'input[placeholder="Your Webhook URL..."]',
`${process.env.NEXTAUTH_URL}/api/mock/webhook`
)
await page.click('text=Query params')
await page.fill('input[placeholder="e.g. email"]', 'firstParam')
await page.fill('input[placeholder="e.g. {{Email}}"]', '{{secret 1}}')
await page.click('text=Add a param')
await page.fill('input[placeholder="e.g. email"] >> nth=1', 'secondParam')
await page.fill(
'input[placeholder="e.g. {{Email}}"] >> nth=1',
'{{secret 2}}'
)
await page.click('text=Headers')
await page.fill('input[placeholder="e.g. Content-Type"]', 'Custom-Typebot')
await page.fill(
'input[placeholder="e.g. application/json"]',
'{{secret 3}}'
)
await page.click('text=Body')
await page.fill('div[role="textbox"]', '{ "customField": "{{secret 4}}" }')
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')
await expect(page.locator('div[role="textbox"] >> nth=-1')).toContainText(
'"statusCode": 200'
)
await page.click('text=Save in variables')
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('[data-testid="variables-input"] >> nth=-1')
await page.click(`text="${name}"`)
await page.fill('input >> nth=-1', value)
}

View File

@@ -0,0 +1,73 @@
import test, { expect, Page } from '@playwright/test'
import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
import { importTypebotInDatabase } from '../../services/database'
const typebotId = 'condition-step'
test.describe('Condition step', () => {
test('its configuration should work', async ({ page }) => {
await importTypebotInDatabase(
path.join(__dirname, '../../fixtures/typebots/logic/condition.json'),
{
id: typebotId,
}
)
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Configure...')
await page.fill('input[placeholder="Search for a variable"]', 'Age')
await page.click('button:has-text("Age")')
await page.click('button:has-text("Select an operator")')
await page.click('button:has-text("Greater than")', { force: true })
await page.fill('input[placeholder="Type a value..."]', '80')
await page.click('button:has-text("Add a comparison")')
await page.fill(
':nth-match(input[placeholder="Search for a variable"], 2)',
'Age'
)
await page.click('button:has-text("Age")')
await page.click('button:has-text("Select an operator")')
await page.click('button:has-text("Less than")', { force: true })
await page.fill(
':nth-match(input[placeholder="Type a value..."], 2)',
'100'
)
await page.click('text=Configure...')
await page.fill('input[placeholder="Search for a variable"]', 'Age')
await page.click('button:has-text("Age")')
await page.click('button:has-text("Select an operator")')
await page.click('button:has-text("Greater than")', { force: true })
await page.fill('input[placeholder="Type a value..."]', '20')
await page.click('text=Preview')
await typebotViewer(page)
.locator('input[placeholder="Type your answer..."]')
.fill('15')
await typebotViewer(page).locator('text=Send').click()
await expect(
typebotViewer(page).locator('text=You are younger than 20')
).toBeVisible()
await page.click('text=Restart')
await typebotViewer(page)
.locator('input[placeholder="Type your answer..."]')
.fill('45')
await typebotViewer(page).locator('text=Send').click()
await expect(
typebotViewer(page).locator('text=You are older than 20')
).toBeVisible()
await page.click('text=Restart')
await typebotViewer(page)
.locator('input[placeholder="Type your answer..."]')
.fill('90')
await typebotViewer(page).locator('text=Send').click()
await expect(
typebotViewer(page).locator('text=You are older than 80')
).toBeVisible()
})
})

View File

@@ -0,0 +1,37 @@
import test, { expect } from '@playwright/test'
import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
import { importTypebotInDatabase } from '../../services/database'
const typebotId = 'redirect-step'
test.describe('Redirect step', () => {
test('its configuration should work', async ({ page, context }) => {
await importTypebotInDatabase(
path.join(__dirname, '../../fixtures/typebots/logic/redirect.json'),
{
id: typebotId,
}
)
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Configure...')
await page.fill('input[placeholder="Type a URL..."]', 'google.com')
await page.click('text=Preview')
await typebotViewer(page).locator('text=Go to URL').click()
await expect(page).toHaveURL('https://www.google.com')
await page.goBack()
await page.click('text=Redirect to google.com')
await page.click('text=Open in new tab')
await page.click('text=Preview')
const [newPage] = await Promise.all([
context.waitForEvent('page'),
typebotViewer(page).locator('text=Go to URL').click(),
])
await newPage.waitForLoadState()
await expect(newPage).toHaveURL('https://www.google.com')
})
})

View File

@@ -0,0 +1,44 @@
import test, { expect } from '@playwright/test'
import path from 'path'
import { typebotViewer } from '../../services/selectorUtils'
import { importTypebotInDatabase } from '../../services/database'
const typebotId = 'set-variable-step'
test.describe('Set variable step', () => {
test('its configuration should work', async ({ page, context }) => {
await importTypebotInDatabase(
path.join(__dirname, '../../fixtures/typebots/logic/setVariable.json'),
{
id: typebotId,
}
)
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Type a number...')
await page.fill('input[placeholder="Select a variable"]', 'Num')
await page.click('text=Create "Num"')
await page.click('text=Click to edit... >> nth = 0')
await page.fill('input[placeholder="Select a variable"]', 'Total')
await page.click('text=Create "Total"')
await page.fill('textarea', '1000 * {{Num}}')
await page.click('text=Click to edit...')
await page.fill('input[placeholder="Select a variable"]', 'Custom var')
await page.click('text=Create "Custom var"')
await page.fill('textarea', 'Custom value')
await page.click('text=Preview')
await typebotViewer(page)
.locator('input[placeholder="Type a number..."]')
.fill('365')
await typebotViewer(page).locator('text=Send').click()
await expect(
typebotViewer(page).locator('text=Total: 365000')
).toBeVisible()
await expect(
typebotViewer(page).locator('text=Custom var: Custom value')
).toBeVisible()
})
})

View File

@@ -0,0 +1,102 @@
import test, { expect, Page } from '@playwright/test'
import { readFileSync } from 'fs'
import { InputStepType } from 'models'
import { parse } from 'papaparse'
import {
createResults,
createTypebots,
parseDefaultBlockWithStep,
} from '../services/database'
import { deleteButtonInConfirmDialog } from '../services/selectorUtils'
const typebotId = 'typebot-for-results'
test.describe('Results page', () => {
test('results should be deletable', async ({ page }) => {
await createTypebots([
{
id: typebotId,
...parseDefaultBlockWithStep({ type: InputStepType.TEXT }),
},
])
await createResults({ typebotId })
await page.goto(`/typebots/${typebotId}/results`)
await selectFirstResults(page)
await page.click('button:has-text("Delete2")')
await deleteButtonInConfirmDialog(page).click()
await expect(page.locator('text=content199')).toBeHidden()
await expect(page.locator('text=content198')).toBeHidden()
await page.check(':nth-match(input[type="checkbox"], 1)', { force: true })
await page.click('button:has-text("Delete198")')
await deleteButtonInConfirmDialog(page).click()
await expect(page.locator(':nth-match(tr, 2)')).toBeHidden()
})
test('submissions table should have infinite scroll', async ({ page }) => {
const scrollToBottom = () =>
page.evaluate(() => {
const tableWrapper = document.querySelector('.table-wrapper')
if (!tableWrapper) return
tableWrapper.scrollTo(0, tableWrapper.scrollHeight)
})
await createResults({ typebotId })
await page.goto(`/typebots/${typebotId}/results`)
await expect(page.locator('text=content199')).toBeVisible()
await expect(page.locator('text=content149')).toBeHidden()
await scrollToBottom()
await expect(page.locator('text=content149')).toBeVisible()
await expect(page.locator('text=content99')).toBeHidden()
await scrollToBottom()
await expect(page.locator('text=content99')).toBeVisible()
await expect(page.locator('text=content49')).toBeHidden()
await scrollToBottom()
await expect(page.locator('text=content49')).toBeVisible()
await expect(page.locator('text=content0')).toBeVisible()
})
test('should correctly export selection in CSV', async ({ page }) => {
await page.goto(`/typebots/${typebotId}/results`)
await selectFirstResults(page)
const [download] = await Promise.all([
page.waitForEvent('download'),
page.locator('button:has-text("Export2")').click(),
])
const path = await download.path()
expect(path).toBeDefined()
const file = readFileSync(path as string).toString()
const { data } = parse(file)
validateExportSelection(data)
await page.check(':nth-match(input[type="checkbox"], 1)', { force: true })
const [downloadAll] = await Promise.all([
page.waitForEvent('download'),
page.locator('button:has-text("Export200")').click(),
])
const pathAll = await downloadAll.path()
expect(pathAll).toBeDefined()
const fileAll = readFileSync(pathAll as string).toString()
const { data: dataAll } = parse(fileAll)
validateExportAll(dataAll)
})
})
const validateExportSelection = (data: unknown[]) => {
expect(data).toHaveLength(3)
expect((data[1] as unknown[])[1]).toBe('content199')
expect((data[2] as unknown[])[1]).toBe('content198')
}
const validateExportAll = (data: unknown[]) => {
expect(data).toHaveLength(201)
expect((data[1] as unknown[])[1]).toBe('content199')
expect((data[200] as unknown[])[1]).toBe('content0')
}
const selectFirstResults = async (page: Page) => {
await page.check(':nth-match(input[type="checkbox"], 2)', { force: true })
return page.check(':nth-match(input[type="checkbox"], 3)', { force: true })
}

View File

@@ -0,0 +1,91 @@
import test, { expect } from '@playwright/test'
import path from 'path'
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'
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/theme.json'),
{
id: typebotId,
}
)
await page.goto(`/typebots/${typebotId}/settings`)
await expect(
typebotViewer(page).locator('a:has-text("Made with Typebot")')
).toHaveAttribute('href', 'https://www.typebot.io/?utm_source=litebadge')
await page.click('button:has-text("General")')
await page.uncheck('input[type="checkbox"]', { force: true })
await expect(
typebotViewer(page).locator('a:has-text("Made with Typebot")')
).toBeHidden()
})
})
test.describe('Typing emulation', () => {
test('should be fillable', async ({ page }) => {
const typebotId = 'typing-emulation-typebot'
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/theme.json'),
{
id: typebotId,
}
)
await page.goto(`/typebots/${typebotId}/settings`)
await page.click('button:has-text("Typing emulation")')
await page.fill('[data-testid="speed"] input', '350')
await page.fill('[data-testid="max-delay"] input', '1.5')
await page.uncheck(':nth-match(input[type="checkbox"], 2)', {
force: true,
})
await expect(page.locator('[data-testid="speed"]')).toBeHidden()
await expect(page.locator('[data-testid="max-delay"]')).toBeHidden()
})
})
test.describe('Metadata', () => {
test('should be fillable', async ({ page }) => {
const favIconUrl = 'https://www.baptistearno.com/favicon.png'
const imageUrl = 'https://www.baptistearno.com/images/site-preview.png'
const typebotId = 'metadata-typebot'
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/theme.json'),
{
id: typebotId,
}
)
await page.goto(`/typebots/${typebotId}/settings`)
await page.click('button:has-text("Metadata")')
// Fav icon
const favIconImg = page.locator(':nth-match(img, 1)')
await expect(favIconImg).toHaveAttribute('src', '/favicon.png')
await favIconImg.click()
await expect(page.locator('text=Giphy')).toBeHidden()
await page.click('button:has-text("Embed link")')
await page.fill(
'input[placeholder="Paste the image link..."]',
favIconUrl
)
await expect(favIconImg).toHaveAttribute('src', favIconUrl)
// Website image
const websiteImg = page.locator(':nth-match(img, 2)')
await expect(websiteImg).toHaveAttribute('src', '/viewer-preview.png')
await websiteImg.click()
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)
await expect(websiteImg).toHaveAttribute('src', imageUrl)
// Title
await page.fill('input#title', 'Awesome typebot')
// Description
await page.fill('textarea#description', 'Lorem ipsum')
})
})
})

View File

@@ -0,0 +1,123 @@
import test, { expect } from '@playwright/test'
import path from 'path'
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 chatContainer = typebotViewer(page).locator(
'[data-testid="container"]'
)
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/theme.json'),
{
id: typebotId,
}
)
await page.goto(`/typebots/${typebotId}/theme`)
await page.click('button:has-text("General")')
// Font
await page.fill('input[type="text"]', 'Roboto Slab')
await expect(chatContainer).toHaveCSS('font-family', '"Roboto Slab"')
// BG color
await expect(chatContainer).toHaveCSS(
'background-color',
'rgba(0, 0, 0, 0)'
)
await page.click('text=Color')
await page.click('[aria-label="Pick a color"]')
await page.fill('[aria-label="Color value"]', '#2a9d8f')
await expect(chatContainer).toHaveCSS(
'background-color',
'rgb(42, 157, 143)'
)
})
})
test.describe('Chat', () => {
test('should reflect change in real-time', async ({ page }) => {
const typebotId = 'chat-theme-typebot'
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/theme.json'),
{
id: typebotId,
}
)
await page.goto(`/typebots/${typebotId}/theme`)
await page.click('button:has-text("Chat")')
// Host bubbles
await page.click(':nth-match([aria-label="Pick a color"], 1)')
await page.fill('[aria-label="Color value"]', '#2a9d8f')
await page.click(':nth-match([aria-label="Pick a color"], 2)')
await page.fill('[aria-label="Color value"]', '#ffffff')
const hostBubble = typebotViewer(page).locator(
'[data-testid="host-bubble"]'
)
await expect(hostBubble).toHaveCSS(
'background-color',
'rgb(42, 157, 143)'
)
await expect(hostBubble).toHaveCSS('color', 'rgb(255, 255, 255)')
// Buttons
await page.click(':nth-match([aria-label="Pick a color"], 5)')
await page.fill('[aria-label="Color value"]', '#7209b7')
await page.click(':nth-match([aria-label="Pick a color"], 6)')
await page.fill('[aria-label="Color value"]', '#e9c46a')
const button = typebotViewer(page).locator('[data-testid="button"]')
await expect(button).toHaveCSS('background-color', 'rgb(114, 9, 183)')
await expect(button).toHaveCSS('color', 'rgb(233, 196, 106)')
// Guest bubbles
await page.click(':nth-match([aria-label="Pick a color"], 3)')
await page.fill('[aria-label="Color value"]', '#d8f3dc')
await page.click(':nth-match([aria-label="Pick a color"], 4)')
await page.fill('[aria-label="Color value"]', '#264653')
await typebotViewer(page).locator('text=Go').click()
const guestBubble = typebotViewer(page).locator(
'[data-testid="guest-bubble"]'
)
await expect(guestBubble).toHaveCSS(
'background-color',
'rgb(216, 243, 220)'
)
await expect(guestBubble).toHaveCSS('color', 'rgb(38, 70, 83)')
// Input
await page.click(':nth-match([aria-label="Pick a color"], 7)')
await page.fill('[aria-label="Color value"]', '#ffe8d6')
await page.click(':nth-match([aria-label="Pick a color"], 8)')
await page.fill('[aria-label="Color value"]', '#023e8a')
await typebotViewer(page).locator('text=Go').click()
const input = typebotViewer(page).locator('.typebot-input')
await expect(input).toHaveCSS('background-color', 'rgb(255, 232, 214)')
await expect(input).toHaveCSS('color', 'rgb(2, 62, 138)')
})
})
test.describe('Custom CSS', () => {
test('should reflect change in real-time', async ({ page }) => {
const typebotId = 'custom-css-theme-typebot'
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/theme.json'),
{
id: typebotId,
}
)
await page.goto(`/typebots/${typebotId}/theme`)
await page.click('button:has-text("Custom CSS")')
await page.fill(
'div[role="textbox"]',
'.typebot-button {background-color: green}'
)
await expect(
typebotViewer(page).locator('[data-testid="button"]')
).toHaveCSS('background-color', 'rgb(0, 128, 0)')
})
})
})