Skip to content

Browser dag - #222

Closed
sroussey wants to merge 2 commits into
mainfrom
browser-dag
Closed

Browser dag#222
sroussey wants to merge 2 commits into
mainfrom
browser-dag

Conversation

@sroussey

Copy link
Copy Markdown
Collaborator

No description provided.

- Added a default transport type of "streamable-http" in the MCP server configuration schema for both browser and node clients.
- Enhanced error handling in the `createMcpClient` function to close the client on a 405 Method Not Allowed error, providing clearer guidance on using the correct transport type instead of SSE.
- Added new browser automation tasks: BrowserNavigateTask, BrowserClickTask, BrowserExtractTask, BrowserEvaluateTask, BrowserTransformTask, BrowserWaitTask, BrowserCloseTask.
- Implemented RunCleanupRegistry for managing cleanup tasks after graph execution.
- Updated TaskGraphRunner to utilize the RunCleanupRegistry for resource management.
- Enhanced documentation to include new browser automation capabilities and usage examples.

Plan: DAG-Style Playwright Browser Nodes with Shared Context
Summary
Build Node/Bun-only Playwright task nodes for DAG workflows using @workglow/tasks, with explicit context passing (context object) and runtime session indirection (session_id) so dataflow remains serializable.
Required nodes: Navigate, Extract, Click, Wait, Evaluate, Transform.
Add Close as a lifecycle node, plus automatic run-end cleanup safety.

Public API Changes
New task classes in @workglow/tasks (Node/Bun entrypoints):
BrowserNavigateTask
BrowserExtractTask
BrowserClickTask
BrowserWaitTask
BrowserEvaluateTask
BrowserTransformTask
BrowserCloseTask (lifecycle support)
New workflow helpers (namespaced to avoid collisions):
workflow.browserNavigate(...)
workflow.browserExtract(...)
workflow.browserClick(...)
workflow.browserWait(...)
workflow.browserEvaluate(...)
workflow.browserTransform(...)
workflow.browserClose(...)
Shared context contract (all browser tasks):
Input contains optional context: Record<string, unknown> and optional session_id: string
Output always returns context
Reserved metadata path: context.__browser = { session_id: string, url?: string, title?: string }
Raw Playwright handles never enter task input/output dataflow
Core runtime cleanup API in @workglow/task-graph:
Add a generic run-scoped cleanup registry token/interface in task-graph
TaskGraph runner executes registered cleanup handlers on complete, error, abort, disable
Browser tasks register session close handlers through this generic cleanup registry
Detailed Implementation
Add generic run cleanup support in task-graph:
Files: RunCleanup.ts (new), index.ts, common.ts, TaskGraphRunner.ts
Introduce RunCleanupRegistry with deduped add(key, fn) and runAll()
In TaskGraphRunner.handleStart, register a fresh cleanup registry in run-scoped ServiceRegistry
In handleComplete, handleError, handleAbort, handleDisable, call cleanup execution (idempotent)
Add browser session manager and Playwright loader in tasks package:
Files: BrowserSessionManager.ts (new), loadPlaywright.ts (new), types.ts (new)
BrowserSessionManager responsibilities:
Map session_id -> { browser, context, page }
getOrCreateSession, getSessionOrThrow, closeSession, closeAll
per-session mutex (runExclusive) to serialize operations on same session while allowing parallelism across sessions
loadPlaywright.ts uses dynamic import and throws clear config error if missing (bun add playwright / npm i playwright)
Implement browser task nodes:
Files (new):
BrowserNavigateTask.ts
BrowserExtractTask.ts
BrowserClickTask.ts
BrowserWaitTask.ts
BrowserEvaluateTask.ts
BrowserTransformTask.ts
BrowserCloseTask.ts
All browser tasks set static cacheable = false, category "Browser", and strict JSON schemas
Required behavior by task:
Navigate: create/reuse session, goto URL, update context.__browser, return navigation metadata
Click: resolve session, click selector, optional wait-for-navigation/load-state, return updated context
Wait: support one mode per run (timeout | selector | url | load_state | function), validate mode-specific fields
Extract: selector-driven extraction specs (text/html/attr/property/count/exists/list variants), return data
Evaluate: trusted JS string executed in page with { args, context }, return result
Transform: trusted JS string executed host-side with { input, context, data }, return { context, data }
Close: close one session id and remove/clear context.__browser (or only session_id field if configured)
Runtime-specific exports and registration:
Update node.ts and bun.ts to export/register browser tasks
Do not export/register browser tasks in browser.ts or common.ts
Add shared browser task export barrel for node/bun only if useful (index.ts)
Package dependency model:
Update package.json:
Add playwright as optional peer dependency
Add peerDependenciesMeta.playwright.optional = true
Keep dynamic import path to avoid forcing install for non-browser users
Documentation and examples:
Update README.md with browser section:
context/session pattern
full chain example using required six nodes
explicit browserClose usage and note about auto-cleanup
security note: Evaluate/Transform run trusted JS; use existing JavaScriptTask for safer interpreted transformations when needed
Node I/O Shape (Decision-Complete)
Common input fields for browser nodes:
context?: object default {}
session_id?: string (overrides context.__browser.session_id when provided)
timeout_ms?: number default 30000 (task-specific defaults allowed)
Common output fields:
context: object always emitted
Task-specific payload:
Navigate: url, title, status?, ok?
Extract: data
Click: clicked: true, url
Wait: waited: true
Evaluate: result
Transform: data?
Close: closed: true
Testing Plan
Unit tests for tasks (mock Playwright):
BrowserNavigateTask.test.ts
BrowserExtractTask.test.ts
BrowserClickWaitEvaluateTask.test.ts
BrowserTransformTask.test.ts
BrowserCloseTask.test.ts
DAG integration tests:
BrowserWorkflowDag.test.ts
Validate auto-connection of context across chain
Validate explicit branch behavior with manual connect() when needed
Validate per-session mutex ordering under parallel scheduler
Cleanup tests:
RunCleanupRegistry.test.ts
Verify run-end cleanup on success/failure/abort closes open browser sessions
Verify explicit close + run-end cleanup remains idempotent
Dependency/error tests:
missing Playwright dependency emits actionable TaskConfigurationError
missing session id for non-navigate browser nodes yields clear validation error
Compatibility and Rollout
Backward compatibility:
Additive API only; existing tasks/workflows unchanged
Browser tasks available only in Node/Bun package entrypoints
Performance/runtime:
Sessions reused by session_id to avoid relaunch overhead
Per-session serialization avoids racey page mutations in parallel DAG branches
Security stance:
Evaluate and Transform intentionally execute trusted JS strings
Document trust boundary and recommended safer alternative via existing JavaScriptTask
Assumptions and Defaults
Runtime scope: Node + Bun only.
Context model: serializable context + session_id; no raw Playwright objects in dataflow.
API naming: namespaced browser* helpers.
Scripting model: trusted JS strings for Evaluate/Transform; JavaScriptTask remains available for interpreted transforms.
Lifecycle: explicit close node plus automatic run-end cleanup.
Dependency model: optional Playwright peer dependency with dynamic import.
Skills: none used (request does not match available skill-creator/skill-installer workflows).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a comprehensive browser automation task suite using Playwright, along with a run-scoped cleanup registry to ensure proper resource management. The implementation includes seven new browser tasks (Navigate, Extract, Click, Wait, Evaluate, Transform, Close) with context-based session management, automatic cleanup, and mutual exclusion for same-session operations.

Changes:

  • Added browser automation tasks with Playwright integration for Node.js and Bun runtimes
  • Implemented RunCleanupRegistry for automatic resource cleanup on task graph completion, failure, abort, or disable
  • Enhanced MCP client error messages and added default transport configuration

Reviewed changes

Copilot reviewed 31 out of 32 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/util/src/mcp/McpClientUtil.node.ts Added default transport and improved 405 error handling with client cleanup
packages/util/src/mcp/McpClientUtil.browser.ts Added default transport and improved 405 error handling with client cleanup
packages/test/src/test/task/browserTestRuntime.ts Test harness for browser tasks with mock Playwright API
packages/test/src/test/task/BrowserTransformTask.test.ts Unit tests for browser transform task
packages/test/src/test/task/BrowserNavigateTask.test.ts Unit tests for browser navigate task
packages/test/src/test/task/BrowserExtractTask.test.ts Unit tests for browser extract task
packages/test/src/test/task/BrowserDependencyErrors.test.ts Tests for Playwright dependency error messages
packages/test/src/test/task/BrowserCloseTask.test.ts Unit tests for browser close task
packages/test/src/test/task/BrowserClickWaitEvaluateTask.test.ts Unit tests for click, wait, and evaluate tasks
packages/test/src/test/task-graph/RunCleanupRegistry.test.ts Tests for run-scoped cleanup registry
packages/test/src/test/task-graph/BrowserWorkflowDag.test.ts Integration tests for browser workflow chains
packages/tasks/src/types.ts Exported browser task types
packages/tasks/src/task/browser/types.ts Core types and utilities for browser task context management
packages/tasks/src/task/browser/loadPlaywright.ts Lazy Playwright loader with helpful error messages
packages/tasks/src/task/browser/index.ts Browser task exports
packages/tasks/src/task/browser/BrowserWaitTask.ts Task for waiting on timeouts, selectors, URLs, load states, or functions
packages/tasks/src/task/browser/BrowserTransformTask.ts Task for host-side JavaScript transformation of context and data
packages/tasks/src/task/browser/BrowserSessionManager.ts Session manager with mutex for exclusive access
packages/tasks/src/task/browser/BrowserNavigateTask.ts Task for navigating to URLs and creating sessions
packages/tasks/src/task/browser/BrowserExtractTask.ts Task for extracting data from pages using selectors
packages/tasks/src/task/browser/BrowserEvaluateTask.ts Task for executing JavaScript in page context
packages/tasks/src/task/browser/BrowserCloseTask.ts Task for closing browser sessions
packages/tasks/src/task/browser/BrowserClickTask.ts Task for clicking elements
packages/tasks/src/node.ts Registered browser tasks for Node.js runtime
packages/tasks/src/bun.ts Registered browser tasks for Bun runtime
packages/tasks/package.json Added optional Playwright peer dependency
packages/tasks/README.md Added comprehensive browser task documentation
packages/task-graph/src/task/index.ts Exported RunCleanup module
packages/task-graph/src/task/RunCleanup.ts Cleanup registry implementation
packages/task-graph/src/task-graph/TaskGraphRunner.ts Integrated cleanup registry into graph lifecycle
packages/task-graph/src/task-graph/TaskGraph.ts Added registry config passthrough
bun.lock Updated lockfile with Playwright dependency

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +68 to +70
if (!sessionId) {
throw new TaskConfigurationError("No browser session id found for close operation");
}

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resolveSessionId function is called with required=true but the result is then checked for null/undefined. This creates unreachable code since resolveSessionId will throw a TaskConfigurationError when required=true and no session_id is found. Either remove the null check or change required to false.

Suggested change
if (!sessionId) {
throw new TaskConfigurationError("No browser session id found for close operation");
}

Copilot uses AI. Check for mistakes.
Comment on lines +149 to +153
async waitForFunction(fn: (arg: any) => unknown, arg: any) {
state.waitEvents.push("function");
const value = fn(arg);
if (!value) {
throw new TaskConfigurationError("waitForFunction predicate returned false");

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test runtime implementation doesn't accurately simulate the real Playwright waitForFunction behavior. In real Playwright, waitForFunction polls the predicate until it returns a truthy value or times out. This implementation only runs the predicate once and throws if it's falsy, which doesn't accurately test the retry/polling behavior that the actual task will encounter.

Suggested change
async waitForFunction(fn: (arg: any) => unknown, arg: any) {
state.waitEvents.push("function");
const value = fn(arg);
if (!value) {
throw new TaskConfigurationError("waitForFunction predicate returned false");
async waitForFunction(
fn: (arg: any) => unknown | Promise<unknown>,
arg: any,
options?: { timeoutMs?: number; pollingIntervalMs?: number }
) {
state.waitEvents.push("function");
const timeoutMs = options?.timeoutMs ?? 1000;
const pollingIntervalMs = options?.pollingIntervalMs ?? 50;
const start = Date.now();
// Poll the predicate until it becomes truthy or times out, similar to Playwright's behavior.
// This allows tests to exercise retry logic instead of relying on a single evaluation.
// eslint-disable-next-line no-constant-condition
while (true) {
const value = await fn(arg);
if (value) {
return;
}
if (Date.now() - start >= timeoutMs) {
throw new TaskConfigurationError(
"waitForFunction predicate did not become truthy within the timeout"
);
}
await sleep(pollingIntervalMs);

Copilot uses AI. Check for mistakes.
@sroussey sroussey closed this Feb 25, 2026
@sroussey
sroussey deleted the browser-dag branch April 11, 2026 16:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants