E2E Test Author LangGraph
What it does
A planner turns the user story into concrete scenarios: the happy path, the edge cases, the failure states worth asserting. A coder writes Playwright tests for them. A runner executes the tests against the real page in a browser. On failure, a healer diagnoses: a drifted selector or a wrong assertion gets fixed and re-run; a genuine application bug is kept as a failing test and flagged, never papered over. The loop continues until the suite passes honestly or the budget is spent, then a reporter opens the tests as a pull request.
The cast Β· 5 agents
Write a Playwright test per scenario. Prefer role- and text-based locators over brittle CSS paths. If the healer sent fixes, apply exactly those. Save the files.
For each failure, decide the cause: a selector that drifted, an assertion that misreads intended behavior, or a genuine application bug. Return an object with a 'green' boolean (true when every remaining failure is a genuine bug or nothing fails), the selector/assertion fixes for the coder, and the genuine bugs. Never rewrite a test just to make a real bug pass.
Run each test's steps against the real page in the browser: navigate, act, and check the assertions by inspecting the page state. Report pass or fail per test with what the page actually showed on failure.
Derive the scenarios this story needs covered: the happy path, boundary inputs, and the failure states a user could hit. Keep the list short and high-value. Do not write code.
Open a pull request with the suite, the scenarios covered, and β prominently β any genuine bugs the tests exposed, each with its failing test as the reproduction.
Flow
Interface
Depends on Β· 3 MCP servers
πPlaywrightio.github.microsoft/playwright-mcpbrowserBVerified# Generated by @socketcat/compiler for target: langgraph
# blueprint: com.socketcat/e2e-test-author v1.0.0 schema: socketcat.dev/blueprint/v0
# This code is yours. Edit it freely. The socketcat_runtime helper is optional and can be vendored.
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
import socketcat_runtime as _rt
class State(TypedDict, total=False):
story: object
pr: object
scenarios: object
__loop_1: object
suite: object
runResult: object
verdict: object
def node_AgentInvoke_0(state):
return _rt.run_agent("planner", state, {"story":"story"}, "scenarios")
def node_LoopHeader_1(state):
return {}
def node_LoopTick_2(state):
return {"__loop_1": state.get("__loop_1", 0) + 1}
def node_Nop_3(state):
return {}
def node_AgentInvoke_4(state):
return _rt.run_agent("coder", state, {"verdict":"verdict","scenarios":"scenarios"}, "suite")
def node_AgentInvoke_5(state):
return _rt.run_agent("runner", state, {"suite":"suite"}, "runResult")
def node_AgentInvoke_6(state):
return _rt.run_agent("healer", state, {"runResult":"runResult","scenarios":"scenarios"}, "verdict")
def node_AgentInvoke_7(state):
return _rt.run_agent("reporter", state, {"suite":"suite","verdict":"verdict"}, "pr")
def route_LoopHeader_1(state):
count = state.get("__loop_1", 0)
return "exit" if (_rt.cond("verdict.green == true", state) or count >= 4) else "loop"
def build():
b = StateGraph(State)
b.add_node("AgentInvoke_0", node_AgentInvoke_0)
b.add_node("LoopHeader_1", node_LoopHeader_1)
b.add_node("LoopTick_2", node_LoopTick_2)
b.add_node("Nop_3", node_Nop_3)
b.add_node("AgentInvoke_4", node_AgentInvoke_4)
b.add_node("AgentInvoke_5", node_AgentInvoke_5)
b.add_node("AgentInvoke_6", node_AgentInvoke_6)
b.add_node("AgentInvoke_7", node_AgentInvoke_7)
b.add_edge(START, "AgentInvoke_0")
b.add_edge("AgentInvoke_0", "AgentInvoke_4")
b.add_conditional_edges("LoopHeader_1", route_LoopHeader_1, {"loop": "AgentInvoke_4", "exit": "Nop_3"})
b.add_edge("LoopTick_2", "LoopHeader_1")
b.add_edge("Nop_3", "AgentInvoke_7")
b.add_edge("AgentInvoke_4", "AgentInvoke_5")
b.add_edge("AgentInvoke_5", "AgentInvoke_6")
b.add_edge("AgentInvoke_6", "LoopTick_2")
b.add_edge("AgentInvoke_7", END)
return b.compile()
INPUTS = ["story"]
OUTPUTS = ["pr"]
if __name__ == "__main__":
_rt.main(build, INPUTS, OUTPUTS)
βΈblueprint.json (the portable format)
{
"id": "com.socketcat/e2e-test-author",
"flow": {
"type": "sequence",
"blocks": [
{
"in": {
"story": "story"
},
"out": "scenarios",
"use": "planner",
"type": "agent"
},
{
"max": 4,
"type": "loop",
"until": "verdict.green == true",
"blocks": [
{
"in": {
"verdict": "verdict",
"scenarios": "scenarios"
},
"out": "suite",
"use": "coder",
"type": "agent"
},
{
"in": {
"suite": "suite"
},
"out": "runResult",
"use": "runner",
"type": "agent"
},
{
"in": {
"runResult": "runResult",
"scenarios": "scenarios"
},
"out": "verdict",
"use": "healer",
"type": "agent"
}
]
},
{
"in": {
"suite": "suite",
"verdict": "verdict"
},
"out": "pr",
"use": "reporter",
"type": "agent"
}
]
},
"tags": [
"testing",
"playwright",
"self-healing",
"qa"
],
"title": "E2E Test Author",
"agents": {
"coder": {
"model": {
"hint": "coding"
},
"title": "Coder",
"tools": [
"fs.write_file"
],
"instructions": "Write a Playwright test per scenario. Prefer role- and text-based locators over brittle CSS paths. If the healer sent fixes, apply exactly those. Save the files."
},
"healer": {
"model": {
"hint": "reasoning"
},
"title": "Healer",
"output": {
"type": "object",
"required": [
"green"
],
"properties": {
"bugs": {
"type": "array"
},
"fixes": {
"type": "array"
},
"green": {
"type": "boolean"
}
}
},
"instructions": "For each failure, decide the cause: a selector that drifted, an assertion that misreads intended behavior, or a genuine application bug. Return an object with a 'green' boolean (true when every remaining failure is a genuine bug or nothing fails), the selector/assertion fixes for the coder, and the genuine bugs. Never rewrite a test just to make a real bug pass."
},
"runner": {
"model": {
"hint": "fast"
},
"title": "Runner",
"tools": [
"browser.open",
"browser.click",
"browser.snapshot"
],
"instructions": "Run each test's steps against the real page in the browser: navigate, act, and check the assertions by inspecting the page state. Report pass or fail per test with what the page actually showed on failure."
},
"planner": {
"model": {
"hint": "reasoning"
},
"title": "Planner",
"instructions": "Derive the scenarios this story needs covered: the happy path, boundary inputs, and the failure states a user could hit. Keep the list short and high-value. Do not write code."
},
"reporter": {
"model": {
"hint": "fast"
},
"title": "Reporter",
"tools": [
"code.open_pr"
],
"instructions": "Open a pull request with the suite, the scenarios covered, and β prominently β any genuine bugs the tests exposed, each with its failing test as the reproduction."
}
},
"estate": {
"suite": {
"type": "object",
"description": "The current test files."
},
"verdict": {
"type": "object",
"description": "The healer's call, including a 'green' flag and real bugs found."
},
"runResult": {
"type": "object",
"description": "The runner's pass/fail results with failure detail."
},
"scenarios": {
"type": "array",
"description": "The planner's list of scenarios to cover."
}
},
"$schema": "socketcat.dev/blueprint/v0",
"authors": [
{
"url": "https://socketcat.com",
"name": "SocketCat"
}
],
"license": "MIT",
"servers": [
{
"ref": "io.github.microsoft/playwright-mcp",
"alias": "browser"
},
{
"ref": "io.github.modelcontextprotocol/filesystem",
"alias": "fs"
},
{
"ref": "io.github.github/github-mcp",
"alias": "code"
}
],
"summary": "Plans scenarios from a user story, writes Playwright tests, runs them in a real browser, and self-heals failures until they pass for the right reason.",
"targets": [
"langgraph",
"*"
],
"version": "1.0.0",
"interface": {
"inputs": {
"story": {
"type": "string",
"description": "The user story or PR to derive tests from, plus the app URL."
}
},
"outputs": {
"pr": {
"type": "string",
"description": "The pull request with the passing suite, and any genuine bugs found."
}
}
},
"extensions": {
"dev.langgraph": {
"checkpointer": "memory"
}
},
"description": "A planner turns the user story into concrete scenarios: the happy path, the edge cases, the failure states worth asserting. A coder writes Playwright tests for them. A runner executes the tests against the real page in a browser. On failure, a healer diagnoses: a drifted selector or a wrong assertion gets fixed and re-run; a genuine application bug is kept as a failing test and flagged, never papered over. The loop continues until the suite passes honestly or the budget is spent, then a reporter opens the tests as a pull request."
}