diff --git a/apps/builder/playwright/tests/integrations/webhook.spec.ts b/apps/builder/playwright/tests/integrations/webhook.spec.ts index c17972abac..986edcc507 100644 --- a/apps/builder/playwright/tests/integrations/webhook.spec.ts +++ b/apps/builder/playwright/tests/integrations/webhook.spec.ts @@ -31,31 +31,7 @@ test.describe('Webhook block', () => { `"Group #1": "answer value", "Group #2": "20", "Group #2 (1)": "Yes"` ) }) - test('Generated body should work', async ({ page }) => { - const typebotId = cuid() - await importTypebotInDatabase( - path.join(__dirname, '../../fixtures/typebots/integrations/webhook.json'), - { - id: typebotId, - } - ) - await createWebhook(typebotId) - - await page.goto(`/typebots/${typebotId}/edit`) - await page.click('text=Configure...') - await page.fill( - 'input[placeholder="Paste webhook URL..."]', - `${process.env.PLAYWRIGHT_BUILDER_TEST_BASE_URL}/api/mock/webhook-easy-config` - ) - await page.click('text=Advanced configuration') - await page.click('text=GET') - await page.click('text=POST') - await page.click('text=Test the request') - await expect(page.locator('div[role="textbox"] >> nth=-1')).toContainText( - '"message": "This is a sample result, it has been generated ⬇️"' - ) - }) test('its configuration should work', async ({ page }) => { const typebotId = cuid() await importTypebotInDatabase( diff --git a/apps/viewer/playwright/fixtures/typebots/webhook.json b/apps/viewer/playwright/fixtures/typebots/webhook.json index 79ce0f45b2..cd8130310d 100644 --- a/apps/viewer/playwright/fixtures/typebots/webhook.json +++ b/apps/viewer/playwright/fixtures/typebots/webhook.json @@ -1,72 +1,193 @@ { - "id": "cl26li8fl0407iez0w2tlw8fn", - "createdAt": "2022-04-19T20:25:30.417Z", - "updatedAt": "2022-04-19T20:40:48.366Z", + "id": "cl9ip9u0l00001ad79a2lzm55", + "createdAt": "2022-10-21T16:22:07.414Z", + "updatedAt": "2022-10-21T16:30:57.642Z", "icon": null, "name": "My typebot", "publishedTypebotId": null, "folderId": null, "groups": [ { - "id": "cl26li8fj0000iez05x7razkg", + "id": "cl9ip9u0j0000d71a5d98gwni", + "title": "Start", "blocks": [ { - "id": "cl26li8fj0001iez0bqfraw9h", + "id": "cl9ip9u0j0001d71a44dsd2p1", "type": "start", "label": "Start", - "groupId": "cl26li8fj0000iez05x7razkg", - "outgoingEdgeId": "cl26liqj6000g2e6ed2cwkvse" + "groupId": "cl9ip9u0j0000d71a5d98gwni", + "outgoingEdgeId": "cl9ipkkb2001b3b6oh3vptq9k" } ], - "title": "Start", "graphCoordinates": { "x": 0, "y": 0 } }, { - "id": "cl26lidjz000a2e6etf4v03hv", + "id": "cl9ipa38j00083b6o69e90m4t", + "graphCoordinates": { "x": 340, "y": 341 }, + "title": "Group #1", "blocks": [ { - "id": "cl26lidk4000b2e6es2fos0nl", + "id": "cl9ipaaut000a3b6ovrqlec3x", + "groupId": "cl9ipa38j00083b6o69e90m4t", + "type": "text input", + "options": { + "isLong": false, + "labels": { "button": "Send", "placeholder": "Type a name..." }, + "variableId": "vcl9ipajth000c3b6okl97r81j" + } + }, + { + "id": "cl9ipan8f000d3b6oo2ovi3ac", + "groupId": "cl9ipa38j00083b6o69e90m4t", + "type": "number input", + "options": { + "labels": { "button": "Send", "placeholder": "Type an age..." }, + "variableId": "vcl9ipaszl000e3b6ousjxuw7b" + } + }, + { + "id": "cl9ipb08n000f3b6ok3mi2p48", + "groupId": "cl9ipa38j00083b6o69e90m4t", "type": "choice input", + "options": { + "buttonLabel": "Send", + "isMultipleChoice": false, + "variableId": "vcl9ipg4tb00103b6oue08w3nm" + }, "items": [ { - "id": "cl26lidk5000c2e6e39wyc7wq", + "id": "cl9ipb08n000g3b6okr691uad", + "blockId": "cl9ipb08n000f3b6ok3mi2p48", + "type": 0, + "content": "Male" + }, + { + "blockId": "cl9ipb08n000f3b6ok3mi2p48", "type": 0, - "blockId": "cl26lidk4000b2e6es2fos0nl", - "content": "Send success webhook" + "id": "cl9ipb2kk000h3b6oadwtonnz", + "content": "Female" } ], - "groupId": "cl26lidjz000a2e6etf4v03hv", - "options": { "buttonLabel": "Send", "isMultipleChoice": false } + "outgoingEdgeId": "cl9ipcp83000o3b6odsn0a9a1" + } + ] + }, + { + "id": "cl9ipbcjy000j3b6oqngo7luv", + "graphCoordinates": { "x": 781, "y": 91 }, + "title": "Group #2", + "blocks": [ + { + "id": "cl9ipbl6l000m3b6o3evn41kv", + "groupId": "cl9ipbcjy000j3b6oqngo7luv", + "type": "Set variable", + "options": { + "variableId": "vcl9ipbokm000n3b6o06hvarrf", + "expressionToEvaluate": "{\n \"name\": \"John\",\n \"age\": 25,\n \"gender\": \"male\"\n}" + } }, { - "id": "cl26lip76000e2e6ebmph843a", + "id": "cl9ipbcjy000k3b6oe8lta5c1", + "groupId": "cl9ipbcjy000j3b6oqngo7luv", "type": "Webhook", - "groupId": "cl26lidjz000a2e6etf4v03hv", "options": { - "isCustomBody": false, - "isAdvancedConfig": false, + "responseVariableMapping": [ + { + "id": "cl9ipdspg000p3b6ognbfvmdx", + "variableId": "vcl9ipdxnj000q3b6oy55th4xb", + "bodyPath": "data" + } + ], + "variablesForTest": [], + "isAdvancedConfig": true, + "isCustomBody": true + }, + "webhookId": "full-body-webhook" + }, + { + "id": "cl9ipe5t8000s3b6ocswre500", + "groupId": "cl9ipbcjy000j3b6oqngo7luv", + "type": "text", + "content": { + "html": "
Data of first request:
{{Data}}
", + "richText": [ + { + "type": "p", + "children": [{ "text": "Data of first request:" }] + }, + { "type": "p", "children": [{ "text": "" }] }, + { "type": "p", "children": [{ "text": "{{Data}}" }] } + ], + "plainText": "Data of first request:{{Data}}" + }, + "outgoingEdgeId": "cl9ipet83000z3b6of6zfqota" + } + ] + }, + { + "id": "cl9ipej6b000u3b6oeaz305l6", + "graphCoordinates": { "x": 1138, "y": 85 }, + "title": "Group #2 copy", + "blocks": [ + { + "id": "cl9ipej6c000w3b6otzk247vl", + "groupId": "cl9ipej6b000u3b6oeaz305l6", + "type": "Webhook", + "options": { + "responseVariableMapping": [ + { + "id": "cl9ipdspg000p3b6ognbfvmdx", + "variableId": "vcl9ipdxnj000q3b6oy55th4xb", + "bodyPath": "data" + } + ], "variablesForTest": [], - "responseVariableMapping": [] + "isAdvancedConfig": true, + "isCustomBody": true }, - "webhookId": "success-webhook" + "webhookId": "partial-body-webhook" }, { - "id": "cl26m0pdz00042e6ebjdoclaa", - "groupId": "cl26lidjz000a2e6etf4v03hv", + "id": "cl9ipej6c000y3b6oegzkgloq", + "groupId": "cl9ipej6b000u3b6oeaz305l6", + "type": "text", + "content": { + "html": "
Data of second request:
{{Data}}
", + "richText": [ + { + "type": "p", + "children": [{ "text": "Data of second request:" }] + }, + { "type": "p", "children": [{ "text": "" }] }, + { "type": "p", "children": [{ "text": "{{Data}}" }] } + ], + "plainText": "Data of second request:{{Data}}" + } + } + ] + }, + { + "id": "cl9ipkaer00153b6ov230yuv2", + "graphCoordinates": { "x": 333, "y": 26 }, + "title": "Group #4", + "blocks": [ + { + "id": "cl9ipkaer00163b6o0ohmmscn", + "groupId": "cl9ipkaer00153b6ov230yuv2", "type": "choice input", "options": { "buttonLabel": "Send", "isMultipleChoice": false }, "items": [ { - "id": "cl26m0pdz00052e6ecmxwfz44", - "blockId": "cl26m0pdz00042e6ebjdoclaa", + "id": "cl9ipkaer00173b6oxof4zrqn", + "blockId": "cl9ipkaer00163b6o0ohmmscn", "type": 0, - "content": "Send failed webhook" + "content": "Send failing webhook" } ] }, { - "id": "cl26m0w9b00072e6eld1ei291", - "groupId": "cl26lidjz000a2e6etf4v03hv", + "id": "cl9ipki9u00193b6okmhudo0f", + "groupId": "cl9ipkaer00153b6ov230yuv2", "type": "Webhook", "options": { "responseVariableMapping": [], @@ -74,26 +195,51 @@ "isAdvancedConfig": false, "isCustomBody": false }, - "webhookId": "failed-webhook" + "webhookId": "failing-webhook", + "outgoingEdgeId": "cl9ipklm0001c3b6oy0a5nbhr" } - ], - "title": "Group #1", - "graphCoordinates": { "x": 386, "y": 117 } + ] } ], "variables": [ - { "id": "vcl26lzmg100012e6e9rn57c3o", "name": "var1" }, - { "id": "vcl26lzo7q00022e6edw3pe7lf", "name": "var2" }, - { "id": "vcl26lzq6s00032e6ecuhh80qz", "name": "var3" } + { "id": "vcl9ipajth000c3b6okl97r81j", "name": "Name" }, + { "id": "vcl9ipaszl000e3b6ousjxuw7b", "name": "Age" }, + { "id": "vcl9ipbokm000n3b6o06hvarrf", "name": "Full body" }, + { "id": "vcl9ipdxnj000q3b6oy55th4xb", "name": "Data" }, + { "id": "vcl9ipg4tb00103b6oue08w3nm", "name": "Gender" } ], "edges": [ { - "id": "cl26liqj6000g2e6ed2cwkvse", - "to": { "groupId": "cl26lidjz000a2e6etf4v03hv" }, "from": { - "blockId": "cl26li8fj0001iez0bqfraw9h", - "groupId": "cl26li8fj0000iez05x7razkg" - } + "groupId": "cl9ipa38j00083b6o69e90m4t", + "blockId": "cl9ipb08n000f3b6ok3mi2p48" + }, + "to": { "groupId": "cl9ipbcjy000j3b6oqngo7luv" }, + "id": "cl9ipcp83000o3b6odsn0a9a1" + }, + { + "from": { + "groupId": "cl9ipbcjy000j3b6oqngo7luv", + "blockId": "cl9ipe5t8000s3b6ocswre500" + }, + "to": { "groupId": "cl9ipej6b000u3b6oeaz305l6" }, + "id": "cl9ipet83000z3b6of6zfqota" + }, + { + "from": { + "groupId": "cl9ip9u0j0000d71a5d98gwni", + "blockId": "cl9ip9u0j0001d71a44dsd2p1" + }, + "to": { "groupId": "cl9ipkaer00153b6ov230yuv2" }, + "id": "cl9ipkkb2001b3b6oh3vptq9k" + }, + { + "from": { + "groupId": "cl9ipkaer00153b6ov230yuv2", + "blockId": "cl9ipki9u00193b6okmhudo0f" + }, + "to": { "groupId": "cl9ipa38j00083b6o69e90m4t" }, + "id": "cl9ipklm0001c3b6oy0a5nbhr" } ], "theme": { @@ -115,8 +261,9 @@ }, "settings": { "general": { - "isBrandingEnabled": true, + "isBrandingEnabled": false, "isInputPrefillEnabled": true, + "isHideQueryParamsEnabled": true, "isNewResultOnRefreshEnabled": false }, "metadata": { @@ -125,5 +272,8 @@ "typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 } }, "publicId": null, - "customDomain": null + "customDomain": null, + "workspaceId": "proWorkspace", + "isArchived": false, + "isClosed": false } diff --git a/apps/viewer/playwright/tests/webhook.spec.ts b/apps/viewer/playwright/tests/webhook.spec.ts index 0de8f26e8b..ee0c4a3d0f 100644 --- a/apps/viewer/playwright/tests/webhook.spec.ts +++ b/apps/viewer/playwright/tests/webhook.spec.ts @@ -14,40 +14,50 @@ test('should execute webhooks properly', async ({ page }) => { path.join(__dirname, '../fixtures/typebots/webhook.json'), { id: typebotId, publicId: `${typebotId}-public` } ) + await createWebhook(typebotId, { - id: 'success-webhook', - url: 'http://localhost:3001/api/mock/success', + id: 'failing-webhook', + url: 'http://localhost:3001/api/mock/fail', method: HttpMethod.POST, }) + await createWebhook(typebotId, { - id: 'failed-webhook', - url: 'http://localhost:3001/api/mock/fail', + id: 'partial-body-webhook', + url: 'http://localhost:3000/api/mock/webhook-easy-config', + method: HttpMethod.POST, + body: `{ + "name": "{{Name}}", + "age": {{Age}}, + "gender": "{{Gender}}" + }`, + }) + + await createWebhook(typebotId, { + id: 'full-body-webhook', + url: 'http://localhost:3000/api/mock/webhook-easy-config', method: HttpMethod.POST, + body: `{{Full body}}`, }) await page.goto(`/${typebotId}-public`) - await Promise.all([ - page.waitForResponse( - async (resp) => - resp.request().url().includes(`/api/typebots/${typebotId}/blocks`) && - resp.status() === 200 && - (await resp.json()).statusCode === 200 - ), - typebotViewer(page).locator('text=Send success webhook').click(), - ]) - await Promise.all([ - page.waitForResponse( - async (resp) => - resp.request().url().includes(`/api/typebots/${typebotId}/blocks`) && - resp.status() === 200 && - (await resp.json()).statusCode === 500 - ), - typebotViewer(page).locator('text=Send failed webhook').click(), - ]) + await typebotViewer(page).locator('text=Send failing webhook').click() + await typebotViewer(page) + .locator('[placeholder="Type a name..."]') + .fill('John') + await typebotViewer(page).locator('text="Send"').click() + await typebotViewer(page).locator('[placeholder="Type an age..."]').fill('30') + await typebotViewer(page).locator('text="Send"').click() + await typebotViewer(page).locator('text="Male"').click() + await expect( + typebotViewer(page).getByText('{"name":"John","age":25,"gender":"male"}') + ).toBeVisible() + await expect( + typebotViewer(page).getByText('{"name":"John","age":30,"gender":"Male"}') + ).toBeVisible() await page.goto(`http://localhost:3000/typebots/${typebotId}/results`) await page.click('text="See logs"') await expect( - page.locator('text="Webhook successfuly executed."') + page.locator('text="Webhook successfuly executed." >> nth=1') ).toBeVisible() await expect(page.locator('text="Webhook returned an error"')).toBeVisible() }) diff --git a/packages/bot-engine/src/services/variable.ts b/packages/bot-engine/src/services/variable.ts index 9fb71243a1..99b37eca7b 100644 --- a/packages/bot-engine/src/services/variable.ts +++ b/packages/bot-engine/src/services/variable.ts @@ -1,4 +1,4 @@ -import { Variable } from 'models' +import { Variable, VariableWithValue } from 'models' import { isDefined, isNotDefined } from 'utils' export const stringContainsVariable = (str: string): boolean => @@ -18,11 +18,10 @@ export const parseVariables = const matchedVarName = fullVariableString.replace(/{{|}}/g, '') const variable = variables.find((v) => { return matchedVarName === v.name && isDefined(v.value) - }) + }) as VariableWithValue | undefined if (!variable) return '' if (options.fieldToParse === 'id') return variable.id const { value } = variable - if (isNotDefined(value)) return '' if (options.escapeForJson) return jsonParse(value) const parsedValue = safeStringify(value) if (!parsedValue) return ''