chore(e2e): 👷 Fix e2e pipeline
This commit is contained in:
@ -37,7 +37,3 @@ STRIPE_WEBHOOK_SECRET=
|
|||||||
|
|
||||||
# (Optional) Used for GIF search
|
# (Optional) Used for GIF search
|
||||||
NEXT_PUBLIC_GIPHY_API_KEY=
|
NEXT_PUBLIC_GIPHY_API_KEY=
|
||||||
|
|
||||||
# (Optional) for e2e tests
|
|
||||||
PLAYWRIGHT_BUILDER_TEST_BASE_URL=http://localhost:3000
|
|
||||||
GOOGLE_REFRESH_TOKEN_TEST=
|
|
||||||
|
19
.github/workflows/playwright.yml
vendored
19
.github/workflows/playwright.yml
vendored
@ -14,11 +14,24 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
run: yarn
|
||||||
- name: Run tests
|
- name: Install Playwright
|
||||||
run: yarn turbo run test --scope=builder
|
run: npx playwright install --with-deps
|
||||||
|
- name: Build db package
|
||||||
|
working-directory: ./packages/db
|
||||||
|
run: yarn build
|
||||||
|
env:
|
||||||
|
DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
|
||||||
|
- name: Build models package
|
||||||
|
working-directory: ./packages/models
|
||||||
|
run: yarn build
|
||||||
|
- name: Run tests
|
||||||
|
working-directory: ./apps/builder
|
||||||
|
run: yarn test
|
||||||
env:
|
env:
|
||||||
GOOGLE_REFRESH_TOKEN_TEST: ${{ secrets.GOOGLE_REFRESH_TOKEN_TEST }}
|
|
||||||
PLAYWRIGHT_BUILDER_TEST_BASE_URL: ${{ github.event.deployment_status.target_url }}
|
PLAYWRIGHT_BUILDER_TEST_BASE_URL: ${{ github.event.deployment_status.target_url }}
|
||||||
|
GITHUB_EMAIL: ${{ secrets.PLAYWRIGHT_GITHUB_EMAIL }}
|
||||||
|
GITHUB_PASSWORD: ${{ secrets.PLAYWRIGHT_GITHUB_PASSWORD }}
|
||||||
|
DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
|
||||||
- name: Upload test results
|
- name: Upload test results
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
|
@ -15,6 +15,7 @@ import { UploadIcon } from 'assets/icons'
|
|||||||
import { UploadButton } from 'components/shared/buttons/UploadButton'
|
import { UploadButton } from 'components/shared/buttons/UploadButton'
|
||||||
import { useUser } from 'contexts/UserContext'
|
import { useUser } from 'contexts/UserContext'
|
||||||
import React, { ChangeEvent, useState } from 'react'
|
import React, { ChangeEvent, useState } from 'react'
|
||||||
|
import { isDefined } from 'utils'
|
||||||
|
|
||||||
export const PersonalInfoForm = () => {
|
export const PersonalInfoForm = () => {
|
||||||
const {
|
const {
|
||||||
@ -75,28 +76,29 @@ export const PersonalInfoForm = () => {
|
|||||||
onChange={handleNameChange}
|
onChange={handleNameChange}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Tooltip
|
{isDefined(user?.email) && (
|
||||||
label="Can't update the email because it is linked to an OAuth service"
|
<Tooltip
|
||||||
placement="left"
|
label="Updating email is not available."
|
||||||
hasArrow
|
placement="left"
|
||||||
isDisabled={!isOAuthProvider}
|
hasArrow
|
||||||
>
|
>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel
|
<FormLabel
|
||||||
htmlFor="email"
|
htmlFor="email"
|
||||||
color={isOAuthProvider ? 'gray.500' : 'current'}
|
color={isOAuthProvider ? 'gray.500' : 'current'}
|
||||||
>
|
>
|
||||||
Email address
|
Email address
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<Input
|
<Input
|
||||||
id="email"
|
id="email"
|
||||||
type="email"
|
type="email"
|
||||||
isDisabled={isOAuthProvider}
|
isDisabled
|
||||||
value={user?.email ?? ''}
|
value={user?.email ?? ''}
|
||||||
onChange={handleEmailChange}
|
onChange={handleEmailChange}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
{hasUnsavedChanges && (
|
{hasUnsavedChanges && (
|
||||||
<Flex justifyContent="flex-end">
|
<Flex justifyContent="flex-end">
|
||||||
|
@ -64,6 +64,7 @@ export const UserContext = ({ children }: { children: ReactNode }) => {
|
|||||||
if (status === 'loading') return
|
if (status === 'loading') return
|
||||||
if (status === 'unauthenticated' && !isSigningIn())
|
if (status === 'unauthenticated' && !isSigningIn())
|
||||||
router.replace('/signin')
|
router.replace('/signin')
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [status, router])
|
}, [status, router])
|
||||||
|
|
||||||
const isSigningIn = () => ['/signin', '/register'].includes(router.pathname)
|
const isSigningIn = () => ['/signin', '/register'].includes(router.pathname)
|
||||||
|
@ -7,12 +7,12 @@
|
|||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"test": "yarn playwright test",
|
"test": "dotenv -e ./playwright/.env -- yarn playwright test",
|
||||||
"test:open": "PWDEBUG=1 yarn playwright test"
|
"test:open": "dotenv -e ./playwright/.env -v PWDEBUG=1 -- yarn playwright test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/css-reset": "^1.1.1",
|
"@chakra-ui/css-reset": "^1.1.1",
|
||||||
"@chakra-ui/react": "^1.7.4",
|
"@chakra-ui/react": "^1.8.1",
|
||||||
"@codemirror/basic-setup": "^0.19.1",
|
"@codemirror/basic-setup": "^0.19.1",
|
||||||
"@codemirror/lang-css": "^0.19.3",
|
"@codemirror/lang-css": "^0.19.3",
|
||||||
"@codemirror/lang-json": "^0.19.1",
|
"@codemirror/lang-json": "^0.19.1",
|
||||||
@ -23,14 +23,14 @@
|
|||||||
"@giphy/js-types": "^4.1.0",
|
"@giphy/js-types": "^4.1.0",
|
||||||
"@giphy/react-components": "^5.4.0",
|
"@giphy/react-components": "^5.4.0",
|
||||||
"@googleapis/drive": "^2.1.0",
|
"@googleapis/drive": "^2.1.0",
|
||||||
"@next-auth/prisma-adapter": "next",
|
"@next-auth/prisma-adapter": "1.0.1",
|
||||||
"@udecode/plate-basic-marks": "^9.0.0",
|
"@udecode/plate-basic-marks": "^9.2.1",
|
||||||
"@udecode/plate-common": "^7.0.2",
|
"@udecode/plate-common": "^7.0.2",
|
||||||
"@udecode/plate-core": "^9.0.0",
|
"@udecode/plate-core": "^9.2.1",
|
||||||
"@udecode/plate-link": "^9.0.0",
|
"@udecode/plate-link": "^9.2.1",
|
||||||
"@udecode/plate-ui-link": "^9.0.0",
|
"@udecode/plate-ui-link": "^9.2.1",
|
||||||
"@udecode/plate-ui-toolbar": "^9.0.0",
|
"@udecode/plate-ui-toolbar": "^9.2.1",
|
||||||
"aws-sdk": "^2.1051.0",
|
"aws-sdk": "^2.1065.0",
|
||||||
"bot-engine": "*",
|
"bot-engine": "*",
|
||||||
"browser-image-compression": "^1.0.17",
|
"browser-image-compression": "^1.0.17",
|
||||||
"db": "*",
|
"db": "*",
|
||||||
@ -41,18 +41,18 @@
|
|||||||
"google-spreadsheet": "^3.2.0",
|
"google-spreadsheet": "^3.2.0",
|
||||||
"got": "^12.0.1",
|
"got": "^12.0.1",
|
||||||
"htmlparser2": "^7.2.0",
|
"htmlparser2": "^7.2.0",
|
||||||
"immer": "^9.0.7",
|
"immer": "^9.0.12",
|
||||||
"js-video-url-parser": "^0.5.1",
|
"js-video-url-parser": "^0.5.1",
|
||||||
"kbar": "^0.1.0-beta.24",
|
"kbar": "^0.1.0-beta.24",
|
||||||
"micro": "^9.3.4",
|
"micro": "^9.3.4",
|
||||||
"micro-cors": "^0.1.1",
|
"micro-cors": "^0.1.1",
|
||||||
"models": "*",
|
"models": "*",
|
||||||
"next": "^12.0.7",
|
"next": "^12.0.9",
|
||||||
"next-auth": "beta",
|
"next-auth": "4.1.2",
|
||||||
"nodemailer": "^6.7.2",
|
"nodemailer": "^6.7.2",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"papaparse": "^5.3.1",
|
"papaparse": "^5.3.1",
|
||||||
"qs": "^6.10.2",
|
"qs": "^6.10.3",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-frame-component": "^5.2.1",
|
"react-frame-component": "^5.2.1",
|
||||||
@ -61,17 +61,17 @@
|
|||||||
"slate": "^0.72.3",
|
"slate": "^0.72.3",
|
||||||
"slate-history": "^0.66.0",
|
"slate-history": "^0.66.0",
|
||||||
"slate-hyperscript": "^0.67.0",
|
"slate-hyperscript": "^0.67.0",
|
||||||
"slate-react": "^0.72.1",
|
"slate-react": "^0.72.7",
|
||||||
"stripe": "^8.195.0",
|
"stripe": "^8.200.0",
|
||||||
"styled-components": "^5.3.3",
|
"styled-components": "^5.3.3",
|
||||||
"svg-round-corners": "^0.3.0",
|
"svg-round-corners": "^0.3.0",
|
||||||
"swr": "^1.1.2",
|
"swr": "^1.2.0",
|
||||||
"use-debounce": "^7.0.1",
|
"use-debounce": "^7.0.1",
|
||||||
"use-immer": "^0.6.0",
|
"use-immer": "^0.6.0",
|
||||||
"utils": "*"
|
"utils": "*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.18.0",
|
"@playwright/test": "^1.18.1",
|
||||||
"@testing-library/cypress": "^8.0.2",
|
"@testing-library/cypress": "^8.0.2",
|
||||||
"@types/google-spreadsheet": "^3.1.5",
|
"@types/google-spreadsheet": "^3.1.5",
|
||||||
"@types/micro-cors": "^0.1.2",
|
"@types/micro-cors": "^0.1.2",
|
||||||
@ -82,17 +82,15 @@
|
|||||||
"@types/react": "^17.0.38",
|
"@types/react": "^17.0.38",
|
||||||
"@types/react-table": "^7.7.9",
|
"@types/react-table": "^7.7.9",
|
||||||
"@types/testing-library__cypress": "^5.0.9",
|
"@types/testing-library__cypress": "^5.0.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.9.0",
|
"@typescript-eslint/eslint-plugin": "^5.10.1",
|
||||||
"cypress": "^9.2.0",
|
"dotenv": "^14.3.2",
|
||||||
"cypress-file-upload": "^5.0.8",
|
|
||||||
"cypress-social-logins": "^1.13.0",
|
|
||||||
"eslint": "<8.0.0",
|
"eslint": "<8.0.0",
|
||||||
"eslint-config-next": "12.0.7",
|
"eslint-config-next": "12.0.9",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-cypress": "^2.12.1",
|
"eslint-plugin-cypress": "^2.12.1",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"next-transpile-modules": "^9.0.0",
|
"next-transpile-modules": "^9.0.0",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"typescript": "^4.5.4"
|
"typescript": "^4.5.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import NextAuth, { NextAuthOptions } from 'next-auth'
|
import NextAuth from 'next-auth'
|
||||||
import { PrismaAdapter } from '@next-auth/prisma-adapter'
|
import { PrismaAdapter } from '@next-auth/prisma-adapter'
|
||||||
import EmailProvider from 'next-auth/providers/email'
|
import EmailProvider from 'next-auth/providers/email'
|
||||||
import GitHubProvider from 'next-auth/providers/github'
|
import GitHubProvider from 'next-auth/providers/github'
|
||||||
import GoogleProvider from 'next-auth/providers/google'
|
import GoogleProvider from 'next-auth/providers/google'
|
||||||
import FacebookProvider from 'next-auth/providers/facebook'
|
import FacebookProvider from 'next-auth/providers/facebook'
|
||||||
import CredentialsProvider from 'next-auth/providers/credentials'
|
|
||||||
import prisma from 'libs/prisma'
|
import prisma from 'libs/prisma'
|
||||||
import { Provider } from 'next-auth/providers'
|
import { Provider } from 'next-auth/providers'
|
||||||
import { User } from 'db'
|
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
const providers: Provider[] = [
|
const providers: Provider[] = [
|
||||||
@ -48,54 +46,18 @@ if (process.env.FACEBOOK_CLIENT_ID && process.env.FACEBOOK_CLIENT_SECRET)
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production')
|
|
||||||
providers.push(
|
|
||||||
CredentialsProvider({
|
|
||||||
name: 'Credentials',
|
|
||||||
credentials: {
|
|
||||||
email: {
|
|
||||||
label: 'Email',
|
|
||||||
type: 'email',
|
|
||||||
placeholder: 'credentials@email.com',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
async authorize(credentials) {
|
|
||||||
const user = await prisma.user.findUnique({
|
|
||||||
where: { email: credentials?.email },
|
|
||||||
})
|
|
||||||
return user
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const createOptions = (req: NextApiRequest): NextAuthOptions => ({
|
|
||||||
adapter: PrismaAdapter(prisma),
|
|
||||||
secret: process.env.SECRET,
|
|
||||||
providers,
|
|
||||||
session: {
|
|
||||||
strategy: process.env.NODE_ENV === 'production' ? 'database' : 'jwt',
|
|
||||||
},
|
|
||||||
callbacks: {
|
|
||||||
jwt: async ({ token, user, account }) => {
|
|
||||||
if (req.url === '/api/auth/session?update' && token.user) {
|
|
||||||
token.user = await prisma.user.findUnique({
|
|
||||||
where: { id: (token.user as User).id },
|
|
||||||
})
|
|
||||||
} else if (user) {
|
|
||||||
token.user = user
|
|
||||||
}
|
|
||||||
account?.type && token && (token.providerType = account?.type)
|
|
||||||
return token
|
|
||||||
},
|
|
||||||
session: async ({ session, token, user }) => {
|
|
||||||
token?.user ? (session.user = token.user as User) : (session.user = user)
|
|
||||||
if (token?.providerType) session.providerType = token.providerType
|
|
||||||
return session
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
NextAuth(req, res, createOptions(req))
|
NextAuth(req, res, {
|
||||||
|
adapter: PrismaAdapter(prisma),
|
||||||
|
secret: process.env.SECRET,
|
||||||
|
providers,
|
||||||
|
session: {
|
||||||
|
strategy: 'database',
|
||||||
|
},
|
||||||
|
callbacks: {
|
||||||
|
session: async ({ session, user }) => ({ ...session, user }),
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default handler
|
export default handler
|
||||||
|
@ -8,7 +8,7 @@ const config: PlaywrightTestConfig = {
|
|||||||
expect: {
|
expect: {
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
},
|
},
|
||||||
retries: process.env.CI ? 2 : 0,
|
retries: 2,
|
||||||
workers: process.env.CI ? 1 : undefined,
|
workers: process.env.CI ? 1 : undefined,
|
||||||
reporter: 'html',
|
reporter: 'html',
|
||||||
maxFailures: process.env.CI ? 10 : undefined,
|
maxFailures: process.env.CI ? 10 : undefined,
|
||||||
@ -18,6 +18,7 @@ const config: PlaywrightTestConfig = {
|
|||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
storageState: path.join(__dirname, 'playwright/authenticatedState.json'),
|
storageState: path.join(__dirname, 'playwright/authenticatedState.json'),
|
||||||
video: 'retain-on-failure',
|
video: 'retain-on-failure',
|
||||||
|
locale: 'en-US',
|
||||||
},
|
},
|
||||||
outputDir: path.join(__dirname, 'playwright/test-results/'),
|
outputDir: path.join(__dirname, 'playwright/test-results/'),
|
||||||
projects: [
|
projects: [
|
||||||
|
5
apps/builder/playwright/.env.example
Normal file
5
apps/builder/playwright/.env.example
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
PLAYWRIGHT_BUILDER_TEST_BASE_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# For auth
|
||||||
|
GITHUB_EMAIL=
|
||||||
|
GITHUB_PASSWORD=
|
@ -1,31 +1,43 @@
|
|||||||
import { chromium, FullConfig, Page } from '@playwright/test'
|
import { chromium, FullConfig, Page } from '@playwright/test'
|
||||||
import { setupDatabase, teardownDatabase, user } from './services/database'
|
import { setupDatabase, teardownDatabase } from './services/database'
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
require('dotenv').config({ path: '.env' })
|
||||||
|
|
||||||
async function globalSetup(config: FullConfig) {
|
async function globalSetup(config: FullConfig) {
|
||||||
const { baseURL } = config.projects[0].use
|
const { baseURL } = config.projects[0].use
|
||||||
if (!baseURL) throw new Error('baseURL is missing')
|
if (!baseURL) throw new Error('baseURL is missing')
|
||||||
|
|
||||||
await teardownDatabase()
|
await teardownDatabase()
|
||||||
await setupDatabase()
|
|
||||||
|
|
||||||
// Skip auth if debugging
|
|
||||||
if (process.env.PWDEBUG === '1') return
|
|
||||||
|
|
||||||
const browser = await chromium.launch()
|
const browser = await chromium.launch()
|
||||||
const page = await browser.newPage()
|
const page = await browser.newPage()
|
||||||
await signIn(page, user.email)
|
await signIn(page)
|
||||||
await page.context().storageState({
|
await page.context().storageState({
|
||||||
path: './playwright/authenticatedState.json',
|
path: './playwright/authenticatedState.json',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await setupDatabase(process.env.GITHUB_EMAIL as string)
|
||||||
}
|
}
|
||||||
|
|
||||||
const signIn = async (page: Page, email: string) => {
|
const signIn = async (page: Page) => {
|
||||||
await page.goto('http://localhost:3000/api/auth/signin')
|
if (!process.env.GITHUB_EMAIL || !process.env.GITHUB_PASSWORD)
|
||||||
await page.fill('[placeholder="credentials\\@email\\.com"]', email)
|
throw new Error(
|
||||||
await Promise.all([
|
'GITHUB_USERNAME or GITHUB_PASSWORD are missing in the environment. They are required to log in.'
|
||||||
page.waitForNavigation({ url: 'http://localhost:3000/typebots' }),
|
)
|
||||||
page.press('[placeholder="credentials\\@email\\.com"]', 'Enter'),
|
await page.goto(`${process.env.PLAYWRIGHT_BUILDER_TEST_BASE_URL}/signin`)
|
||||||
])
|
await page.click('text=Continue with GitHub')
|
||||||
|
await page.fill('input[name="login"]', process.env.GITHUB_EMAIL)
|
||||||
|
await page.fill('input[name="password"]', process.env.GITHUB_PASSWORD)
|
||||||
|
await page.press('input[name="password"]', 'Enter')
|
||||||
|
try {
|
||||||
|
await page.locator('text=Authorize baptisteArno').click({ timeout: 3000 })
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await page.waitForNavigation({
|
||||||
|
url: `${process.env.PLAYWRIGHT_BUILDER_TEST_BASE_URL}/typebots`,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default globalSetup
|
export default globalSetup
|
||||||
|
@ -5,20 +5,23 @@ import {
|
|||||||
Step,
|
Step,
|
||||||
Typebot,
|
Typebot,
|
||||||
} from 'models'
|
} from 'models'
|
||||||
import { CredentialsType, DashboardFolder, Plan, PrismaClient, User } from 'db'
|
import { CredentialsType, DashboardFolder, PrismaClient, User } from 'db'
|
||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
|
|
||||||
export const user = { id: 'user1', email: 'test1@gmail.com' }
|
|
||||||
|
|
||||||
const prisma = new PrismaClient()
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
export const teardownDatabase = async () => prisma.user.deleteMany()
|
export const teardownDatabase = async () => prisma.user.deleteMany()
|
||||||
|
|
||||||
export const setupDatabase = async () => {
|
export const setupDatabase = async (userEmail: string) => {
|
||||||
await createUsers()
|
const createdUser = await getSignedInUser(userEmail)
|
||||||
|
if (!createdUser) throw new Error("Couldn't find user")
|
||||||
|
process.env.PLAYWRIGHT_USER_ID = createdUser.id
|
||||||
return createCredentials()
|
return createCredentials()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getSignedInUser = (email: string) =>
|
||||||
|
prisma.user.findFirst({ where: { email } })
|
||||||
|
|
||||||
export const createTypebots = async (partialTypebots: Partial<Typebot>[]) => {
|
export const createTypebots = async (partialTypebots: Partial<Typebot>[]) => {
|
||||||
await prisma.typebot.createMany({
|
await prisma.typebot.createMany({
|
||||||
data: partialTypebots.map(parseTestTypebot) as any[],
|
data: partialTypebots.map(parseTestTypebot) as any[],
|
||||||
@ -33,47 +36,37 @@ export const createTypebots = async (partialTypebots: Partial<Typebot>[]) => {
|
|||||||
export const createFolders = (partialFolders: Partial<DashboardFolder>[]) =>
|
export const createFolders = (partialFolders: Partial<DashboardFolder>[]) =>
|
||||||
prisma.dashboardFolder.createMany({
|
prisma.dashboardFolder.createMany({
|
||||||
data: partialFolders.map((folder) => ({
|
data: partialFolders.map((folder) => ({
|
||||||
ownerId: user.id,
|
ownerId: process.env.PLAYWRIGHT_USER_ID as string,
|
||||||
name: 'Folder #1',
|
name: 'Folder #1',
|
||||||
id: 'folder',
|
id: 'folder',
|
||||||
...folder,
|
...folder,
|
||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
|
|
||||||
const createUsers = () =>
|
const createCredentials = () =>
|
||||||
prisma.user.create({
|
prisma.credentials.createMany({
|
||||||
data: {
|
|
||||||
...user,
|
|
||||||
emailVerified: new Date(),
|
|
||||||
plan: Plan.FREE,
|
|
||||||
stripeId: 'stripe-test2',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const createCredentials = () => {
|
|
||||||
if (!process.env.GOOGLE_REFRESH_TOKEN_TEST)
|
|
||||||
console.warn(
|
|
||||||
'GOOGLE_REFRESH_TOKEN_TEST env var is missing. It is required to run Google Sheets tests'
|
|
||||||
)
|
|
||||||
return prisma.credentials.createMany({
|
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
name: 'test2@gmail.com',
|
name: 'test2@gmail.com',
|
||||||
ownerId: user.id,
|
ownerId: process.env.PLAYWRIGHT_USER_ID as string,
|
||||||
type: CredentialsType.GOOGLE_SHEETS,
|
type: CredentialsType.GOOGLE_SHEETS,
|
||||||
data: {
|
data: {
|
||||||
expiry_date: 1642441058842,
|
expiry_date: 1642441058842,
|
||||||
access_token:
|
access_token:
|
||||||
'ya29.A0ARrdaM--PV_87ebjywDJpXKb77NBFJl16meVUapYdfNv6W6ZzqqC47fNaPaRjbDbOIIcp6f49cMaX5ndK9TAFnKwlVqz3nrK9nLKqgyDIhYsIq47smcAIZkK56SWPx3X3DwAFqRu2UPojpd2upWwo-3uJrod',
|
'ya29.A0ARrdaM--PV_87ebjywDJpXKb77NBFJl16meVUapYdfNv6W6ZzqqC47fNaPaRjbDbOIIcp6f49cMaX5ndK9TAFnKwlVqz3nrK9nLKqgyDIhYsIq47smcAIZkK56SWPx3X3DwAFqRu2UPojpd2upWwo-3uJrod',
|
||||||
refresh_token: process.env.GOOGLE_REFRESH_TOKEN_TEST,
|
// This token is linked to a mock Google account (typebot.test.user@gmail.com)
|
||||||
|
refresh_token:
|
||||||
|
'1//03W5-TyIxXd7nCgYIARAAGAMSNwF-L9IrAGAmp5MG8RqVyk6YYmqDDn9x-4nHTkSUj4xZWuMs6mNeyjdS_bgO0CWuZEfJoAd_zIw',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
export const updateUser = (data: Partial<User>) =>
|
export const updateUser = (data: Partial<User>) =>
|
||||||
prisma.user.update({ data, where: { id: user.id } })
|
prisma.user.update({
|
||||||
|
data,
|
||||||
|
where: { id: process.env.PLAYWRIGHT_USER_ID as string },
|
||||||
|
})
|
||||||
|
|
||||||
export const createResults = async ({ typebotId }: { typebotId: string }) => {
|
export const createResults = async ({ typebotId }: { typebotId: string }) => {
|
||||||
await prisma.result.createMany({
|
await prisma.result.createMany({
|
||||||
@ -129,7 +122,7 @@ export const loadRawTypebotInDatabase = (typebot: Typebot) =>
|
|||||||
data: {
|
data: {
|
||||||
...typebot,
|
...typebot,
|
||||||
id: 'typebot4',
|
id: 'typebot4',
|
||||||
ownerId: user.id,
|
ownerId: process.env.PLAYWRIGHT_USER_ID,
|
||||||
} as any,
|
} as any,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -137,7 +130,7 @@ const parseTestTypebot = (partialTypebot: Partial<Typebot>): Typebot => ({
|
|||||||
id: partialTypebot.id ?? 'typebot',
|
id: partialTypebot.id ?? 'typebot',
|
||||||
folderId: null,
|
folderId: null,
|
||||||
name: 'My typebot',
|
name: 'My typebot',
|
||||||
ownerId: user.id,
|
ownerId: process.env.PLAYWRIGHT_USER_ID as string,
|
||||||
theme: defaultTheme,
|
theme: defaultTheme,
|
||||||
settings: defaultSettings,
|
settings: defaultSettings,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
@ -224,7 +217,13 @@ export const importTypebotInDatabase = (
|
|||||||
updates?: Partial<Typebot>
|
updates?: Partial<Typebot>
|
||||||
) => {
|
) => {
|
||||||
const typebot: Typebot = JSON.parse(readFileSync(path).toString())
|
const typebot: Typebot = JSON.parse(readFileSync(path).toString())
|
||||||
return prisma.typebot.create({
|
try {
|
||||||
data: { ...typebot, ...updates, ownerId: user.id } as any,
|
return prisma.typebot.create({
|
||||||
})
|
data: {
|
||||||
|
...typebot,
|
||||||
|
...updates,
|
||||||
|
ownerId: process.env.PLAYWRIGHT_USER_ID,
|
||||||
|
} as any,
|
||||||
|
})
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import test, { expect } from '@playwright/test'
|
|||||||
import { refreshUser } from '../services/browser'
|
import { refreshUser } from '../services/browser'
|
||||||
import { Plan } from 'db'
|
import { Plan } from 'db'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { updateUser, user } from '../services/database'
|
import { updateUser } from '../services/database'
|
||||||
|
|
||||||
test.describe('Account page', () => {
|
test.describe('Account page', () => {
|
||||||
test('should edit user info properly', async ({ page }) => {
|
test('should edit user info properly', async ({ page }) => {
|
||||||
@ -14,17 +14,14 @@ test.describe('Account page', () => {
|
|||||||
).toBeDefined()
|
).toBeDefined()
|
||||||
await page.fill('#name', 'John Doe')
|
await page.fill('#name', 'John Doe')
|
||||||
expect(saveButton).toBeVisible()
|
expect(saveButton).toBeVisible()
|
||||||
const avatarImg = page.locator('img')
|
|
||||||
await expect(page.locator('text=JD')).toBeVisible()
|
|
||||||
await expect(avatarImg).toBeHidden()
|
|
||||||
await page.setInputFiles(
|
await page.setInputFiles(
|
||||||
'input[type="file"]',
|
'input[type="file"]',
|
||||||
path.join(__dirname, '../fixtures/avatar.jpg')
|
path.join(__dirname, '../fixtures/avatar.jpg')
|
||||||
)
|
)
|
||||||
await expect(avatarImg).toHaveAttribute(
|
await expect(page.locator('img')).toHaveAttribute(
|
||||||
'src',
|
'src',
|
||||||
new RegExp(
|
new RegExp(
|
||||||
`https://s3.eu-west-3.amazonaws.com/typebot/users/${user.id}/avatar`,
|
`https://s3.eu-west-3.amazonaws.com/typebot/users/${process.env.PLAYWRIGHT_USER_ID}/avatar`,
|
||||||
'gm'
|
'gm'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -43,7 +40,6 @@ test.describe('Account page', () => {
|
|||||||
)
|
)
|
||||||
await expect(manageSubscriptionButton).toBeHidden()
|
await expect(manageSubscriptionButton).toBeHidden()
|
||||||
await updateUser({ plan: Plan.PRO, stripeId: 'stripeId' })
|
await updateUser({ plan: Plan.PRO, stripeId: 'stripeId' })
|
||||||
await page.evaluate(refreshUser)
|
|
||||||
await page.reload()
|
await page.reload()
|
||||||
await expect(page.locator('text=Pro plan')).toBeVisible()
|
await expect(page.locator('text=Pro plan')).toBeVisible()
|
||||||
await expect(manageSubscriptionButton).toBeVisible()
|
await expect(manageSubscriptionButton).toBeVisible()
|
||||||
|
@ -16,7 +16,10 @@ test.describe('Condition step', () => {
|
|||||||
|
|
||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
await page.click('text=Configure...')
|
await page.click('text=Configure...')
|
||||||
await page.fill('input[placeholder="Search for a variable"]', 'Age')
|
await page.fill(
|
||||||
|
'input[placeholder="Search for a variable"] >> nth=-1',
|
||||||
|
'Age'
|
||||||
|
)
|
||||||
await page.click('button:has-text("Age")')
|
await page.click('button:has-text("Age")')
|
||||||
await page.click('button:has-text("Select an operator")')
|
await page.click('button:has-text("Select an operator")')
|
||||||
await page.click('button:has-text("Greater than")', { force: true })
|
await page.click('button:has-text("Greater than")', { force: true })
|
||||||
@ -37,7 +40,10 @@ test.describe('Condition step', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
await page.click('text=Configure...')
|
await page.click('text=Configure...')
|
||||||
await page.fill('input[placeholder="Search for a variable"]', 'Age')
|
await page.fill(
|
||||||
|
'input[placeholder="Search for a variable"] >> nth=-1',
|
||||||
|
'Age'
|
||||||
|
)
|
||||||
await page.click('button:has-text("Age")')
|
await page.click('button:has-text("Age")')
|
||||||
await page.click('button:has-text("Select an operator")')
|
await page.click('button:has-text("Select an operator")')
|
||||||
await page.click('button:has-text("Greater than")', { force: true })
|
await page.click('button:has-text("Greater than")', { force: true })
|
||||||
|
@ -6,7 +6,7 @@ import { importTypebotInDatabase } from '../../services/database'
|
|||||||
const typebotId = 'set-variable-step'
|
const typebotId = 'set-variable-step'
|
||||||
|
|
||||||
test.describe('Set variable step', () => {
|
test.describe('Set variable step', () => {
|
||||||
test('its configuration should work', async ({ page, context }) => {
|
test('its configuration should work', async ({ page }) => {
|
||||||
await importTypebotInDatabase(
|
await importTypebotInDatabase(
|
||||||
path.join(__dirname, '../../fixtures/typebots/logic/setVariable.json'),
|
path.join(__dirname, '../../fixtures/typebots/logic/setVariable.json'),
|
||||||
{
|
{
|
||||||
@ -16,16 +16,19 @@ test.describe('Set variable step', () => {
|
|||||||
|
|
||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
await page.click('text=Type a number...')
|
await page.click('text=Type a number...')
|
||||||
await page.fill('input[placeholder="Select a variable"]', 'Num')
|
await page.fill('input[placeholder="Select a variable"] >> nth=-1', 'Num')
|
||||||
await page.click('text=Create "Num"')
|
await page.click('text=Create "Num"')
|
||||||
|
|
||||||
await page.click('text=Click to edit... >> nth = 0')
|
await page.click('text=Click to edit... >> nth = 0')
|
||||||
await page.fill('input[placeholder="Select a variable"]', 'Total')
|
await page.fill('input[placeholder="Select a variable"] >> nth=-1', 'Total')
|
||||||
await page.click('text=Create "Total"')
|
await page.click('text=Create "Total"')
|
||||||
await page.fill('textarea', '1000 * {{Num}}')
|
await page.fill('textarea', '1000 * {{Num}}')
|
||||||
|
|
||||||
await page.click('text=Click to edit...')
|
await page.click('text=Click to edit...')
|
||||||
await page.fill('input[placeholder="Select a variable"]', 'Custom var')
|
await page.fill(
|
||||||
|
'input[placeholder="Select a variable"] >> nth=-1',
|
||||||
|
'Custom var'
|
||||||
|
)
|
||||||
await page.click('text=Create "Custom var"')
|
await page.click('text=Create "Custom var"')
|
||||||
await page.fill('textarea', 'Custom value')
|
await page.fill('textarea', 'Custom value')
|
||||||
|
|
||||||
|
@ -41,14 +41,18 @@ test.describe.parallel('Theme page', () => {
|
|||||||
test.describe('Chat', () => {
|
test.describe('Chat', () => {
|
||||||
test('should reflect change in real-time', async ({ page }) => {
|
test('should reflect change in real-time', async ({ page }) => {
|
||||||
const typebotId = 'chat-theme-typebot'
|
const typebotId = 'chat-theme-typebot'
|
||||||
await importTypebotInDatabase(
|
try {
|
||||||
path.join(__dirname, '../fixtures/typebots/theme.json'),
|
await importTypebotInDatabase(
|
||||||
{
|
path.join(__dirname, '../fixtures/typebots/theme.json'),
|
||||||
id: typebotId,
|
{
|
||||||
}
|
id: typebotId,
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
} catch {}
|
||||||
|
|
||||||
await page.goto(`/typebots/${typebotId}/theme`)
|
await page.goto(`/typebots/${typebotId}/theme`)
|
||||||
await page.click('button:has-text("Chat")')
|
await page.click('button:has-text("Chat")')
|
||||||
|
await page.waitForTimeout(300)
|
||||||
|
|
||||||
// Host bubbles
|
// Host bubbles
|
||||||
await page.click(':nth-match([aria-label="Pick a color"], 1)')
|
await page.click(':nth-match([aria-label="Pick a color"], 1)')
|
||||||
|
26
package.json
26
package.json
@ -11,34 +11,12 @@
|
|||||||
"db:nuke": "docker-compose down --volumes --remove-orphans",
|
"db:nuke": "docker-compose down --volumes --remove-orphans",
|
||||||
"dev": "dotenv -e .env yarn docker:up && dotenv -e .env turbo run dev --parallel",
|
"dev": "dotenv -e .env yarn docker:up && dotenv -e .env turbo run dev --parallel",
|
||||||
"build": "dotenv -e .env turbo run build",
|
"build": "dotenv -e .env turbo run build",
|
||||||
"test": "dotenv -e .env turbo run test",
|
"test:builder": "cd apps/builder && yarn test",
|
||||||
"lint": "turbo run lint"
|
"lint": "turbo run lint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dotenv-cli": "^4.1.1",
|
"dotenv-cli": "^4.1.1",
|
||||||
"turbo": "^1.1.1"
|
"turbo": "^1.1.1"
|
||||||
},
|
},
|
||||||
"turbo": {
|
"packageManager": "yarn@1.22.17"
|
||||||
"baseBranch": "origin/main",
|
|
||||||
"pipeline": {
|
|
||||||
"build": {
|
|
||||||
"dependsOn": [
|
|
||||||
"^build"
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
".next/**"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"dependsOn": [],
|
|
||||||
"outputs": []
|
|
||||||
},
|
|
||||||
"lint": {
|
|
||||||
"outputs": []
|
|
||||||
},
|
|
||||||
"dev": {
|
|
||||||
"cache": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
"models": "*",
|
"models": "*",
|
||||||
"qs": "^6.10.3",
|
"qs": "^6.10.3",
|
||||||
"react-frame-component": "5.2.2-alpha.0",
|
"react-frame-component": "5.2.2-alpha.0",
|
||||||
"react-phone-number-input": "^3.1.44",
|
"react-phone-number-input": "^3.1.45",
|
||||||
"react-scroll": "^1.8.4",
|
"react-scroll": "^1.8.4",
|
||||||
"react-transition-group": "^4.4.2",
|
"react-transition-group": "^4.4.2",
|
||||||
"utils": "*"
|
"utils": "*"
|
||||||
@ -25,19 +25,19 @@
|
|||||||
"@types/react-phone-number-input": "^3.0.13",
|
"@types/react-phone-number-input": "^3.0.13",
|
||||||
"@types/react-scroll": "^1.8.3",
|
"@types/react-scroll": "^1.8.3",
|
||||||
"@types/react-transition-group": "^4.4.4",
|
"@types/react-transition-group": "^4.4.4",
|
||||||
"autoprefixer": "^10.4.1",
|
"autoprefixer": "^10.4.2",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"postcss": "^8.4.5",
|
"postcss": "^8.4.5",
|
||||||
"rollup": "^2.63.0",
|
"rollup": "^2.66.1",
|
||||||
"rollup-plugin-dts": "^4.1.0",
|
"rollup-plugin-dts": "^4.1.0",
|
||||||
"rollup-plugin-peer-deps-external": "^2.2.4",
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
||||||
"rollup-plugin-postcss": "^4.0.2",
|
"rollup-plugin-postcss": "^4.0.2",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"tailwindcss": "^3.0.11",
|
"tailwindcss": "^3.0.17",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.9.0",
|
"@typescript-eslint/eslint-plugin": "^5.10.1",
|
||||||
"eslint": "<8.0.0",
|
"eslint": "<8.0.0",
|
||||||
"eslint-config-next": "12.0.7",
|
"eslint-config-next": "12.0.9",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-cypress": "^2.12.1",
|
"eslint-plugin-cypress": "^2.12.1",
|
||||||
"eslint-plugin-prettier": "^4.0.0"
|
"eslint-plugin-prettier": "^4.0.0"
|
||||||
|
@ -6,10 +6,10 @@
|
|||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^4.5.4"
|
"typescript": "^4.5.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "^12.0.7",
|
"next": "^12.0.9",
|
||||||
"db": "*"
|
"db": "*"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -10,14 +10,14 @@
|
|||||||
"@rollup/plugin-commonjs": "^21.0.1",
|
"@rollup/plugin-commonjs": "^21.0.1",
|
||||||
"@rollup/plugin-node-resolve": "^13.1.3",
|
"@rollup/plugin-node-resolve": "^13.1.3",
|
||||||
"@rollup/plugin-typescript": "^8.3.0",
|
"@rollup/plugin-typescript": "^8.3.0",
|
||||||
"rollup": "^2.63.0",
|
"rollup": "^2.66.1",
|
||||||
"rollup-plugin-dts": "^4.1.0",
|
"rollup-plugin-dts": "^4.1.0",
|
||||||
"rollup-plugin-peer-deps-external": "^2.2.4",
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
||||||
"typescript": "^4.5.4"
|
"typescript": "^4.5.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"models": "*",
|
"models": "*",
|
||||||
"next": "^12.0.7"
|
"next": "^12.0.9"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "yarn rollup -c",
|
"build": "yarn rollup -c",
|
||||||
|
23
turbo.json
Normal file
23
turbo.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"baseBranch": "origin/main",
|
||||||
|
"pipeline": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": [
|
||||||
|
"^build"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
".next/**"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"dependsOn": [],
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"cache": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user