2
0

(variables) Add session option in variables (#1490)

Closes #1488
This commit is contained in:
Baptiste Arnaud
2024-05-02 15:11:16 +02:00
committed by GitHub
parent 87653f8e10
commit b4ae098440
20 changed files with 156 additions and 82 deletions

View File

@ -11,13 +11,6 @@
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.tabSize": 2, "editor.tabSize": 2,
"typescript.updateImportsOnFileMove.enabled": "always", "typescript.updateImportsOnFileMove.enabled": "always",
"playwright.env": {
"DATABASE_URL": "postgresql://postgres:typebot@127.0.0.1:5432/typebot",
"NEXT_PUBLIC_VIEWER_URL": "http://localhost:3001",
"NEXTAUTH_URL": "http://localhost:3000",
"ENCRYPTION_SECRET": "H+KbL/OFrqbEuDy/1zX8bsPG+spXri3S",
"S3_ENDPOINT": "http://localhost:9000"
},
"[prisma]": { "[prisma]": {
"editor.defaultFormatter": "Prisma.prisma" "editor.defaultFormatter": "Prisma.prisma"
} }

View File

@ -99,7 +99,7 @@
}, },
"devDependencies": { "devDependencies": {
"@chakra-ui/styled-system": "2.9.1", "@chakra-ui/styled-system": "2.9.1",
"@playwright/test": "1.41.2", "@playwright/test": "1.43.1",
"@typebot.io/billing": "workspace:*", "@typebot.io/billing": "workspace:*",
"@typebot.io/forge": "workspace:*", "@typebot.io/forge": "workspace:*",
"@typebot.io/forge-repository": "workspace:*", "@typebot.io/forge-repository": "workspace:*",
@ -123,12 +123,13 @@
"@types/qs": "6.9.7", "@types/qs": "6.9.7",
"@types/react": "18.2.15", "@types/react": "18.2.15",
"@types/tinycolor2": "1.4.3", "@types/tinycolor2": "1.4.3",
"dotenv-cli": "7.2.1", "dotenv-cli": "7.4.1",
"eslint": "8.44.0", "eslint": "8.44.0",
"eslint-config-custom": "workspace:*", "eslint-config-custom": "workspace:*",
"next-runtime-env": "1.6.2", "next-runtime-env": "1.6.2",
"superjson": "1.12.4", "superjson": "1.12.4",
"typescript": "5.4.5", "typescript": "5.4.5",
"zod": "3.22.4" "zod": "3.22.4",
"dotenv": "16.4.5"
} }
} }

View File

@ -1,4 +1,8 @@
import { defineConfig, devices } from '@playwright/test' import { defineConfig, devices } from '@playwright/test'
import { resolve } from 'path'
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('dotenv').config({ path: resolve(__dirname, '../../.env') })
export default defineConfig({ export default defineConfig({
timeout: process.env.CI ? 50 * 1000 : 40 * 1000, timeout: process.env.CI ? 50 * 1000 : 40 * 1000,

View File

@ -6,8 +6,9 @@ import {
Switch, Switch,
SwitchProps, SwitchProps,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import React, { useState } from 'react' import React, { useEffect, useState } from 'react'
import { MoreInfoTooltip } from '../MoreInfoTooltip' import { MoreInfoTooltip } from '../MoreInfoTooltip'
import { isDefined } from '@typebot.io/lib'
export type SwitchWithLabelProps = { export type SwitchWithLabelProps = {
label: string label: string
@ -32,6 +33,11 @@ export const SwitchWithLabel = ({
if (onCheckChange) onCheckChange(!isChecked) if (onCheckChange) onCheckChange(!isChecked)
} }
useEffect(() => {
if (isChecked === undefined && isDefined(initialValue))
setIsChecked(initialValue)
}, [initialValue, isChecked])
return ( return (
<FormControl as={HStack} justifyContent={justifyContent}> <FormControl as={HStack} justifyContent={justifyContent}>
<FormLabel mb="0"> <FormLabel mb="0">

View File

@ -123,7 +123,7 @@ export const VariableSearchInput = ({
if (!inputValue || inputValue === '') return if (!inputValue || inputValue === '') return
const id = 'v' + createId() const id = 'v' + createId()
onSelectVariable({ id, name: inputValue }) onSelectVariable({ id, name: inputValue })
createVariable({ id, name: inputValue }) createVariable({ id, name: inputValue, isSessionVariable: true })
inputRef.current?.blur() inputRef.current?.blur()
onClose() onClose()
} }

View File

@ -24,7 +24,7 @@ test.describe('Date input block', () => {
'date' 'date'
) )
await page.locator('[data-testid="from-date"]').fill('2021-01-01') await page.locator('[data-testid="from-date"]').fill('2021-01-01')
await page.getByRole('button', { name: 'Send' }).click() await page.locator('form').getByRole('button').click()
await expect(page.locator('text="01/01/2021"')).toBeVisible() await expect(page.locator('text="01/01/2021"')).toBeVisible()
await page.click(`text=Pick a date`) await page.click(`text=Pick a date`)

View File

@ -32,7 +32,7 @@ test('options should work', async ({ page }) => {
await page.click('text=Test') await page.click('text=Test')
await expect(page.locator(`text=Send`)).toBeHidden() await expect(page.locator(`text=Send`)).toBeHidden()
await page.getByRole('button', { name: '8' }).click() await page.getByRole('checkbox', { name: '8' }).click()
await page.locator(`text=Send`).click() await page.locator(`text=Send`).click()
await expect(page.getByTestId('guest-bubble')).toHaveText('8') await expect(page.getByTestId('guest-bubble')).toHaveText('8')
await page.click('text=Rate from 0 to 10') await page.click('text=Rate from 0 to 10')

View File

@ -1,7 +1,6 @@
import { Alert, AlertIcon, FormLabel, Stack, Tag, Text } from '@chakra-ui/react' import { Alert, AlertIcon, FormLabel, Stack, Tag, Text } from '@chakra-ui/react'
import { CodeEditor } from '@/components/inputs/CodeEditor' import { CodeEditor } from '@/components/inputs/CodeEditor'
import { SetVariableBlock, Variable } from '@typebot.io/schemas' import { SetVariableBlock, Variable } from '@typebot.io/schemas'
import React from 'react'
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput' import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel' import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
import { Select } from '@/components/inputs/Select' import { Select } from '@/components/inputs/Select'
@ -13,6 +12,7 @@ import {
} from '@typebot.io/schemas/features/blocks/logic/setVariable/constants' } from '@typebot.io/schemas/features/blocks/logic/setVariable/constants'
import { TextInput } from '@/components/inputs' import { TextInput } from '@/components/inputs'
import { isDefined } from '@typebot.io/lib' import { isDefined } from '@typebot.io/lib'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
type Props = { type Props = {
options: SetVariableBlock['options'] options: SetVariableBlock['options']
@ -24,7 +24,12 @@ const setVarTypes = valueTypes.filter(
) )
export const SetVariableSettings = ({ options, onOptionsChange }: Props) => { export const SetVariableSettings = ({ options, onOptionsChange }: Props) => {
const updateVariableId = (variable?: Variable) => const { typebot, updateVariable } = useTypebot()
const selectedVariable = typebot?.variables.find(
(variable) => variable.id === options?.variableId
)
const updateVariableId = (variable?: Pick<Variable, 'id'>) =>
onOptionsChange({ onOptionsChange({
...options, ...options,
variableId: variable?.id, variableId: variable?.id,
@ -36,6 +41,13 @@ export const SetVariableSettings = ({ options, onOptionsChange }: Props) => {
type: type as NonNullable<SetVariableBlock['options']>['type'], type: type as NonNullable<SetVariableBlock['options']>['type'],
}) })
const updateIsSessionVariable = (isSavingInResults: boolean) => {
if (!selectedVariable?.id) return
updateVariable(selectedVariable.id, {
isSessionVariable: !isSavingInResults,
})
}
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
<Stack> <Stack>
@ -49,22 +61,34 @@ export const SetVariableSettings = ({ options, onOptionsChange }: Props) => {
/> />
</Stack> </Stack>
<Stack> <Stack spacing="4">
<Text mb="0" fontWeight="medium"> <Stack>
Value: <Text mb="0" fontWeight="medium">
</Text> Value:
<Select </Text>
selectedItem={options?.type ?? defaultSetVariableOptions.type} <Select
items={setVarTypes.map((type) => ({ selectedItem={options?.type ?? defaultSetVariableOptions.type}
label: type, items={setVarTypes.map((type) => ({
value: type, label: type,
icon: value: type,
type === 'Contact name' || type === 'Phone number' ? ( icon:
<WhatsAppLogo /> type === 'Contact name' || type === 'Phone number' ? (
) : undefined, <WhatsAppLogo />
}))} ) : undefined,
onSelect={updateValueType} }))}
/> onSelect={updateValueType}
/>
</Stack>
{selectedVariable && (
<SwitchWithLabel
key={selectedVariable.id}
label="Save in results?"
moreInfoContent="By default, the variable is saved only for the user chat session. Check this option if you want to also store the variable in the typebot Results table."
initialValue={!selectedVariable.isSessionVariable}
onCheckChange={updateIsSessionVariable}
/>
)}
<SetVariableValue options={options} onOptionsChange={onOptionsChange} /> <SetVariableValue options={options} onOptionsChange={onOptionsChange} />
</Stack> </Stack>
</Stack> </Stack>
@ -91,7 +115,7 @@ const SetVariableValue = ({
isExecutedOnClient, isExecutedOnClient,
}) })
const updateItemVariableId = (variable?: Variable) => { const updateItemVariableId = (variable?: Pick<Variable, 'id'>) => {
if (!options || options.type !== 'Map item with same index') return if (!options || options.type !== 'Map item with same index') return
onOptionsChange({ onOptionsChange({
...options, ...options,
@ -102,7 +126,7 @@ const SetVariableValue = ({
}) })
} }
const updateBaseListVariableId = (variable?: Variable) => { const updateBaseListVariableId = (variable?: Pick<Variable, 'id'>) => {
if (!options || options.type !== 'Map item with same index') return if (!options || options.type !== 'Map item with same index') return
onOptionsChange({ onOptionsChange({
...options, ...options,
@ -113,7 +137,7 @@ const SetVariableValue = ({
}) })
} }
const updateTargetListVariableId = (variable?: Variable) => { const updateTargetListVariableId = (variable?: Pick<Variable, 'id'>) => {
if (!options || options.type !== 'Map item with same index') return if (!options || options.type !== 'Map item with same index') return
onOptionsChange({ onOptionsChange({
...options, ...options,

View File

@ -28,11 +28,16 @@ test.describe('Set variable block', () => {
.fill('1000 * {{Num}}') .fill('1000 * {{Num}}')
await page.click('text=Click to edit...', { force: true }) await page.click('text=Click to edit...', { force: true })
await expect(page.getByText('Save in results?')).toBeHidden()
await page.fill( await page.fill(
'input[placeholder="Select a variable"] >> nth=-1', 'input[placeholder="Select a variable"] >> nth=-1',
'Custom var' 'Custom var'
) )
await page.getByRole('menuitem', { name: 'Create Custom var' }).click() await page.getByRole('menuitem', { name: 'Create Custom var' }).click()
await expect(page.getByText('Save in results?')).toBeVisible()
await expect(
page.getByRole('group').nth(1).locator('.chakra-switch')
).not.toHaveAttribute('data-checked')
await page await page
.getByTestId('code-editor') .getByTestId('code-editor')
.getByRole('textbox') .getByRole('textbox')

View File

@ -33,6 +33,7 @@ test('table features should work', async ({ page }) => {
await expect(page.locator('text=Additional information')).toBeVisible() await expect(page.locator('text=Additional information')).toBeVisible()
await expect(page.locator('text=utm_source')).toBeVisible() await expect(page.locator('text=utm_source')).toBeVisible()
await expect(page.locator('text=utm_userid')).toBeVisible() await expect(page.locator('text=utm_userid')).toBeVisible()
await expect(page.locator('text=utm_session')).toBeHidden()
}) })
await test.step('Resize columns', async () => { await test.step('Resize columns', async () => {

View File

@ -177,7 +177,7 @@ test("can't add new members when limit is reached", async ({ page }) => {
await page.click('button >> text="Invite"') await page.click('button >> text="Invite"')
await expect( await expect(
page.locator( page.locator(
'text="Upgrade your plan to work with more team members, and unlock awesome power features 🚀"' 'text="Upgrade your plan to work with more team members, and unlock awesome power features"'
) )
).toBeVisible() ).toBeVisible()
await expect(page.locator('button >> text="Invite"')).toBeDisabled() await expect(page.locator('button >> text="Invite"')).toBeDisabled()

View File

@ -258,8 +258,21 @@
} }
], ],
"variables": [ "variables": [
{ "id": "giiLFGw5xXBCHzvp1qAbdX", "name": "Name" }, {
{ "id": "3VFChNVSCXQ2rXv4DrJ8Ah", "name": "Email" }, "id": "giiLFGw5xXBCHzvp1qAbdX",
"name": "Name",
"isSessionVariable": true
},
{
"id": "3VFChNVSCXQ2rXv4DrJ8Ah",
"name": "Email",
"isSessionVariable": true
},
{
"id": "8Q8t9YCc3ieAEXSYkmnCxH",
"name": "utm_session",
"isSessionVariable": true
},
{ "id": "8Q8t9YCc3ieAEXSYkmnCxH", "name": "utm_source" }, { "id": "8Q8t9YCc3ieAEXSYkmnCxH", "name": "utm_source" },
{ "id": "ds54chWAyWC4zjkdWAm3Vc", "name": "utm_userid" } { "id": "ds54chWAyWC4zjkdWAm3Vc", "name": "utm_userid" }
], ],

View File

@ -126,6 +126,14 @@ This value block allows you to find the `Id` from `Ids` with the same index as `
/> />
</Frame> </Frame>
## Save in results
By default, new variables are not persisted in the [Results](../../../results) table. They are only stored for the current user chat session. Enabling this option will save the variable in the `Results` table.
## Execute on client
This option is useful when you want to execute the custom code on the client side. This is only necessary when you need access the user's browser information. So, if you need access to `window`, `document`, `navigator`, etc., you should enable this option.
## Get user's geo location ## Get user's geo location
For this you can provide the following custom code: For this you can provide the following custom code:

View File

@ -40,7 +40,7 @@
"dotenv": "16.4.5", "dotenv": "16.4.5",
"@faire/mjml-react": "3.3.0", "@faire/mjml-react": "3.3.0",
"@paralleldrive/cuid2": "2.2.1", "@paralleldrive/cuid2": "2.2.1",
"@playwright/test": "1.36.0", "@playwright/test": "1.43.1",
"@typebot.io/emails": "workspace:*", "@typebot.io/emails": "workspace:*",
"@typebot.io/env": "workspace:*", "@typebot.io/env": "workspace:*",
"@typebot.io/forge": "workspace:*", "@typebot.io/forge": "workspace:*",
@ -55,7 +55,8 @@
"@types/papaparse": "5.3.7", "@types/papaparse": "5.3.7",
"@types/qs": "6.9.7", "@types/qs": "6.9.7",
"@types/react": "18.2.15", "@types/react": "18.2.15",
"dotenv-cli": "7.2.1", "dotenv-cli": "7.4.1",
"dotenv": "16.4.5",
"eslint": "8.44.0", "eslint": "8.44.0",
"eslint-config-custom": "workspace:*", "eslint-config-custom": "workspace:*",
"google-auth-library": "8.9.0", "google-auth-library": "8.9.0",

View File

@ -1,6 +1,8 @@
import { defineConfig, devices } from '@playwright/test' import { defineConfig, devices } from '@playwright/test'
import { resolve } from 'path'
process.env.SKIP_ENV_CHECK = 'true' // eslint-disable-next-line @typescript-eslint/no-var-requires
require('dotenv').config({ path: resolve(__dirname, '../../.env') })
export default defineConfig({ export default defineConfig({
timeout: process.env.CI ? 50 * 1000 : 40 * 1000, timeout: process.env.CI ? 50 * 1000 : 40 * 1000,

View File

@ -1,7 +1,7 @@
import prisma from '@typebot.io/lib/prisma' import prisma from '@typebot.io/lib/prisma'
import { Prisma } from '@typebot.io/prisma' import { Prisma } from '@typebot.io/prisma'
import { TypebotInSession } from '@typebot.io/schemas' import { TypebotInSession } from '@typebot.io/schemas'
import { filterVariablesWithValues } from '@typebot.io/variables/filterVariablesWithValues' import { filterNonSessionVariablesWithValues } from '@typebot.io/variables/filterVariablesWithValues'
type Props = { type Props = {
resultId: string resultId: string
@ -15,7 +15,9 @@ export const upsertResult = ({
hasStarted, hasStarted,
isCompleted, isCompleted,
}: Props): Prisma.PrismaPromise<any> => { }: Props): Prisma.PrismaPromise<any> => {
const variablesWithValue = filterVariablesWithValues(typebot.variables) const variablesWithValue = filterNonSessionVariablesWithValues(
typebot.variables
)
return prisma.result.upsert({ return prisma.result.upsert({
where: { id: resultId }, where: { id: resultId },
update: { update: {

View File

@ -149,7 +149,8 @@ const parseVariablesHeaders = (
if ( if (
existingInputResultHeaders.some((existingInputResultHeader) => existingInputResultHeaders.some((existingInputResultHeader) =>
existingInputResultHeader.variableIds?.includes(variable.id) existingInputResultHeader.variableIds?.includes(variable.id)
) ) ||
variable.isSessionVariable
) )
return existingHeaders return existingHeaders

View File

@ -2,27 +2,27 @@ import { z } from '../../zod'
export const listVariableValue = z.array(z.string().nullable()) export const listVariableValue = z.array(z.string().nullable())
export const variableSchema = z.object({ const baseVariableSchema = z.object({
id: z.string(), id: z.string(),
name: z.string(), name: z.string(),
isSessionVariable: z.boolean().optional(),
})
export const variableSchema = baseVariableSchema.extend({
value: z.string().or(listVariableValue).nullish(), value: z.string().or(listVariableValue).nullish(),
}) })
/** /**
* Variable when retrieved from the database * Variable when retrieved from the database
*/ */
export const variableWithValueSchema = z.object({ export const variableWithValueSchema = baseVariableSchema.extend({
id: z.string(),
name: z.string(),
value: z.string().or(listVariableValue), value: z.string().or(listVariableValue),
}) })
/** /**
* Variable when computed or retrieved from a block * Variable when computed or retrieved from a block
*/ */
const VariableWithUnknowValueSchema = z.object({ const VariableWithUnknowValueSchema = baseVariableSchema.extend({
id: z.string(),
name: z.string(),
value: z.unknown(), value: z.unknown(),
}) })

View File

@ -1,9 +1,9 @@
import { isDefined } from '@typebot.io/lib' import { isDefined } from '@typebot.io/lib'
import { Variable, VariableWithValue } from '../schemas' import { Variable, VariableWithValue } from '../schemas'
export const filterVariablesWithValues = ( export const filterNonSessionVariablesWithValues = (
variables: Variable[] variables: Variable[]
): VariableWithValue[] => ): VariableWithValue[] =>
variables.filter((variable) => variables.filter(
isDefined(variable.value) (variable) => isDefined(variable.value) && !variable.isSessionVariable
) as VariableWithValue[] ) as VariableWithValue[]

69
pnpm-lock.yaml generated
View File

@ -283,8 +283,8 @@ importers:
specifier: 2.9.1 specifier: 2.9.1
version: 2.9.1 version: 2.9.1
'@playwright/test': '@playwright/test':
specifier: 1.41.2 specifier: 1.43.1
version: 1.41.2 version: 1.43.1
'@typebot.io/billing': '@typebot.io/billing':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/billing version: link:../../packages/billing
@ -354,9 +354,12 @@ importers:
'@types/tinycolor2': '@types/tinycolor2':
specifier: 1.4.3 specifier: 1.4.3
version: 1.4.3 version: 1.4.3
dotenv:
specifier: 16.4.5
version: 16.4.5
dotenv-cli: dotenv-cli:
specifier: 7.2.1 specifier: 7.4.1
version: 7.2.1 version: 7.4.1
eslint: eslint:
specifier: 8.44.0 specifier: 8.44.0
version: 8.44.0 version: 8.44.0
@ -649,8 +652,8 @@ importers:
specifier: 2.2.1 specifier: 2.2.1
version: 2.2.1 version: 2.2.1
'@playwright/test': '@playwright/test':
specifier: 1.36.0 specifier: 1.43.1
version: 1.36.0 version: 1.43.1
'@typebot.io/emails': '@typebot.io/emails':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/emails version: link:../../packages/emails
@ -703,8 +706,8 @@ importers:
specifier: 16.4.5 specifier: 16.4.5
version: 16.4.5 version: 16.4.5
dotenv-cli: dotenv-cli:
specifier: 7.2.1 specifier: 7.4.1
version: 7.2.1 version: 7.4.1
eslint: eslint:
specifier: 8.44.0 specifier: 8.44.0
version: 8.44.0 version: 8.44.0
@ -7932,14 +7935,6 @@ packages:
fsevents: 2.3.2 fsevents: 2.3.2
dev: true dev: true
/@playwright/test@1.41.2:
resolution: {integrity: sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==}
engines: {node: '>=16'}
hasBin: true
dependencies:
playwright: 1.41.2
dev: true
/@playwright/test@1.42.1: /@playwright/test@1.42.1:
resolution: {integrity: sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==} resolution: {integrity: sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==}
engines: {node: '>=16'} engines: {node: '>=16'}
@ -7948,6 +7943,14 @@ packages:
playwright: 1.42.1 playwright: 1.42.1
dev: false dev: false
/@playwright/test@1.43.1:
resolution: {integrity: sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==}
engines: {node: '>=16'}
hasBin: true
dependencies:
playwright: 1.43.1
dev: true
/@popperjs/core@2.11.8: /@popperjs/core@2.11.8:
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
dev: false dev: false
@ -13213,6 +13216,16 @@ packages:
minimist: 1.2.8 minimist: 1.2.8
dev: true dev: true
/dotenv-cli@7.4.1:
resolution: {integrity: sha512-fE1aywjRrWGxV3miaiUr3d2zC/VAiuzEGghi+QzgIA9fEf/M5hLMaRSXb4IxbUAwGmaLi0IozdZddnVU96acag==}
hasBin: true
dependencies:
cross-spawn: 7.0.3
dotenv: 16.4.5
dotenv-expand: 10.0.0
minimist: 1.2.8
dev: true
/dotenv-expand@10.0.0: /dotenv-expand@10.0.0:
resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -19262,26 +19275,16 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/playwright-core@1.41.2:
resolution: {integrity: sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==}
engines: {node: '>=16'}
hasBin: true
dev: true
/playwright-core@1.42.1: /playwright-core@1.42.1:
resolution: {integrity: sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==} resolution: {integrity: sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==}
engines: {node: '>=16'} engines: {node: '>=16'}
hasBin: true hasBin: true
dev: false dev: false
/playwright@1.41.2: /playwright-core@1.43.1:
resolution: {integrity: sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==} resolution: {integrity: sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==}
engines: {node: '>=16'} engines: {node: '>=16'}
hasBin: true hasBin: true
dependencies:
playwright-core: 1.41.2
optionalDependencies:
fsevents: 2.3.2
dev: true dev: true
/playwright@1.42.1: /playwright@1.42.1:
@ -19294,6 +19297,16 @@ packages:
fsevents: 2.3.2 fsevents: 2.3.2
dev: false dev: false
/playwright@1.43.1:
resolution: {integrity: sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==}
engines: {node: '>=16'}
hasBin: true
dependencies:
playwright-core: 1.43.1
optionalDependencies:
fsevents: 2.3.2
dev: true
/pngjs@5.0.0: /pngjs@5.0.0:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}