test: Migrate Langchain e2e tests to Playwright (#19161)

This commit is contained in:
oleg
2025-09-08 18:06:00 +02:00
committed by GitHub
parent 574ec6e895
commit 64f260cb72
19 changed files with 1752 additions and 1469 deletions

View File

@@ -0,0 +1,80 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/chat/completions",
"body": {
"contentType": "application/json",
"type": "JSON",
"json": {
"model": "gpt-4.1-mini",
"stream": false,
"messages": [
{
"role": "user",
"content": "Hello!"
}
]
},
"rawBytes": "eyJtb2RlbCI6ImdwdC00LjEtbWluaSIsInN0cmVhbSI6ZmFsc2UsIm1lc3NhZ2VzIjpbeyJyb2xlIjoidXNlciIsImNvbnRlbnQiOiJIZWxsbyEifV19"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-request-id": ["req_b8f45478a83a4dd2b6d3d6c0df25b482"],
"x-ratelimit-reset-tokens": ["0s"],
"x-ratelimit-reset-requests": ["2ms"],
"x-ratelimit-remaining-tokens": ["149999997"]
},
"body": {
"contentType": "application/json",
"type": "JSON",
"json": {
"id": "chatcmpl-CDWB6cgun4QRcijeXQYPQWTaqtLmN",
"object": "chat.completion",
"created": 1757337992,
"model": "gpt-4.1-mini-2025-04-14",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! How can I assist you today?",
"refusal": null,
"annotations": []
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 9,
"completion_tokens": 9,
"total_tokens": 18,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"service_tier": "default",
"system_fingerprint": "fp_6d7dcc9a98"
},
"rawBytes": "ewogICJpZCI6ICJjaGF0Y21wbC1DRFdCNmNndW40UVJjaWplWFFZUFFXVGFxdExtTiIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVhdGVkIjogMTc1NzMzNzk5MiwKICAibW9kZWwiOiAiZ3B0LTQuMS1taW5pLTIwMjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgiOiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Npc3RhbnQiLAogICAgICAgICJjb250ZW50IjogIkhlbGxvISBIb3cgY2FuIEkgYXNzaXN0IHlvdSB0b2RheT8iLAogICAgICAgICJyZWZ1c2FsIjogbnVsbCwKICAgICAgICAiYW5ub3RhdGlvbnMiOiBbXQogICAgICB9LAogICAgICAibG9ncHJvYnMiOiBudWxsLAogICAgICAiZmluaXNoX3JlYXNvbiI6ICJzdG9wIgogICAgfQogIF0sCiAgInVzYWdlIjogewogICAgInByb21wdF90b2tlbnMiOiA5LAogICAgImNvbXBsZXRpb25fdG9rZW5zIjogOSwKICAgICJ0b3RhbF90b2tlbnMiOiAxOCwKICAgICJwcm9tcHRfdG9rZW5zX2RldGFpbHMiOiB7CiAgICAgICJjYWNoZWRfdG9rZW5zIjogMCwKICAgICAgImF1ZGlvX3Rva2VucyI6IDAKICAgIH0sCiAgICAiY29tcGxldGlvbl90b2tlbnNfZGV0YWlscyI6IHsKICAgICAgInJlYXNvbmluZ190b2tlbnMiOiAwLAogICAgICAiYXVkaW9fdG9rZW5zIjogMCwKICAgICAgImFjY2VwdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMCwKICAgICAgInJlamVjdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMAogICAgfQogIH0sCiAgInNlcnZpY2VfdGllciI6ICJkZWZhdWx0IiwKICAic3lzdGVtX2ZpbmdlcnByaW50IjogImZwXzZkN2RjYzlhOTgiCn0K"
}
},
"id": "1757337994261-unknown-host-POST-_v1_chat_completions-1561df08.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@@ -0,0 +1,82 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/chat/completions",
"body": {
"contentType": "application/json",
"type": "JSON",
"json": {
"model": "gpt-4.1-mini",
"stream": false,
"messages": [
{
"role": "user",
"content": "Hello!"
}
]
},
"rawBytes": "eyJtb2RlbCI6ImdwdC00LjEtbWluaSIsInN0cmVhbSI6ZmFsc2UsIm1lc3NhZ2VzIjpbeyJyb2xlIjoidXNlciIsImNvbnRlbnQiOiJIZWxsbyEifV19"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-request-id": ["req_11f2fb25bd0b4a758224bba6f1e68e50"],
"x-ratelimit-reset-tokens": ["0s"],
"x-ratelimit-reset-requests": ["2ms"],
"x-ratelimit-remaining-tokens": ["149999995"],
"x-ratelimit-remaining-requests": ["29999"],
"x-ratelimit-limit-tokens": ["150000000"]
},
"body": {
"contentType": "application/json",
"type": "JSON",
"json": {
"id": "chatcmpl-CDWB7f4flZjMHmcuet4JNe5pRTfVM",
"object": "chat.completion",
"created": 1757337993,
"model": "gpt-4.1-mini-2025-04-14",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! How can I assist you today?",
"refusal": null,
"annotations": []
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 9,
"completion_tokens": 9,
"total_tokens": 18,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"service_tier": "default",
"system_fingerprint": "fp_4fce0778af"
},
"rawBytes": "ewogICJpZCI6ICJjaGF0Y21wbC1DRFdCN2Y0Zmxaak1IbWN1ZXQ0Sk5lNXBSVGZWTSIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVhdGVkIjogMTc1NzMzNzk5MywKICAibW9kZWwiOiAiZ3B0LTQuMS1taW5pLTIwMjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgiOiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Npc3RhbnQiLAogICAgICAgICJjb250ZW50IjogIkhlbGxvISBIb3cgY2FuIEkgYXNzaXN0IHlvdSB0b2RheT8iLAogICAgICAgICJyZWZ1c2FsIjogbnVsbCwKICAgICAgICAiYW5ub3RhdGlvbnMiOiBbXQogICAgICB9LAogICAgICAibG9ncHJvYnMiOiBudWxsLAogICAgICAiZmluaXNoX3JlYXNvbiI6ICJzdG9wIgogICAgfQogIF0sCiAgInVzYWdlIjogewogICAgInByb21wdF90b2tlbnMiOiA5LAogICAgImNvbXBsZXRpb25fdG9rZW5zIjogOSwKICAgICJ0b3RhbF90b2tlbnMiOiAxOCwKICAgICJwcm9tcHRfdG9rZW5zX2RldGFpbHMiOiB7CiAgICAgICJjYWNoZWRfdG9rZW5zIjogMCwKICAgICAgImF1ZGlvX3Rva2VucyI6IDAKICAgIH0sCiAgICAiY29tcGxldGlvbl90b2tlbnNfZGV0YWlscyI6IHsKICAgICAgInJlYXNvbmluZ190b2tlbnMiOiAwLAogICAgICAiYXVkaW9fdG9rZW5zIjogMCwKICAgICAgImFjY2VwdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMCwKICAgICAgInJlamVjdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMAogICAgfQogIH0sCiAgInNlcnZpY2VfdGllciI6ICJkZWZhdWx0IiwKICAic3lzdGVtX2ZpbmdlcnByaW50IjogImZwXzRmY2UwNzc4YWYiCn0K"
}
},
"id": "1757337994532-unknown-host-POST-_v1_chat_completions-1561df08.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@@ -0,0 +1,82 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/chat/completions",
"body": {
"contentType": "application/json",
"type": "JSON",
"json": {
"model": "gpt-4.1-mini",
"stream": false,
"messages": [
{
"role": "user",
"content": "Hello!"
}
]
},
"rawBytes": "eyJtb2RlbCI6ImdwdC00LjEtbWluaSIsInN0cmVhbSI6ZmFsc2UsIm1lc3NhZ2VzIjpbeyJyb2xlIjoidXNlciIsImNvbnRlbnQiOiJIZWxsbyEifV19"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-request-id": ["req_2476c446b9a74442be397d438277a7c1"],
"x-ratelimit-reset-tokens": ["0s"],
"x-ratelimit-reset-requests": ["2ms"],
"x-ratelimit-remaining-tokens": ["149999995"],
"x-ratelimit-remaining-requests": ["29999"],
"x-ratelimit-limit-tokens": ["150000000"]
},
"body": {
"contentType": "application/json",
"type": "JSON",
"json": {
"id": "chatcmpl-CDWB6wRC1FBoWeZr36clt56dB1EzT",
"object": "chat.completion",
"created": 1757337992,
"model": "gpt-4.1-mini-2025-04-14",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! How can I assist you today?",
"refusal": null,
"annotations": []
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 9,
"completion_tokens": 9,
"total_tokens": 18,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"service_tier": "default",
"system_fingerprint": "fp_4fce0778af"
},
"rawBytes": "ewogICJpZCI6ICJjaGF0Y21wbC1DRFdCNndSQzFGQm9XZVpyMzZjbHQ1NmRCMUV6VCIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVhdGVkIjogMTc1NzMzNzk5MiwKICAibW9kZWwiOiAiZ3B0LTQuMS1taW5pLTIwMjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgiOiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Npc3RhbnQiLAogICAgICAgICJjb250ZW50IjogIkhlbGxvISBIb3cgY2FuIEkgYXNzaXN0IHlvdSB0b2RheT8iLAogICAgICAgICJyZWZ1c2FsIjogbnVsbCwKICAgICAgICAiYW5ub3RhdGlvbnMiOiBbXQogICAgICB9LAogICAgICAibG9ncHJvYnMiOiBudWxsLAogICAgICAiZmluaXNoX3JlYXNvbiI6ICJzdG9wIgogICAgfQogIF0sCiAgInVzYWdlIjogewogICAgInByb21wdF90b2tlbnMiOiA5LAogICAgImNvbXBsZXRpb25fdG9rZW5zIjogOSwKICAgICJ0b3RhbF90b2tlbnMiOiAxOCwKICAgICJwcm9tcHRfdG9rZW5zX2RldGFpbHMiOiB7CiAgICAgICJjYWNoZWRfdG9rZW5zIjogMCwKICAgICAgImF1ZGlvX3Rva2VucyI6IDAKICAgIH0sCiAgICAiY29tcGxldGlvbl90b2tlbnNfZGV0YWlscyI6IHsKICAgICAgInJlYXNvbmluZ190b2tlbnMiOiAwLAogICAgICAiYXVkaW9fdG9rZW5zIjogMCwKICAgICAgImFjY2VwdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMCwKICAgICAgInJlamVjdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMAogICAgfQogIH0sCiAgInNlcnZpY2VfdGllciI6ICJkZWZhdWx0IiwKICAic3lzdGVtX2ZpbmdlcnByaW50IjogImZwXzRmY2UwNzc4YWYiCn0K"
}
},
"id": "1757337995023-unknown-host-POST-_v1_chat_completions-1561df08.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@@ -0,0 +1,101 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/chat/completions",
"body": {
"contentType": "application/json",
"type": "JSON",
"json": {
"model": "gpt-4.1-mini",
"stream": false,
"tools": [
{
"type": "function",
"function": {
"name": "calculator",
"description": "Useful for getting the result of a math expression. The input to this tool should be a valid mathematical expression that could be executed by a simple calculator.",
"parameters": {
"type": "object",
"properties": {
"input": {
"type": "string"
}
},
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}
}
}
],
"messages": [
{
"role": "user",
"content": "Hello!"
}
]
},
"rawBytes": "eyJtb2RlbCI6ImdwdC00LjEtbWluaSIsInN0cmVhbSI6ZmFsc2UsInRvb2xzIjpbeyJ0eXBlIjoiZnVuY3Rpb24iLCJmdW5jdGlvbiI6eyJuYW1lIjoiY2FsY3VsYXRvciIsImRlc2NyaXB0aW9uIjoiVXNlZnVsIGZvciBnZXR0aW5nIHRoZSByZXN1bHQgb2YgYSBtYXRoIGV4cHJlc3Npb24uIFRoZSBpbnB1dCB0byB0aGlzIHRvb2wgc2hvdWxkIGJlIGEgdmFsaWQgbWF0aGVtYXRpY2FsIGV4cHJlc3Npb24gdGhhdCBjb3VsZCBiZSBleGVjdXRlZCBieSBhIHNpbXBsZSBjYWxjdWxhdG9yLiIsInBhcmFtZXRlcnMiOnsidHlwZSI6Im9iamVjdCIsInByb3BlcnRpZXMiOnsiaW5wdXQiOnsidHlwZSI6InN0cmluZyJ9fSwiYWRkaXRpb25hbFByb3BlcnRpZXMiOmZhbHNlLCIkc2NoZW1hIjoiaHR0cDovL2pzb24tc2NoZW1hLm9yZy9kcmFmdC0wNy9zY2hlbWEjIn19fV0sIm1lc3NhZ2VzIjpbeyJyb2xlIjoidXNlciIsImNvbnRlbnQiOiJIZWxsbyEifV19"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-request-id": ["req_b9dd655cc0ac465980074d2301a4f058"],
"x-ratelimit-reset-tokens": ["0s"],
"x-ratelimit-reset-requests": ["2ms"],
"x-ratelimit-remaining-tokens": ["149999995"],
"x-ratelimit-remaining-requests": ["29999"],
"x-ratelimit-limit-tokens": ["150000000"]
},
"body": {
"contentType": "application/json",
"type": "JSON",
"json": {
"id": "chatcmpl-CDWBAps4xOl2Ps9V9GQ7RwmRWYGnU",
"object": "chat.completion",
"created": 1757337996,
"model": "gpt-4.1-mini-2025-04-14",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! How can I assist you today?",
"refusal": null,
"annotations": []
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 68,
"completion_tokens": 10,
"total_tokens": 78,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"service_tier": "default",
"system_fingerprint": "fp_4fce0778af"
},
"rawBytes": "ewogICJpZCI6ICJjaGF0Y21wbC1DRFdCQXBzNHhPbDJQczlWOUdRN1J3bVJXWUduVSIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVhdGVkIjogMTc1NzMzNzk5NiwKICAibW9kZWwiOiAiZ3B0LTQuMS1taW5pLTIwMjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgiOiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Npc3RhbnQiLAogICAgICAgICJjb250ZW50IjogIkhlbGxvISBIb3cgY2FuIEkgYXNzaXN0IHlvdSB0b2RheT8iLAogICAgICAgICJyZWZ1c2FsIjogbnVsbCwKICAgICAgICAiYW5ub3RhdGlvbnMiOiBbXQogICAgICB9LAogICAgICAibG9ncHJvYnMiOiBudWxsLAogICAgICAiZmluaXNoX3JlYXNvbiI6ICJzdG9wIgogICAgfQogIF0sCiAgInVzYWdlIjogewogICAgInByb21wdF90b2tlbnMiOiA2OCwKICAgICJjb21wbGV0aW9uX3Rva2VucyI6IDEwLAogICAgInRvdGFsX3Rva2VucyI6IDc4LAogICAgInByb21wdF90b2tlbnNfZGV0YWlscyI6IHsKICAgICAgImNhY2hlZF90b2tlbnMiOiAwLAogICAgICAiYXVkaW9fdG9rZW5zIjogMAogICAgfSwKICAgICJjb21wbGV0aW9uX3Rva2Vuc19kZXRhaWxzIjogewogICAgICAicmVhc29uaW5nX3Rva2VucyI6IDAsCiAgICAgICJhdWRpb190b2tlbnMiOiAwLAogICAgICAiYWNjZXB0ZWRfcHJlZGljdGlvbl90b2tlbnMiOiAwLAogICAgICAicmVqZWN0ZWRfcHJlZGljdGlvbl90b2tlbnMiOiAwCiAgICB9CiAgfSwKICAic2VydmljZV90aWVyIjogImRlZmF1bHQiLAogICJzeXN0ZW1fZmluZ2VycHJpbnQiOiAiZnBfNGZjZTA3NzhhZiIKfQo="
}
},
"id": "1757338000172-unknown-host-POST-_v1_chat_completions-fdac7829.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@@ -0,0 +1,111 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/chat/completions",
"body": {
"contentType": "application/json",
"type": "JSON",
"json": {
"model": "gpt-4.1-mini",
"stream": false,
"tools": [
{
"type": "function",
"function": {
"name": "calculator",
"description": "Useful for getting the result of a math expression. The input to this tool should be a valid mathematical expression that could be executed by a simple calculator.",
"parameters": {
"type": "object",
"properties": {
"input": {
"type": "string"
}
},
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}
}
}
],
"messages": [
{
"role": "user",
"content": "What is 1000 * 10?"
}
]
},
"rawBytes": "eyJtb2RlbCI6ImdwdC00LjEtbWluaSIsInN0cmVhbSI6ZmFsc2UsInRvb2xzIjpbeyJ0eXBlIjoiZnVuY3Rpb24iLCJmdW5jdGlvbiI6eyJuYW1lIjoiY2FsY3VsYXRvciIsImRlc2NyaXB0aW9uIjoiVXNlZnVsIGZvciBnZXR0aW5nIHRoZSByZXN1bHQgb2YgYSBtYXRoIGV4cHJlc3Npb24uIFRoZSBpbnB1dCB0byB0aGlzIHRvb2wgc2hvdWxkIGJlIGEgdmFsaWQgbWF0aGVtYXRpY2FsIGV4cHJlc3Npb24gdGhhdCBjb3VsZCBiZSBleGVjdXRlZCBieSBhIHNpbXBsZSBjYWxjdWxhdG9yLiIsInBhcmFtZXRlcnMiOnsidHlwZSI6Im9iamVjdCIsInByb3BlcnRpZXMiOnsiaW5wdXQiOnsidHlwZSI6InN0cmluZyJ9fSwiYWRkaXRpb25hbFByb3BlcnRpZXMiOmZhbHNlLCIkc2NoZW1hIjoiaHR0cDovL2pzb24tc2NoZW1hLm9yZy9kcmFmdC0wNy9zY2hlbWEjIn19fV0sIm1lc3NhZ2VzIjpbeyJyb2xlIjoidXNlciIsImNvbnRlbnQiOiJXaGF0IGlzIDEwMDAgKiAxMD8ifV19"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-request-id": ["req_56023c245be3448c8373ff3aad21f7c4"],
"x-ratelimit-reset-tokens": ["0s"],
"x-ratelimit-reset-requests": ["2ms"],
"x-ratelimit-remaining-tokens": ["149999992"],
"x-ratelimit-remaining-requests": ["29999"],
"x-ratelimit-limit-tokens": ["150000000"]
},
"body": {
"contentType": "application/json",
"type": "JSON",
"json": {
"id": "chatcmpl-CDWBDR6zMgzDe9t4hkJ0Xq3dxsK3y",
"object": "chat.completion",
"created": 1757337999,
"model": "gpt-4.1-mini-2025-04-14",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_88I326c3cCx7lOEXL3Wpp30c",
"type": "function",
"function": {
"name": "calculator",
"arguments": "{\"input\":\"1000 * 10\"}"
}
}
],
"refusal": null,
"annotations": []
},
"logprobs": null,
"finish_reason": "tool_calls"
}
],
"usage": {
"prompt_tokens": 75,
"completion_tokens": 18,
"total_tokens": 93,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"service_tier": "default",
"system_fingerprint": "fp_4fce0778af"
},
"rawBytes": "ewogICJpZCI6ICJjaGF0Y21wbC1DRFdCRFI2ek1nekRlOXQ0aGtKMFhxM2R4c0szeSIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVhdGVkIjogMTc1NzMzNzk5OSwKICAibW9kZWwiOiAiZ3B0LTQuMS1taW5pLTIwMjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgiOiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Npc3RhbnQiLAogICAgICAgICJjb250ZW50IjogbnVsbCwKICAgICAgICAidG9vbF9jYWxscyI6IFsKICAgICAgICAgIHsKICAgICAgICAgICAgImlkIjogImNhbGxfODhJMzI2YzNjQ3g3bE9FWEwzV3BwMzBjIiwKICAgICAgICAgICAgInR5cGUiOiAiZnVuY3Rpb24iLAogICAgICAgICAgICAiZnVuY3Rpb24iOiB7CiAgICAgICAgICAgICAgIm5hbWUiOiAiY2FsY3VsYXRvciIsCiAgICAgICAgICAgICAgImFyZ3VtZW50cyI6ICJ7XCJpbnB1dFwiOlwiMTAwMCAqIDEwXCJ9IgogICAgICAgICAgICB9CiAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAicmVmdXNhbCI6IG51bGwsCiAgICAgICAgImFubm90YXRpb25zIjogW10KICAgICAgfSwKICAgICAgImxvZ3Byb2JzIjogbnVsbCwKICAgICAgImZpbmlzaF9yZWFzb24iOiAidG9vbF9jYWxscyIKICAgIH0KICBdLAogICJ1c2FnZSI6IHsKICAgICJwcm9tcHRfdG9rZW5zIjogNzUsCiAgICAiY29tcGxldGlvbl90b2tlbnMiOiAxOCwKICAgICJ0b3RhbF90b2tlbnMiOiA5MywKICAgICJwcm9tcHRfdG9rZW5zX2RldGFpbHMiOiB7CiAgICAgICJjYWNoZWRfdG9rZW5zIjogMCwKICAgICAgImF1ZGlvX3Rva2VucyI6IDAKICAgIH0sCiAgICAiY29tcGxldGlvbl90b2tlbnNfZGV0YWlscyI6IHsKICAgICAgInJlYXNvbmluZ190b2tlbnMiOiAwLAogICAgICAiYXVkaW9fdG9rZW5zIjogMCwKICAgICAgImFjY2VwdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMCwKICAgICAgInJlamVjdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMAogICAgfQogIH0sCiAgInNlcnZpY2VfdGllciI6ICJkZWZhdWx0IiwKICAic3lzdGVtX2ZpbmdlcnByaW50IjogImZwXzRmY2UwNzc4YWYiCn0K"
}
},
"id": "1757338002750-unknown-host-POST-_v1_chat_completions-9d7fafef.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@@ -0,0 +1,120 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/chat/completions",
"body": {
"contentType": "application/json",
"type": "JSON",
"json": {
"model": "gpt-4.1-mini",
"stream": false,
"tools": [
{
"type": "function",
"function": {
"name": "calculator",
"description": "Useful for getting the result of a math expression. The input to this tool should be a valid mathematical expression that could be executed by a simple calculator.",
"parameters": {
"type": "object",
"properties": {
"input": {
"type": "string"
}
},
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}
}
}
],
"messages": [
{
"role": "user",
"content": "What is 1000 * 10?"
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_88I326c3cCx7lOEXL3Wpp30c",
"type": "function",
"function": {
"name": "calculator",
"arguments": "{\"input\":\"1000 * 10\"}"
}
}
]
},
{
"role": "tool",
"content": "10000",
"tool_call_id": "call_88I326c3cCx7lOEXL3Wpp30c"
}
]
},
"rawBytes": "eyJtb2RlbCI6ImdwdC00LjEtbWluaSIsInN0cmVhbSI6ZmFsc2UsInRvb2xzIjpbeyJ0eXBlIjoiZnVuY3Rpb24iLCJmdW5jdGlvbiI6eyJuYW1lIjoiY2FsY3VsYXRvciIsImRlc2NyaXB0aW9uIjoiVXNlZnVsIGZvciBnZXR0aW5nIHRoZSByZXN1bHQgb2YgYSBtYXRoIGV4cHJlc3Npb24uIFRoZSBpbnB1dCB0byB0aGlzIHRvb2wgc2hvdWxkIGJlIGEgdmFsaWQgbWF0aGVtYXRpY2FsIGV4cHJlc3Npb24gdGhhdCBjb3VsZCBiZSBleGVjdXRlZCBieSBhIHNpbXBsZSBjYWxjdWxhdG9yLiIsInBhcmFtZXRlcnMiOnsidHlwZSI6Im9iamVjdCIsInByb3BlcnRpZXMiOnsiaW5wdXQiOnsidHlwZSI6InN0cmluZyJ9fSwiYWRkaXRpb25hbFByb3BlcnRpZXMiOmZhbHNlLCIkc2NoZW1hIjoiaHR0cDovL2pzb24tc2NoZW1hLm9yZy9kcmFmdC0wNy9zY2hlbWEjIn19fV0sIm1lc3NhZ2VzIjpbeyJyb2xlIjoidXNlciIsImNvbnRlbnQiOiJXaGF0IGlzIDEwMDAgKiAxMD8ifSx7InJvbGUiOiJhc3Npc3RhbnQiLCJjb250ZW50IjoiIiwidG9vbF9jYWxscyI6W3siaWQiOiJjYWxsXzg4STMyNmMzY0N4N2xPRVhMM1dwcDMwYyIsInR5cGUiOiJmdW5jdGlvbiIsImZ1bmN0aW9uIjp7Im5hbWUiOiJjYWxjdWxhdG9yIiwiYXJndW1lbnRzIjoie1wiaW5wdXRcIjpcIjEwMDAgKiAxMFwifSJ9fV19LHsicm9sZSI6InRvb2wiLCJjb250ZW50IjoiMTAwMDAiLCJ0b29sX2NhbGxfaWQiOiJjYWxsXzg4STMyNmMzY0N4N2xPRVhMM1dwcDMwYyJ9XX0="
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-request-id": ["req_65b7f6b5aad740e0965555ae8df92d43"],
"x-ratelimit-reset-tokens": ["0s"],
"x-ratelimit-reset-requests": ["2ms"],
"x-ratelimit-remaining-tokens": ["149999992"],
"x-ratelimit-remaining-requests": ["29999"],
"x-ratelimit-limit-tokens": ["150000000"]
},
"body": {
"contentType": "application/json",
"type": "JSON",
"json": {
"id": "chatcmpl-CDWBEPUWN3El2oq4aRaezkvUlRjv8",
"object": "chat.completion",
"created": 1757338000,
"model": "gpt-4.1-mini-2025-04-14",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "1000 multiplied by 10 equals 10,000.",
"refusal": null,
"annotations": []
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 102,
"completion_tokens": 13,
"total_tokens": 115,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"service_tier": "default",
"system_fingerprint": "fp_4fce0778af"
},
"rawBytes": "ewogICJpZCI6ICJjaGF0Y21wbC1DRFdCRVBVV04zRWwyb3E0YVJhZXprdlVsUmp2OCIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVhdGVkIjogMTc1NzMzODAwMCwKICAibW9kZWwiOiAiZ3B0LTQuMS1taW5pLTIwMjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgiOiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Npc3RhbnQiLAogICAgICAgICJjb250ZW50IjogIjEwMDAgbXVsdGlwbGllZCBieSAxMCBlcXVhbHMgMTAsMDAwLiIsCiAgICAgICAgInJlZnVzYWwiOiBudWxsLAogICAgICAgICJhbm5vdGF0aW9ucyI6IFtdCiAgICAgIH0sCiAgICAgICJsb2dwcm9icyI6IG51bGwsCiAgICAgICJmaW5pc2hfcmVhc29uIjogInN0b3AiCiAgICB9CiAgXSwKICAidXNhZ2UiOiB7CiAgICAicHJvbXB0X3Rva2VucyI6IDEwMiwKICAgICJjb21wbGV0aW9uX3Rva2VucyI6IDEzLAogICAgInRvdGFsX3Rva2VucyI6IDExNSwKICAgICJwcm9tcHRfdG9rZW5zX2RldGFpbHMiOiB7CiAgICAgICJjYWNoZWRfdG9rZW5zIjogMCwKICAgICAgImF1ZGlvX3Rva2VucyI6IDAKICAgIH0sCiAgICAiY29tcGxldGlvbl90b2tlbnNfZGV0YWlscyI6IHsKICAgICAgInJlYXNvbmluZ190b2tlbnMiOiAwLAogICAgICAiYXVkaW9fdG9rZW5zIjogMCwKICAgICAgImFjY2VwdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMCwKICAgICAgInJlamVjdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMAogICAgfQogIH0sCiAgInNlcnZpY2VfdGllciI6ICJkZWZhdWx0IiwKICAic3lzdGVtX2ZpbmdlcnByaW50IjogImZwXzRmY2UwNzc4YWYiCn0K"
}
},
"id": "1757338002751-unknown-host-POST-_v1_chat_completions-e8887e9d.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@@ -34,6 +34,7 @@
"n8n-containers": "workspace:*",
"n8n-core": "workspace:*",
"n8n-workflow": "workspace:*",
"flatted": "catalog:",
"nanoid": "catalog:",
"tsx": "catalog:",
"mockserver-client": "^5.15.0",

View File

@@ -541,6 +541,28 @@ export class CanvasPage extends BasePage {
await this.clickContextMenuAction('execute');
}
async clearExecutionData(): Promise<void> {
await this.page.getByTestId('clear-execution-data-button').click();
}
getManualChatModal(): Locator {
return this.page.getByTestId('canvas-chat');
}
getManualChatInput(): Locator {
return this.getManualChatModal().locator('.chat-inputs textarea');
}
getManualChatMessages(): Locator {
return this.getManualChatModal().locator('.chat-messages-list .chat-message');
}
getManualChatLatestBotMessage(): Locator {
return this.getManualChatModal()
.locator('.chat-messages-list .chat-message.chat-message-from-bot')
.last();
}
getNodesWithSpinner(): Locator {
return this.page.getByTestId('canvas-node').filter({
has: this.page.locator('[data-icon=refresh-cw]'),
@@ -560,6 +582,67 @@ export class CanvasPage extends BasePage {
return this.page.locator('[data-test-id="canvas-node"].selected');
}
// Disable node via context menu
async disableNodeFromContextMenu(nodeName: string): Promise<void> {
await this.rightClickNode(nodeName);
await this.page
.getByTestId('context-menu')
.getByTestId('context-menu-item-toggle_activation')
.click();
}
// Chat open/close buttons (manual chat)
async clickManualChatButton(): Promise<void> {
await this.page.getByTestId('workflow-chat-button').click();
await this.getManualChatModal().waitFor({ state: 'visible' });
}
async closeManualChatModal(): Promise<void> {
// Same toggle button closes the chat
await this.page.getByTestId('workflow-chat-button').click();
}
// Input plus endpoints (to add supplemental nodes to parent inputs)
getInputPlusEndpointByType(nodeName: string, endpointType: string) {
return this.page
.locator(
`[data-test-id="canvas-node-input-handle"][data-connection-type="${endpointType}"][data-node-name="${nodeName}"] [data-test-id="canvas-handle-plus"]`,
)
.first();
}
// Generic supplemental node addition, then wrappers for specific types
async addSupplementalNodeToParent(
childNodeName: string,
endpointType:
| 'main'
| 'ai_chain'
| 'ai_document'
| 'ai_embedding'
| 'ai_languageModel'
| 'ai_memory'
| 'ai_outputParser'
| 'ai_tool'
| 'ai_retriever'
| 'ai_textSplitter'
| 'ai_vectorRetriever'
| 'ai_vectorStore',
parentNodeName: string,
{ closeNDV = false, exactMatch = false }: { closeNDV?: boolean; exactMatch?: boolean } = {},
): Promise<void> {
await this.getInputPlusEndpointByType(parentNodeName, endpointType).click();
if (exactMatch) {
await this.nodeCreatorNodeItems().getByText(childNodeName, { exact: true }).click();
} else {
await this.nodeCreatorNodeItems().filter({ hasText: childNodeName }).first().click();
}
if (closeNDV) {
await this.page.keyboard.press('Escape');
}
}
async openExecutions() {
await this.page.getByTestId('radio-button-executions').click();
}

View File

@@ -0,0 +1,67 @@
import type { Locator, Page } from '@playwright/test';
import { expect } from '@playwright/test';
import { BasePage } from './BasePage';
export class CredentialsEditModal extends BasePage {
constructor(page: Page) {
super(page);
}
getModal(): Locator {
return this.page.getByTestId('editCredential-modal');
}
async waitForModal(): Promise<void> {
await this.getModal().waitFor({ state: 'visible' });
}
async fillField(key: string, value: string): Promise<void> {
const input = this.page.getByTestId(`parameter-input-${key}`).locator('input, textarea');
await input.fill(value);
await expect(input).toHaveValue(value);
}
async fillAllFields(values: Record<string, string>): Promise<void> {
for (const [key, val] of Object.entries(values)) {
await this.fillField(key, val);
}
}
getSaveButton(): Locator {
return this.page.getByTestId('credential-save-button');
}
async save(): Promise<void> {
const saveBtn = this.getSaveButton();
await saveBtn.click();
await saveBtn.waitFor({ state: 'visible' });
// Saved state changes the button text to "Saved"
// Defensive wait for text when UI updates
try {
await saveBtn
.getByText('Saved', { exact: true })
.waitFor({ state: 'visible', timeout: 3000 });
} catch {
// ignore if text assertion is flaky; modal close below will still ensure flow continues
}
}
async close(): Promise<void> {
const closeBtn = this.getModal().locator('.el-dialog__close').first();
if (await closeBtn.isVisible()) {
await closeBtn.click();
}
}
async setValues(values: Record<string, string>, save: boolean = true): Promise<void> {
await this.waitForModal();
await this.fillAllFields(values);
if (save) {
await this.save();
await this.close();
}
}
}

View File

@@ -52,10 +52,18 @@ export class NodeDetailsViewPage extends BasePage {
await this.clickByTestId('node-execute-button');
}
getOutputPanel() {
return this.page.getByTestId('output-panel');
}
getContainer() {
return this.page.getByTestId('ndv');
}
getInputPanel() {
return this.page.getByTestId('ndv-input-panel');
}
getParameterExpressionPreviewValue() {
return this.page.getByTestId('parameter-expression-preview-value');
}
@@ -81,6 +89,14 @@ export class NodeDetailsViewPage extends BasePage {
return this.page.getByTestId('run-data-pane-header');
}
getOutputTable() {
return this.getOutputPanel().getByTestId('ndv-data-container').locator('table');
}
getOutputDataContainer() {
return this.getOutputPanel().getByTestId('ndv-data-container');
}
async setPinnedData(data: object | string) {
const pinnedData = typeof data === 'string' ? data : JSON.stringify(data);
await this.getEditPinnedDataButton().click();
@@ -373,6 +389,14 @@ export class NodeDetailsViewPage extends BasePage {
await this.page.getByRole('option', { name: nodeName }).click();
}
getInputTableHeader(index: number = 0) {
return this.getInputPanel().locator('table th').nth(index);
}
getInputTbodyCell(row: number, col: number) {
return this.getInputPanel().locator('table tbody tr').nth(row).locator('td').nth(col);
}
getAssignmentName(paramName: string, index = 0) {
return this.getAssignmentCollectionContainer(paramName)
.getByTestId('assignment')
@@ -457,6 +481,14 @@ export class NodeDetailsViewPage extends BasePage {
await input.type(content);
}
getInputTable() {
return this.getInputPanel().locator('table');
}
getInputTableCellSpan(row: number, col: number, dataName: string) {
return this.getInputTbodyCell(row, col).locator(`span[data-name="${dataName}"]`).first();
}
getAddFieldToSortByButton() {
return this.getNodeParameters().getByText('Add Field To Sort By');
}
@@ -493,6 +525,46 @@ export class NodeDetailsViewPage extends BasePage {
await input.fill(value);
}
async clickGetBackToCanvas(): Promise<void> {
await this.clickBackToCanvasButton();
}
getRunDataInfoCallout() {
return this.page.getByTestId('run-data-callout');
}
getOutputPanelTable() {
return this.getOutputTable();
}
async checkParameterCheckboxInputByName(name: string): Promise<void> {
const checkbox = this.getParameterInput(name).locator('.el-switch.switch-input');
await checkbox.click();
}
// Credentials modal helpers
async clickCreateNewCredential(eq: number = 0): Promise<void> {
await this.page.getByTestId('node-credentials-select').nth(eq).click();
await this.page.getByTestId('node-credentials-select-item-new').click();
}
// Run selector and linking helpers
getInputRunSelector() {
return this.page.locator('[data-test-id="ndv-input-panel"] [data-test-id="run-selector"]');
}
getOutputRunSelector() {
return this.page.locator('[data-test-id="output-panel"] [data-test-id="run-selector"]');
}
getInputRunSelectorInput() {
return this.getInputRunSelector().locator('input');
}
async toggleInputRunLinking(): Promise<void> {
await this.getInputPanel().getByTestId('link-run').click();
}
getNodeRunErrorMessage() {
return this.page.getByTestId('node-error-message');
}
@@ -725,6 +797,19 @@ export class NodeDetailsViewPage extends BasePage {
getInputSelect() {
return this.page.getByTestId('ndv-input-select').locator('input');
}
getInputTableRows() {
return this.getInputTable().locator('tr');
}
getOutputRunSelectorInput() {
return this.getOutputPanel().locator('[data-test-id="run-selector"] input');
}
getAiOutputModeToggle() {
return this.page.getByTestId('ai-output-mode-select');
}
getCredentialLabel(credentialType: string) {
return this.page.getByText(credentialType);
}

View File

@@ -4,6 +4,7 @@ import { AIAssistantPage } from './AIAssistantPage';
import { BecomeCreatorCTAPage } from './BecomeCreatorCTAPage';
import { CanvasPage } from './CanvasPage';
import { CommunityNodesPage } from './CommunityNodesPage';
import { CredentialsEditModal } from './CredentialsEditModal';
import { CredentialsPage } from './CredentialsPage';
import { DemoPage } from './DemoPage';
import { ExecutionsPage } from './ExecutionsPage';
@@ -59,6 +60,7 @@ export class n8nPage {
readonly workflowActivationModal: WorkflowActivationModal;
readonly workflowSettingsModal: WorkflowSettingsModal;
readonly workflowSharingModal: WorkflowSharingModal;
readonly credentialsModal: CredentialsEditModal;
// Composables
readonly workflowComposer: WorkflowComposer;
@@ -98,6 +100,7 @@ export class n8nPage {
// Modals
this.workflowActivationModal = new WorkflowActivationModal(page);
this.workflowSettingsModal = new WorkflowSettingsModal(page);
this.credentialsModal = new CredentialsEditModal(page);
// Composables
this.workflowComposer = new WorkflowComposer(this);

View File

@@ -0,0 +1,534 @@
import {
AGENT_NODE_NAME,
EDIT_FIELDS_SET_NODE_NAME,
MANUAL_CHAT_TRIGGER_NODE_NAME,
AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME,
AI_MEMORY_WINDOW_BUFFER_MEMORY_NODE_NAME,
AI_MEMORY_POSTGRES_NODE_NAME,
AI_TOOL_CALCULATOR_NODE_NAME,
AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME,
AI_TOOL_CODE_NODE_NAME,
AI_TOOL_WIKIPEDIA_NODE_NAME,
BASIC_LLM_CHAIN_NODE_NAME,
CHAT_TRIGGER_NODE_DISPLAY_NAME,
SCHEDULE_TRIGGER_NODE_NAME,
} from '../../config/constants';
import { test, expect } from '../../fixtures/base';
import type { n8nPage } from '../../pages/n8nPage';
// Helper functions for common operations
async function addOpenAILanguageModelWithCredentials(
n8n: n8nPage,
parentNode: string,
options: { exactMatch?: boolean; closeNDV?: boolean } = { exactMatch: true, closeNDV: false },
) {
await n8n.canvas.addSupplementalNodeToParent(
AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME,
'ai_languageModel',
parentNode,
options,
);
await n8n.ndv.clickCreateNewCredential();
await n8n.credentialsModal.setValues({
apiKey: 'abcd',
});
await n8n.ndv.clickBackToCanvasButton();
}
async function waitForWorkflowSuccess(n8n: n8nPage, timeout = 3000) {
await n8n.notifications.waitForNotificationAndClose('Workflow executed successfully', {
timeout,
});
}
async function executeChatAndWaitForResponse(n8n: n8nPage, message: string) {
await n8n.canvas.logsPanel.sendManualChatMessage(message);
await waitForWorkflowSuccess(n8n);
}
async function verifyChatMessages(n8n: n8nPage, expectedCount: number, inputMessage?: string) {
const messages = n8n.canvas.getManualChatMessages();
await expect(messages).toHaveCount(expectedCount);
if (inputMessage) {
await expect(messages.first()).toContainText(inputMessage);
}
await expect(messages.last()).toBeVisible();
return messages;
}
async function verifyLogsPanelEntries(n8n: n8nPage, expectedEntries: string[]) {
await expect(n8n.canvas.logsPanel.getLogEntries().first()).toBeVisible();
await expect(n8n.canvas.logsPanel.getLogEntries()).toHaveCount(expectedEntries.length);
for (let i = 0; i < expectedEntries.length; i++) {
await expect(n8n.canvas.logsPanel.getLogEntries().nth(i)).toHaveText(expectedEntries[i]);
}
}
async function setupBasicAgentWorkflow(n8n: n8nPage, additionalNodes: string[] = []) {
await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: true });
// Add additional nodes if specified
for (const nodeName of additionalNodes) {
await n8n.canvas.addSupplementalNodeToParent(nodeName, 'ai_tool', AGENT_NODE_NAME, {
closeNDV: true,
});
}
// Always add OpenAI Language Model
await addOpenAILanguageModelWithCredentials(n8n, AGENT_NODE_NAME);
}
test.describe('Langchain Integration @capability:proxy', () => {
test.beforeEach(async ({ n8n, proxyServer }) => {
await proxyServer.clearAllExpectations();
await proxyServer.loadExpectations('langchain');
await n8n.canvas.openNewWorkflow();
});
test.describe('Workflow Execution Behavior', () => {
test('should not open chat modal', async ({ n8n }) => {
await n8n.canvas.addNode(EDIT_FIELDS_SET_NODE_NAME, { closeNDV: true });
await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: true });
await n8n.canvas.addSupplementalNodeToParent(
AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME,
'ai_languageModel',
AGENT_NODE_NAME,
{ exactMatch: true, closeNDV: true },
);
await n8n.canvas.clickExecuteWorkflowButton();
await expect(n8n.canvas.getManualChatModal()).toBeHidden();
});
test('should remove test workflow button', async ({ n8n }) => {
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
await n8n.canvas.addNode(EDIT_FIELDS_SET_NODE_NAME, { closeNDV: true });
await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: true });
await n8n.canvas.addSupplementalNodeToParent(
AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME,
'ai_languageModel',
AGENT_NODE_NAME,
{ exactMatch: true, closeNDV: true },
);
await n8n.canvas.disableNodeFromContextMenu(SCHEDULE_TRIGGER_NODE_NAME);
await expect(n8n.canvas.getExecuteWorkflowButton()).toBeHidden();
});
});
test.describe('Node Connection and Configuration', () => {
test('should add nodes to all Agent node input types', async ({ n8n }) => {
const agentSubNodes = [
AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME,
AI_MEMORY_WINDOW_BUFFER_MEMORY_NODE_NAME,
AI_TOOL_CALCULATOR_NODE_NAME,
AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME,
];
await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: false });
await n8n.ndv.checkParameterCheckboxInputByName('hasOutputParser');
await n8n.ndv.clickBackToCanvasButton();
await n8n.canvas.addSupplementalNodeToParent(
AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME,
'ai_languageModel',
AGENT_NODE_NAME,
{ exactMatch: true, closeNDV: true },
);
await n8n.canvas.addSupplementalNodeToParent(
AI_MEMORY_WINDOW_BUFFER_MEMORY_NODE_NAME,
'ai_memory',
AGENT_NODE_NAME,
{ closeNDV: true },
);
await n8n.canvas.addSupplementalNodeToParent(
AI_TOOL_CALCULATOR_NODE_NAME,
'ai_tool',
AGENT_NODE_NAME,
{ closeNDV: true },
);
await n8n.canvas.addSupplementalNodeToParent(
AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME,
'ai_outputParser',
AGENT_NODE_NAME,
{ closeNDV: true },
);
for (const nodeName of agentSubNodes) {
await expect(n8n.canvas.connectionBetweenNodes(nodeName, AGENT_NODE_NAME)).toBeAttached();
}
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2 + agentSubNodes.length); // Chat Trigger + Agent + 4 inputs
});
test('should add multiple tool nodes to Agent node tool input type', async ({ n8n }) => {
await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: true });
const tools = [
AI_TOOL_CALCULATOR_NODE_NAME,
AI_TOOL_CODE_NODE_NAME,
AI_TOOL_CODE_NODE_NAME,
AI_TOOL_WIKIPEDIA_NODE_NAME,
];
for (const tool of tools) {
await n8n.canvas.addSupplementalNodeToParent(tool, 'ai_tool', AGENT_NODE_NAME, {
closeNDV: true,
});
await expect(n8n.canvas.connectionBetweenNodes(tool, AGENT_NODE_NAME)).toBeAttached();
}
// Chat Trigger + Agent + Tools
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2 + tools.length);
});
});
test.describe('Auto-add Behavior', () => {
test('should auto-add chat trigger and basic LLM chain when adding LLM node', async ({
n8n,
}) => {
await n8n.canvas.addNode(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, { closeNDV: true });
await expect(
n8n.canvas.connectionBetweenNodes(
CHAT_TRIGGER_NODE_DISPLAY_NAME,
BASIC_LLM_CHAIN_NODE_NAME,
),
).toBeAttached();
await expect(
n8n.canvas.connectionBetweenNodes(
AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME,
BASIC_LLM_CHAIN_NODE_NAME,
),
).toBeAttached();
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(3);
});
test('should not auto-add nodes if AI nodes are already present', async ({ n8n }) => {
await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: true });
await n8n.canvas.addNode(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, { closeNDV: true });
await expect(
n8n.canvas.connectionBetweenNodes(CHAT_TRIGGER_NODE_DISPLAY_NAME, AGENT_NODE_NAME),
).toBeAttached();
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(3);
});
test('should not auto-add nodes if ChatTrigger is already present', async ({ n8n }) => {
await n8n.canvas.addNode(MANUAL_CHAT_TRIGGER_NODE_NAME, { closeNDV: true });
await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: true });
await n8n.canvas.addNode(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, { closeNDV: true });
await expect(
n8n.canvas.connectionBetweenNodes(CHAT_TRIGGER_NODE_DISPLAY_NAME, AGENT_NODE_NAME),
).toBeAttached();
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(3);
});
});
test.describe('Chat Execution and Interaction', () => {
test('should be able to open and execute Basic LLM Chain node', async ({ n8n }) => {
await n8n.canvas.addNode(BASIC_LLM_CHAIN_NODE_NAME, { closeNDV: true });
await addOpenAILanguageModelWithCredentials(n8n, BASIC_LLM_CHAIN_NODE_NAME);
await n8n.canvas.openNode(BASIC_LLM_CHAIN_NODE_NAME);
const inputMessage = 'Hello!';
await n8n.ndv.execute();
await executeChatAndWaitForResponse(n8n, inputMessage);
// Verify chat message appears
await expect(n8n.canvas.getManualChatLatestBotMessage()).toBeVisible();
});
test('should be able to open and execute Agent node', async ({ n8n }) => {
await setupBasicAgentWorkflow(n8n);
const inputMessage = 'Hello!';
await n8n.canvas.clickManualChatButton();
await executeChatAndWaitForResponse(n8n, inputMessage);
// Verify chat message appears
await expect(n8n.canvas.getManualChatLatestBotMessage()).toBeVisible();
});
test('should add and use Manual Chat Trigger node together with Agent node', async ({
n8n,
}) => {
await setupBasicAgentWorkflow(n8n);
const inputMessage = 'Hello!';
await n8n.canvas.clickManualChatButton();
await executeChatAndWaitForResponse(n8n, inputMessage);
await verifyChatMessages(n8n, 2, inputMessage);
await verifyLogsPanelEntries(n8n, [
'When chat message received',
'AI Agent',
'OpenAI Chat Model',
]);
await n8n.canvas.closeManualChatModal();
await expect(n8n.canvas.logsPanel.getLogEntries()).toBeHidden();
await expect(n8n.canvas.getManualChatInput()).toBeHidden();
});
});
test.describe('Tool Usage Notifications', () => {
test('should show tool info notice if no existing tools were used during execution', async ({
n8n,
}) => {
await setupBasicAgentWorkflow(n8n, [AI_TOOL_CALCULATOR_NODE_NAME]);
await n8n.canvas.openNode(AGENT_NODE_NAME);
const inputMessage = 'Hello!';
await n8n.ndv.execute();
await executeChatAndWaitForResponse(n8n, inputMessage);
await n8n.canvas.closeManualChatModal();
await n8n.canvas.openNode(AGENT_NODE_NAME);
await expect(n8n.ndv.getRunDataInfoCallout()).toBeVisible();
});
test('should not show tool info notice if tools were used during execution', async ({
n8n,
}) => {
await n8n.canvas.addNode(MANUAL_CHAT_TRIGGER_NODE_NAME, { closeNDV: true });
await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: false });
await expect(n8n.ndv.getRunDataInfoCallout()).toBeHidden();
await n8n.ndv.clickBackToCanvasButton();
await addOpenAILanguageModelWithCredentials(n8n, AGENT_NODE_NAME);
await n8n.canvas.addSupplementalNodeToParent(
AI_TOOL_CALCULATOR_NODE_NAME,
'ai_tool',
AGENT_NODE_NAME,
{ closeNDV: true },
);
const inputMessage = 'What is 1000 * 10?';
await n8n.canvas.clickManualChatButton();
await executeChatAndWaitForResponse(n8n, inputMessage);
await n8n.canvas.closeManualChatModal();
await n8n.canvas.openNode(AGENT_NODE_NAME);
await expect(n8n.ndv.getRunDataInfoCallout()).toBeHidden();
});
});
test.describe('Error Handling and Logs Display', () => {
// Helper function to set up the agent workflow with Postgres error configuration
async function setupAgentWorkflowWithPostgresError(n8n: n8nPage) {
await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: true });
// Add Calculator Tool (required for OpenAI model)
await n8n.canvas.addSupplementalNodeToParent(
AI_TOOL_CALCULATOR_NODE_NAME,
'ai_tool',
AGENT_NODE_NAME,
{ closeNDV: true },
);
// Add and configure Postgres Memory
await n8n.canvas.addSupplementalNodeToParent(
AI_MEMORY_POSTGRES_NODE_NAME,
'ai_memory',
AGENT_NODE_NAME,
{ closeNDV: false },
);
await n8n.ndv.clickCreateNewCredential();
await n8n.credentialsModal.setValues({
password: 'testtesttest',
});
await n8n.ndv.getParameterInput('sessionIdType').click();
await n8n.page.getByRole('option', { name: 'Define below' }).click();
await n8n.ndv.getParameterInput('sessionKey').locator('input').fill('asdasd');
await n8n.ndv.clickBackToCanvasButton();
// Add and configure OpenAI Language Model
await addOpenAILanguageModelWithCredentials(n8n, AGENT_NODE_NAME);
await n8n.canvas.clickZoomToFitButton();
}
// Helper function to assert logs tab is active
async function assertLogsTabIsActive(n8n: n8nPage) {
await expect(n8n.ndv.getOutputDataContainer()).toBeVisible();
await expect(n8n.ndv.getAiOutputModeToggle()).toBeVisible();
const radioButtons = n8n.ndv.getAiOutputModeToggle().locator('[role="radio"]');
await expect(radioButtons).toHaveCount(2);
await expect(radioButtons.nth(1)).toHaveAttribute('aria-checked', 'true');
}
// Helper function to assert error message is visible
async function assertErrorMessageVisible(n8n: n8nPage) {
await expect(
n8n.ndv.getOutputPanel().getByTestId('node-error-message').first(),
).toBeVisible();
await expect(
n8n.ndv.getOutputPanel().getByTestId('node-error-message').first(),
).toContainText('Error in sub-node');
}
test('should open logs tab by default when there was an error', async ({ n8n }) => {
await setupAgentWorkflowWithPostgresError(n8n);
const inputMessage = 'Test the code tool';
// Execute workflow with chat trigger
await n8n.canvas.clickManualChatButton();
await executeChatAndWaitForResponse(n8n, inputMessage);
// Check that messages and logs are displayed
const messages = await verifyChatMessages(n8n, 2, inputMessage);
await expect(messages.last()).toContainText(
'[ERROR: The service refused the connection - perhaps it is offline]',
);
await expect(n8n.canvas.logsPanel.getLogEntries().first()).toBeVisible();
await expect(n8n.canvas.logsPanel.getLogEntries()).toHaveCount(3);
await expect(n8n.canvas.logsPanel.getSelectedLogEntry()).toHaveText('AI Agent');
await expect(n8n.canvas.logsPanel.outputPanel.get()).toContainText(
AI_MEMORY_POSTGRES_NODE_NAME,
);
await n8n.canvas.closeManualChatModal();
// Open the AI Agent node to see the logs
await n8n.canvas.openNode(AGENT_NODE_NAME);
// Assert that logs tab is active and error is displayed
await assertLogsTabIsActive(n8n);
await assertErrorMessageVisible(n8n);
});
test('should switch to logs tab on error, when NDV is already opened', async ({ n8n }) => {
// Remove the auto-added chat trigger
await n8n.canvas.addNode(MANUAL_CHAT_TRIGGER_NODE_NAME, { closeNDV: false });
// Set manual trigger to output standard pinned data
await n8n.ndv.getEditPinnedDataButton().click();
await n8n.ndv.savePinnedData();
await n8n.ndv.close();
// Set up the same workflow components but with manual trigger
await setupAgentWorkflowWithPostgresError(n8n);
// Open the AI Agent node
await n8n.canvas.openNode(AGENT_NODE_NAME);
await n8n.ndv.getParameterInput('promptType').click();
await n8n.page.getByRole('option', { name: 'Define below' }).click();
await n8n.ndv.getParameterInput('text').locator('textarea').fill('Some text');
await n8n.ndv.execute();
await waitForWorkflowSuccess(n8n);
// Assert that logs tab is active and error is displayed
await assertLogsTabIsActive(n8n);
await assertErrorMessageVisible(n8n);
});
});
test.describe('Advanced Workflow Features', () => {
test('should render runItems for sub-nodes and allow switching between them', async ({
n8n,
}) => {
await n8n.start.fromImportedWorkflow('In_memory_vector_store_fake_embeddings.json');
await n8n.canvas.clickZoomToFitButton();
await n8n.canvas.deselectAll();
await n8n.canvas.executeNode('Populate VS');
await waitForWorkflowSuccess(n8n);
const assertInputOutputTextExists = async (text: string) => {
await expect(n8n.ndv.getOutputPanel()).toContainText(text);
await expect(n8n.ndv.getInputPanel()).toContainText(text);
};
const assertInputOutputTextNotExists = async (text: string) => {
await expect(n8n.ndv.getOutputPanel()).not.toContainText(text);
await expect(n8n.ndv.getInputPanel()).not.toContainText(text);
};
await n8n.canvas.openNode('Character Text Splitter');
await expect(n8n.ndv.getOutputRunSelector()).toBeVisible();
await expect(n8n.ndv.getInputRunSelector()).toBeVisible();
await expect(n8n.ndv.getInputRunSelectorInput()).toHaveValue('3 of 3');
await expect(n8n.ndv.getOutputRunSelectorInput()).toHaveValue('3 of 3');
await assertInputOutputTextExists('Kyiv');
await assertInputOutputTextNotExists('Berlin');
await assertInputOutputTextNotExists('Prague');
await n8n.ndv.changeOutputRunSelector('2 of 3');
await assertInputOutputTextExists('Berlin');
await assertInputOutputTextNotExists('Kyiv');
await assertInputOutputTextNotExists('Prague');
await n8n.ndv.changeOutputRunSelector('1 of 3');
await assertInputOutputTextExists('Prague');
await assertInputOutputTextNotExists('Berlin');
await assertInputOutputTextNotExists('Kyiv');
await n8n.ndv.toggleInputRunLinking();
await n8n.ndv.changeOutputRunSelector('2 of 3');
await expect(n8n.ndv.getInputRunSelectorInput()).toHaveValue('1 of 3');
await expect(n8n.ndv.getOutputRunSelectorInput()).toHaveValue('2 of 3');
await expect(n8n.ndv.getInputPanel()).toContainText('Prague');
await expect(n8n.ndv.getInputPanel()).not.toContainText('Berlin');
await expect(n8n.ndv.getOutputPanel()).toContainText('Berlin');
await expect(n8n.ndv.getOutputPanel()).not.toContainText('Prague');
await n8n.ndv.toggleInputRunLinking();
await expect(n8n.ndv.getInputRunSelectorInput()).toHaveValue('1 of 3');
await expect(n8n.ndv.getOutputRunSelectorInput()).toHaveValue('1 of 3');
await assertInputOutputTextExists('Prague');
await assertInputOutputTextNotExists('Berlin');
await assertInputOutputTextNotExists('Kyiv');
});
test('should execute up to Node 1 when using partial execution', async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('Test_workflow_chat_partial_execution.json');
await n8n.canvas.clickZoomToFitButton();
// Check that chat modal is not initially visible
await expect(n8n.canvas.getManualChatModal().locator('main')).toBeHidden();
// Open Node 1 and execute it
await n8n.canvas.openNode('Node 1');
await n8n.ndv.execute();
// Chat modal should now be visible
await expect(n8n.canvas.getManualChatModal().locator('main')).toBeVisible();
// Send first message
await n8n.canvas.logsPanel.sendManualChatMessage('Test');
await expect(n8n.canvas.getManualChatLatestBotMessage()).toContainText('this_my_field_1');
// Refresh session
await n8n.page.getByTestId('refresh-session-button').click();
await expect(n8n.canvas.getManualChatMessages()).not.toBeAttached();
// Send another message
await n8n.canvas.logsPanel.sendManualChatMessage('Another test');
await expect(n8n.canvas.getManualChatLatestBotMessage()).toContainText('this_my_field_3');
await expect(n8n.canvas.getManualChatLatestBotMessage()).toContainText('this_my_field_4');
});
});
});

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,68 @@
{
"nodes": [
{
"parameters": {
"options": {}
},
"id": "535fd3dd-e78f-4ffa-a085-79723fc81b38",
"name": "When chat message received",
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"typeVersion": 1.1,
"position": [320, -380],
"webhookId": "4fb58136-3481-494a-a30f-d9e064dac186"
},
{
"parameters": {
"mode": "raw",
"jsonOutput": "{\n \"this_my_field_1\": \"value\",\n \"this_my_field_2\": 1\n}\n",
"options": {}
},
"id": "78201ec2-6def-40b7-85e5-97b580d7f642",
"name": "Node 1",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [580, -380]
},
{
"parameters": {
"mode": "raw",
"jsonOutput": "{\n \"this_my_field_3\": \"value\",\n \"this_my_field_4\": 1\n}\n",
"options": {}
},
"id": "1cfca06d-3ec3-427f-89f7-1ef321e025ff",
"name": "Node 2",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [780, -380]
}
],
"connections": {
"When chat message received": {
"main": [
[
{
"node": "Node 1",
"type": "main",
"index": 0
}
]
]
},
"Node 1": {
"main": [
[
{
"node": "Node 2",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "178ef8a5109fc76c716d40bcadb720c455319f7b7a3fd5a39e4f336a091f524a"
}
}