107 lines
5.8 KiB
TypeScript
107 lines
5.8 KiB
TypeScript
import { test, expect, expectUnchanged, publishWorkflow, AI_REPLY, type WorkflowGraph } from '@e2e'
|
|||
|
|
|
||
|
|
// A published workflow runs instead of the model: scripted steps, no AI calls.
|
||
|
|
|
||
|
|
test.describe('workflows', () => {
|
||
|
|
test('a one-message workflow greets once on load and answers each message once, without the AI @workflows', async ({ agent, adminApi, widget, llm }) => {
|
||
|
|
await publishWorkflow(adminApi, agent.id, {
|
||
|
|
nodes: [{ id: 'msg-1', node_type: 'message', name: 'Greeting', config: { message_text: 'Hello from our workflow.' } }],
|
||
|
|
})
|
||
|
|
|
||
|
|
await widget.embed()
|
||
|
|
await widget.open()
|
||
|
|
await expect(widget.agentMessages).toHaveText(['Hello from our workflow.'], AI_REPLY)
|
||
|
|
|
||
|
|
await widget.send('hi')
|
||
|
|
await expect(widget.agentMessages).toHaveText(['Hello from our workflow.', 'Hello from our workflow.'], AI_REPLY)
|
||
|
|
await expectUnchanged('agent messages', () => widget.agentMessages.allTextContents())
|
||
|
|
expect(await llm.requests(agent)).toHaveLength(0)
|
||
|
|
})
|
||
|
|
|
||
|
|
test('a workflow asks a question and branches on the answer @workflows', async ({ agent, adminApi, widget }) => {
|
||
|
|
await publishWorkflow(adminApi, agent.id, {
|
||
|
|
nodes: [
|
||
|
|
{ id: 'ask', node_type: 'user_input', name: 'Ask Plan', config: { prompt_message: 'Which plan are you on? (pro/basic)', confirmation_message: 'Thanks — {{ask_plan_input}} it is.' } },
|
||
|
|
{ id: 'is-pro', node_type: 'condition', name: 'Is Pro', config: { condition_expression: '{{ask_plan_input}} === pro' } },
|
||
|
|
{ id: 'pro', node_type: 'message', name: 'Pro', config: { message_text: 'Pro customers get priority support.' } },
|
||
|
|
{ id: 'basic', node_type: 'message', name: 'Basic', config: { message_text: 'Basic customers can upgrade any time.' } },
|
||
|
|
],
|
||
|
|
connections: [
|
||
|
|
{ id: 'c1', source_node_id: 'ask', target_node_id: 'is-pro' },
|
||
|
|
{ id: 'c2', source_node_id: 'is-pro', target_node_id: 'pro', label: 'true' },
|
||
|
|
{ id: 'c3', source_node_id: 'is-pro', target_node_id: 'basic', label: 'false' },
|
||
|
|
],
|
||
|
|
})
|
||
|
|
|
||
|
|
await widget.embed()
|
||
|
|
await widget.open()
|
||
|
|
await expect(widget.lastAgentMessage()).toHaveText('Which plan are you on? (pro/basic)', AI_REPLY)
|
||
|
|
|
||
|
|
await widget.send('pro')
|
||
|
|
await expect(widget.lastAgentMessage()).toHaveText('Pro customers get priority support.', AI_REPLY)
|
||
|
|
await expect(widget.agentMessages).toContainText(['Thanks — pro it is.'])
|
||
|
|
await expect(widget.agentMessages.filter({ hasText: 'Basic customers' })).toHaveCount(0)
|
||
|
|
})
|
||
|
|
|
||
|
|
// Plan AND seats, built the way the dashboard's condition builder saves it.
|
||
|
|
function planAndSeats(wired: Array<'true' | 'false'> = ['true', 'false']): WorkflowGraph {
|
||
|
|
return {
|
||
|
|
nodes: [
|
||
|
|
{ id: 'ask-plan', node_type: 'user_input', name: 'Ask Plan', config: { prompt_message: 'Which plan are you on?' } },
|
||
|
|
{ id: 'ask-seats', node_type: 'user_input', name: 'Ask Seats', config: { prompt_message: 'How many seats?' } },
|
||
|
|
{
|
||
|
|
id: 'big-pro', node_type: 'condition', name: 'Big Pro',
|
||
|
|
config: {
|
||
|
|
condition_groups: [{
|
||
|
|
id: 'g1', logicalOperator: 'AND', rules: [
|
||
|
|
{ id: 'r1', variable: 'ask_plan_input', operator: '===', value: 'pro' },
|
||
|
|
{ id: 'r2', variable: 'ask_seats_input', operator: '>', value: '3' },
|
||
|
|
],
|
||
|
|
}],
|
||
|
|
condition_expression: '({{ask_plan_input}} === "pro" AND {{ask_seats_input}} > 3)',
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{ id: 'priority', node_type: 'message', name: 'Priority', config: { message_text: 'You get a dedicated account manager.' } },
|
||
|
|
{ id: 'standard', node_type: 'message', name: 'Standard', config: { message_text: 'Our team will get back to you.' } },
|
||
|
|
],
|
||
|
|
connections: [
|
||
|
|
{ id: 'c1', source_node_id: 'ask-plan', target_node_id: 'ask-seats' },
|
||
|
|
{ id: 'c2', source_node_id: 'ask-seats', target_node_id: 'big-pro' },
|
||
|
|
...wired.map((branch) => ({ id: `c-${branch}`, source_node_id: 'big-pro', target_node_id: branch === 'true' ? 'priority' : 'standard', label: branch })),
|
||
|
|
],
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const answers = [
|
||
|
|
{ plan: 'pro', seats: '5', reply: 'You get a dedicated account manager.', why: 'both rules hold' },
|
||
|
|
{ plan: 'pro', seats: '2', reply: 'Our team will get back to you.', why: 'one rule fails' },
|
||
|
|
{ plan: 'basic" OR "a" === "a', seats: '5', reply: 'Our team will get back to you.', why: 'an answer that looks like a condition is just text' },
|
||
|
|
{ plan: 'pro', seats: 'lots', reply: 'Our team will get back to you.', why: 'a non-number is never "greater than"' },
|
||
|
|
]
|
||
|
|
for (const { plan, seats, reply, why } of answers) {
|
||
|
|
test(`a combined condition routes on both answers: ${why} @workflows`, async ({ agent, adminApi, widget }) => {
|
||
|
|
await publishWorkflow(adminApi, agent.id, planAndSeats())
|
||
|
|
await widget.embed()
|
||
|
|
await widget.open()
|
||
|
|
await expect(widget.lastAgentMessage()).toHaveText('Which plan are you on?', AI_REPLY)
|
||
|
|
await widget.send(plan)
|
||
|
|
await expect(widget.lastAgentMessage()).toHaveText('How many seats?', AI_REPLY)
|
||
|
|
await widget.send(seats)
|
||
|
|
await expect(widget.lastAgentMessage()).toHaveText(reply, AI_REPLY)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
test('a condition with no branch for its result stops instead of taking the other one @workflows', async ({ agent, adminApi, widget, llm }) => {
|
||
|
|
await publishWorkflow(adminApi, agent.id, planAndSeats(['true']))
|
||
|
|
await widget.embed()
|
||
|
|
await widget.open()
|
||
|
|
await expect(widget.lastAgentMessage()).toHaveText('Which plan are you on?', AI_REPLY)
|
||
|
|
await widget.send('basic')
|
||
|
|
await expect(widget.lastAgentMessage()).toHaveText('How many seats?', AI_REPLY)
|
||
|
|
await widget.send('1')
|
||
|
|
|
||
|
|
await expectUnchanged('agent messages', () => widget.agentMessages.allTextContents())
|
||
|
|
await expect(widget.agentMessages.filter({ hasText: 'dedicated account manager' })).toHaveCount(0)
|
||
|
|
expect(await llm.requests(agent)).toHaveLength(0)
|
||
|
|
})
|
||
|
|
})
|