This workflow follows the Agent → Googlesheetstrigger recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Automa\u00e7\u00e3o completa",
"nodes": [
{
"parameters": {
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_ID",
"mode": "list",
"cachedResultName": "Teste automa\u00e7\u00e3o",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "P\u00e1gina1",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=0"
},
"options": {}
},
"id": "9583c21f-afc7-4aee-8dad-708276dcc7de",
"name": "Monitor QA Spreadsheet",
"type": "n8n-nodes-base.googleSheetsTrigger",
"typeVersion": 1,
"position": [
-736,
-464
],
"credentials": {
"googleSheetsTriggerOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Pega os dados da linha que disparou o trigger\nconst row = $input.first().json;\n\n// Mapeamento exato das suas colunas\nconst idRoteiro = row['ID do roteiro'] || '';\nconst nomeCenario = row['Nome do cen\u00e1rio'] || '';\nconst urlTela = row['URL da tela'] || '';\nconst urlLogin = row['URL de Login'] || ''; // coluna nova sugerida\nconst dadosTeste = row['Dados de teste'] || '';\nconst passosManuais = row['Passos manuais'] || '';\nconst resultadoEsperado = row['Resultado esperado'] || '';\nconst observacoesQA = row['Observa\u00e7\u00f5es do QA'] || '';\n\n// Valida\u00e7\u00e3o \u2014 s\u00f3 processa se o trigger estiver TRUE\nconst pronto = row['Roteiro pronto para automa\u00e7\u00e3o'];\nif (String(pronto).toUpperCase() !== 'TRUE') {\n return [{ json: { skip: true, motivo: 'Roteiro n\u00e3o marcado como pronto' } }];\n}\n\n// Gera nome do arquivo fs-friendly a partir do ID + nome do cen\u00e1rio\nconst fileSlug = `${idRoteiro}-${nomeCenario}`\n .toLowerCase()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '') // remove acentos\n .replace(/\\s+/g, '-') // espa\u00e7os viram h\u00edfens\n .replace(/[^a-z0-9-]/g, '') // remove caracteres especiais\n .replace(/-+/g, '-'); // h\u00edfens duplos viram um s\u00f3\n\n// Monta o markdown no formato exato que o Playwright Generator espera\nconst testPlan = `# ${nomeCenario}\n\n## Contexto\n\n- **ID do Roteiro:** ${idRoteiro}\n- **URL de Login:** ${urlLogin || 'N/A'}\n- **URL da Tela:** ${urlTela}\n\n## Dados de Teste\n\n${dadosTeste}\n\n## Test Scenarios\n\n### 1. ${nomeCenario}\n\n**Seed:** \\`tests/seed.spec.ts\\`\n\n#### 1.1 ${nomeCenario}\n\n**Steps:**\n${passosManuais\n .split('\\n')\n .filter(p => p.trim() !== '')\n .map((p, i) => `${i + 1}. ${p.trim()}`)\n .join('\\n')}\n\n**Expected Results:**\n${resultadoEsperado\n .split('\\n')\n .filter(r => r.trim() !== '')\n .map(r => `- ${r.trim()}`)\n .join('\\n')}\n\n${observacoesQA ? `## Observa\u00e7\u00f5es do QA\\n${observacoesQA}` : ''}\n`;\n\nreturn [{\n json: {\n idRoteiro,\n nomeCenario,\n urlTela,\n urlLogin,\n dadosTeste,\n testPlan,\n fileName: `${fileSlug}.md`, // para salvar em specs/\n fileNameSpec: `${fileSlug}.spec.ts`, // para salvar em tests/ depois\n fileSlug\n }\n}];"
},
"id": "ccaec946-ec7e-40ea-af41-1edba4848c65",
"name": "Build Playwright Generator Prompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-320,
-464
]
},
{
"parameters": {
"promptType": "define",
"text": "=Generate a Playwright test for this plan:\n\n<test-suite>{{ $json.nomeCenario }}</test-suite>\n<test-name>{{ $json.nomeCenario }}</test-name>\n<test-file>{{ $json.fileNameSpec }}</test-file>\n<seed-file>tests/seed.spec.ts</seed-file>\n<body>\n{{ $json.testPlan }}\n</body>",
"options": {
"systemMessage": "You are a Playwright Test Generator, an expert in browser automation \nand end-to-end testing. Your specialty is creating robust, reliable \nPlaywright tests that accurately simulate user interactions and \nvalidate application behavior.\n\n## Snapshot policy\n- Call browser_snapshot ONLY when you need a selector you don't have yet.\n- Never call browser_snapshot after a successful action.\n- Never call browser_snapshot more than once per step.\n- If a selector fails twice, move on and mark the step as FAILED in \n the test comment. Do not retry more than twice.\n\n## Interaction rules\n- Avoid browser_wait_for unless the test plan explicitly requires it.\n- Prefer browser_verify_text_visible or browser_verify_element_visible \n for validations after navigation or clicks.\n- The target parameter in browser_click, browser_type, and \n browser_hover must always be a valid CSS selector or semantic \n Playwright selector \u2014 never a natural language description.\n\n## Selector priority\n1. getByRole\n2. getByLabel \n3. getByText\n4. getByPlaceholder\n5. CSS selector with data-test or data-testid attribute\n6. CSS selector (last resort)\n\n## Error policy\n- If a step fails after 2 attempts, write the test step with a \n comment // FAILED: could not resolve selector and continue.\n- Never loop indefinitely trying to recover from an error.\n- Do not call browser_snapshot to diagnose an error you already \n tried to fix once.\n\n## Test generation flow\n1. Receive the test plan.\n2. Call generator_setup_page once to set up the scenario.\n3. For each step: execute with Playwright tools, using step \n description as intent.\n4. Call generator_read_log ONCE at the end, after all steps are done.\n5. Immediately call generator_write_test with the complete test file.\n6. Do not call generator_read_log mid-execution.\n\n## Output rules\n- One test file per scenario.\n- fileName must start with tests/ and end with .spec.ts\n Example: tests/login-valido.spec.ts\n- Wrap test in a describe block matching the top-level test plan name.\n- Test title must match the scenario name.\n- Add a comment with the step text before each step execution.\n- Apply best practices from the generator log when writing the test.",
"maxIterations": 30
}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"position": [
80,
-448
],
"id": "11cc6162-6acf-458e-aec9-174c9efa44bd",
"name": "AI Agent"
},
{
"parameters": {
"model": {
"__rl": true,
"value": "claude-sonnet-4-6",
"mode": "list",
"cachedResultName": "Claude Sonnet 4.6"
},
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
"typeVersion": 1.5,
"position": [
-112,
-160
],
"id": "facc8374-f43c-4d83-9993-df3fdfc83c2a",
"name": "Anthropic Chat Model",
"credentials": {
"anthropicApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"toolDescription": "generator_setup_page",
"method": "POST",
"url": "http://host.docker.internal:3456/tool/generator_setup_page",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"plan\": {{ JSON.stringify($fromAI('plan', 'Full test plan in markdown/text format')) }}\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequestTool",
"typeVersion": 4.4,
"position": [
-240,
384
],
"id": "e3058b16-2975-4797-9f75-6fd53cc147ff",
"name": "HTTP Request"
},
{
"parameters": {
"toolDescription": "browser_navigate",
"method": "POST",
"url": "http://host.docker.internal:3456/tool/browser_navigate",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"url\": {{ JSON.stringify($fromAI('url', 'URL to navigate to')) }},\n \"intent\": {{ JSON.stringify($fromAI('intent', 'Reason for navigating to this URL')) }}\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequestTool",
"typeVersion": 4.4,
"position": [
-112,
384
],
"id": "d3118ba1-bb9a-475b-8aeb-ea15d51bf481",
"name": "HTTP Request1"
},
{
"parameters": {
"toolDescription": "browser_snapshot",
"method": "POST",
"url": "http://host.docker.internal:3456/tool/browser_snapshot",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{}",
"options": {}
},
"type": "n8n-nodes-base.httpRequestTool",
"typeVersion": 4.4,
"position": [
32,
384
],
"id": "af8a9e7f-120b-4f9e-b87c-ba54df933c47",
"name": "HTTP Request2"
},
{
"parameters": {
"toolDescription": "browser_click",
"method": "POST",
"url": "http://host.docker.internal:3456/tool/browser_click",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"target\": {{ JSON.stringify($fromAI('target', 'CSS selector for the element')) }},\n \"intent\": {{ JSON.stringify($fromAI('intent', 'Reason for clicking this element')) }}\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequestTool",
"typeVersion": 4.4,
"position": [
176,
384
],
"id": "bbc860be-d9d3-408c-82a8-2121dd584d53",
"name": "HTTP Request3"
},
{
"parameters": {
"toolDescription": "browser_type",
"method": "POST",
"url": "http://host.docker.internal:3456/tool/browser_type",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"target\": {{ JSON.stringify($fromAI('target', 'CSS selector for the input field')) }},\n \"text\": {{ JSON.stringify($fromAI('text', 'Text to type into the field')) }},\n \"intent\": {{ JSON.stringify($fromAI('intent', 'Reason for typing')) }}\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequestTool",
"typeVersion": 4.4,
"position": [
320,
384
],
"id": "c059a5c3-ebd0-4e7e-bec2-9a6495719f7c",
"name": "HTTP Request4"
},
{
"parameters": {
"toolDescription": "browser_hover",
"method": "POST",
"url": "http://host.docker.internal:3456/tool/browser_hover",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"target\": {{ JSON.stringify($fromAI('target', 'CSS selector for the element to hover')) }},\n \"intent\": {{ JSON.stringify($fromAI('intent', 'Reason for hovering this element')) }}\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequestTool",
"typeVersion": 4.4,
"position": [
464,
384
],
"id": "a9734440-892b-4fb9-8a6f-dc7c9aa38676",
"name": "HTTP Request5"
},
{
"parameters": {
"toolDescription": "browser_press_key",
"method": "POST",
"url": "http://host.docker.internal:3456/tool/browser_press_key",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"key\": {{ JSON.stringify($fromAI('key', 'Keyboard key to press')) }},\n \"intent\": {{ JSON.stringify($fromAI('intent', 'Reason for pressing this key')) }}\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequestTool",
"typeVersion": 4.4,
"position": [
608,
384
],
"id": "5bb2f856-76df-4b64-b0de-0498d0178556",
"name": "HTTP Request6"
},
{
"parameters": {
"toolDescription": "browser_wait_for",
"method": "POST",
"url": "http://host.docker.internal:3456/tool/browser_wait_for",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"text\": {{ JSON.stringify($fromAI('text', 'Text to wait for on the page')) }},\n \"intent\": {{ JSON.stringify($fromAI('intent', 'Reason for waiting')) }}\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequestTool",
"typeVersion": 4.4,
"position": [
752,
384
],
"id": "ab8e4eea-3a26-45af-8b1d-ad5453ab1dd8",
"name": "HTTP Request7"
},
{
"parameters": {
"toolDescription": "browser_verify_element_visible",
"method": "POST",
"url": "http://host.docker.internal:3456/tool/browser_verify_element_visible",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"target\": {{ JSON.stringify($fromAI('target', 'CSS selector for the element expected to be visible')) }},\n \"intent\": {{ JSON.stringify($fromAI('intent', 'Reason for verifying this element')) }}\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequestTool",
"typeVersion": 4.4,
"position": [
896,
384
],
"id": "482458e0-2bfa-4b7e-8243-a0b94b8b3280",
"name": "HTTP Request8"
},
{
"parameters": {
"toolDescription": "browser_verify_text_visible",
"method": "POST",
"url": "http://host.docker.internal:3456/tool/browser_verify_text_visible",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"text\": {{ JSON.stringify($fromAI('text', 'Text expected to be visible')) }},\n \"intent\": {{ JSON.stringify($fromAI('intent', 'Reason for verifying this text')) }}\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequestTool",
"typeVersion": 4.4,
"position": [
1040,
384
],
"id": "fae0e408-442f-4425-a226-4ef48468c273",
"name": "HTTP Request9"
},
{
"parameters": {
"toolDescription": "browser_verify_value",
"method": "POST",
"url": "http://host.docker.internal:3456/tool/browser_verify_value",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"target\": {{ JSON.stringify($fromAI('target', 'CSS selector for the field')) }},\n \"value\": {{ JSON.stringify($fromAI('value', 'Expected value')) }},\n \"intent\": {{ JSON.stringify($fromAI('intent', 'Reason for verifying this value')) }}\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequestTool",
"typeVersion": 4.4,
"position": [
1184,
384
],
"id": "97a119c1-6cee-4caa-b104-c72ec6a75493",
"name": "HTTP Request10"
},
{
"parameters": {
"toolDescription": "generator_read_log",
"method": "POST",
"url": "http://host.docker.internal:3456/tool/generator_read_log",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{}",
"options": {}
},
"type": "n8n-nodes-base.httpRequestTool",
"typeVersion": 4.4,
"position": [
1328,
384
],
"id": "3adfb477-813b-44ba-8ff5-d29ea97d605d",
"name": "HTTP Request11"
},
{
"parameters": {
"toolDescription": "generator_write_test",
"method": "POST",
"url": "http://host.docker.internal:3456/tool/generator_write_test",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"fileName\": {{ JSON.stringify($fromAI('fileName', 'Spec file path starting with tests/ and ending with .spec.ts')) }},\n \"code\": {{ JSON.stringify($fromAI('code', 'Complete Playwright test source code')) }}\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequestTool",
"typeVersion": 4.4,
"position": [
1472,
384
],
"id": "b4745eee-4c2f-4727-956d-344c9f89c2f0",
"name": "HTTP Request12"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"id": "check-pronto",
"leftValue": "={{ $json[\"Roteiro pronto para automa\u00e7\u00e3o\"] }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "3cad0885-429a-4c64-a015-57c07d58a02f",
"name": "Roteiro pronto?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
-528,
-464
]
}
],
"connections": {
"Monitor QA Spreadsheet": {
"main": [
[
{
"node": "Roteiro pronto?",
"type": "main",
"index": 0
}
]
]
},
"Build Playwright Generator Prompt": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"Anthropic Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"HTTP Request": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"HTTP Request1": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"HTTP Request2": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"HTTP Request3": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"HTTP Request4": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"HTTP Request5": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"HTTP Request6": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"HTTP Request7": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"HTTP Request8": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"HTTP Request9": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"HTTP Request10": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"HTTP Request11": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"HTTP Request12": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Roteiro pronto?": {
"main": [
[
{
"node": "Build Playwright Generator Prompt",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"versionId": "a3852e3a-404f-4da9-b7e2-5b1c5e0a6609",
"meta": {
"templateCredsSetupCompleted": true
},
"nodeGroups": [],
"id": "Bxcu6vd2fZyVmRDI",
"tags": []
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
anthropicApigoogleSheetsTriggerOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Automação completa. Uses googleSheetsTrigger, agent, lmChatAnthropic, httpRequestTool. Event-driven trigger; 18 nodes.
Source: https://github.com/P-Colaco/n8n-ai-test-pipeline/blob/main/workflow.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Personal Assistant. Uses memoryBufferWindow, agent, agentTool, httpRequestTool. Event-driven trigger; 77 nodes.
This workflow is designed for marketers, content creators, agencies, and solo founders who want to publish long‑form posts with visuals on autopilot using n8n and AI agents.
[1] Inbound Email → 6-Agent Pipeline → Sheets+Slack (FIXED). Uses agent, lmChatAnthropic, slack, executeWorkflowTrigger. Event-driven trigger; 42 nodes.
ALTUS v8 — Garage Force AI Command Center. Uses telegramTrigger, telegram, httpRequest, agent. Event-driven trigger; 42 nodes.
ALTUS v8 — Garage Force AI Command Center. Uses telegramTrigger, telegram, httpRequest, agent. Event-driven trigger; 40 nodes.