athena-browser-mcp 2.0.4 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -5
- package/dist/src/browser/page-network-tracker.d.ts +88 -0
- package/dist/src/browser/page-network-tracker.d.ts.map +1 -0
- package/dist/src/browser/page-network-tracker.js +271 -0
- package/dist/src/browser/page-network-tracker.js.map +1 -0
- package/dist/src/browser/page-stabilization.d.ts +35 -0
- package/dist/src/browser/page-stabilization.d.ts.map +1 -0
- package/dist/src/browser/page-stabilization.js +42 -0
- package/dist/src/browser/page-stabilization.js.map +1 -0
- package/dist/src/browser/session-manager.d.ts +4 -0
- package/dist/src/browser/session-manager.d.ts.map +1 -1
- package/dist/src/browser/session-manager.js +34 -0
- package/dist/src/browser/session-manager.js.map +1 -1
- package/dist/src/observation/eid-linker.d.ts +84 -0
- package/dist/src/observation/eid-linker.d.ts.map +1 -0
- package/dist/src/observation/eid-linker.js +268 -0
- package/dist/src/observation/eid-linker.js.map +1 -0
- package/dist/src/observation/index.d.ts +12 -0
- package/dist/src/observation/index.d.ts.map +1 -0
- package/dist/src/observation/index.js +15 -0
- package/dist/src/observation/index.js.map +1 -0
- package/dist/src/observation/observation-accumulator.d.ts +58 -0
- package/dist/src/observation/observation-accumulator.d.ts.map +1 -0
- package/dist/src/observation/observation-accumulator.js +213 -0
- package/dist/src/observation/observation-accumulator.js.map +1 -0
- package/dist/src/observation/observation.types.d.ts +108 -0
- package/dist/src/observation/observation.types.d.ts.map +1 -0
- package/dist/src/observation/observation.types.js +44 -0
- package/dist/src/observation/observation.types.js.map +1 -0
- package/dist/src/observation/observer-script.d.ts +19 -0
- package/dist/src/observation/observer-script.d.ts.map +1 -0
- package/dist/src/observation/observer-script.js +519 -0
- package/dist/src/observation/observer-script.js.map +1 -0
- package/dist/src/snapshot/snapshot.types.d.ts +6 -0
- package/dist/src/snapshot/snapshot.types.d.ts.map +1 -1
- package/dist/src/snapshot/snapshot.types.js.map +1 -1
- package/dist/src/state/diff-engine.d.ts.map +1 -1
- package/dist/src/state/diff-engine.js +129 -1
- package/dist/src/state/diff-engine.js.map +1 -1
- package/dist/src/state/state-manager.d.ts.map +1 -1
- package/dist/src/state/state-manager.js +9 -0
- package/dist/src/state/state-manager.js.map +1 -1
- package/dist/src/state/state-renderer.d.ts +13 -0
- package/dist/src/state/state-renderer.d.ts.map +1 -1
- package/dist/src/state/state-renderer.js +172 -2
- package/dist/src/state/state-renderer.js.map +1 -1
- package/dist/src/state/types.d.ts +37 -0
- package/dist/src/state/types.d.ts.map +1 -1
- package/dist/src/tools/browser-tools.d.ts.map +1 -1
- package/dist/src/tools/browser-tools.js +15 -1
- package/dist/src/tools/browser-tools.js.map +1 -1
- package/dist/src/tools/execute-action.d.ts +22 -6
- package/dist/src/tools/execute-action.d.ts.map +1 -1
- package/dist/src/tools/execute-action.js +80 -21
- package/dist/src/tools/execute-action.js.map +1 -1
- package/dist/src/tools/tool-schemas.d.ts +68 -68
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,12 +6,42 @@
|
|
|
6
6
|
|
|
7
7
|
MCP server for AI browser automation - 18 tools with semantic element targeting.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Why Athena?
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
LLM agents face two hard constraints: limited context windows and expensive tokens. Yet browser automation requires understanding complex, ever-changing page state. Traditional tools dump raw accessibility trees or screenshots, wasting precious context and creating needle-in-haystack problems where agents struggle to locate relevant elements.
|
|
12
|
+
|
|
13
|
+
Athena solves this with **semantic page snapshots** - compact, structured representations designed for LLM consumption:
|
|
14
|
+
|
|
15
|
+
- **Token-efficient** - Hierarchical layers and regions eliminate noise, fitting more page understanding into less context
|
|
16
|
+
- **High recall** - Structured XML with semantic element IDs lets agents find elements without scanning entire DOM trees
|
|
17
|
+
- **Intuitive querying** - `find_elements` with semantic filters (kind, label, region) so agents ask for what they need
|
|
18
|
+
- **Stable references** - Semantic `eid`s survive DOM mutations, eliminating stale element errors
|
|
19
|
+
|
|
20
|
+
The result: fewer tokens, faster task completion, and higher-quality outputs with fewer errors.
|
|
21
|
+
|
|
22
|
+
## Benchmark
|
|
23
|
+
|
|
24
|
+
Comparison between Athena Browser MCP and Playwright MCP on real-world browser automation tasks. Tests run in Claude Code with Claude Opus 4.5.
|
|
25
|
+
|
|
26
|
+
| # | Task | Agent | Result | Tokens Used | Time Taken |
|
|
27
|
+
| --- | ------------------------------------------------------------------------------------- | -------------- | ---------- | ----------- | ---------- |
|
|
28
|
+
| 1 | Login → Create wishlist "Summer Escapes" → Add beach property (Airbnb) | **Athena** | ✅ Success | 92,870 | 2m 08s |
|
|
29
|
+
| | | **Playwright** | ✅ Success | 137,063 | 5m 23s |
|
|
30
|
+
| 2 | Bangkok Experiences → Food tour → Extract itinerary & pricing (Airbnb) | **Athena** | ✅ Success | 87,194 | 3m 27s |
|
|
31
|
+
| | | **Playwright** | ✅ Success | 94,942 | 3m 38s |
|
|
32
|
+
| 3 | Miami → Beachfront stays under $300 → Top 3 names + prices (Airbnb) | **Athena** | ✅ Success | 124,597 | 5m 38s |
|
|
33
|
+
| | | **Playwright** | ✅ Success | 122,077 | 4m 51s |
|
|
34
|
+
| 4 | Paris → "Play" section → Top 5 titles + descriptions (Airbnb) | **Athena** | ❌ Failed | 146,575 | 4m 15s |
|
|
35
|
+
| | | **Playwright** | ❌ Failed | 189,495 | 7m 37s |
|
|
36
|
+
| 5 | Navigate Apple → find iPhone → configure iPhone 17 → add 256GB Black → confirm in bag | **Athena** | ✅ Success | 65,629 | 3m 30s |
|
|
37
|
+
| | | **Playwright** | ✅ Success | 102,754 | 6m 59s |
|
|
38
|
+
|
|
39
|
+
**Total Results:**
|
|
40
|
+
|
|
41
|
+
- **Tokens**: Athena used **125,341 fewer tokens** (~19.4% more efficient)
|
|
42
|
+
- **Time**: Athena completed tasks **9m 30s faster** (~33.4% faster)
|
|
43
|
+
|
|
44
|
+
_Benchmark on a larger dataset coming soon._
|
|
15
45
|
|
|
16
46
|
## Architecture
|
|
17
47
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Network Tracker
|
|
3
|
+
*
|
|
4
|
+
* Tracks in-flight network requests for a page and provides a reliable
|
|
5
|
+
* "network quiet" wait mechanism. Unlike Playwright's waitForLoadState('networkidle'),
|
|
6
|
+
* this tracks requests triggered after page load (e.g., by user actions).
|
|
7
|
+
*
|
|
8
|
+
* Uses a generation counter to safely handle navigation - late events from
|
|
9
|
+
* previous documents are ignored.
|
|
10
|
+
*/
|
|
11
|
+
import type { Page } from 'playwright';
|
|
12
|
+
/**
|
|
13
|
+
* Tracks network requests for a single page.
|
|
14
|
+
*
|
|
15
|
+
* Attach to a page via `attach()`, then use `waitForQuiet()` to wait for
|
|
16
|
+
* network activity to settle. Call `markNavigation()` when navigating to
|
|
17
|
+
* safely reset state without race conditions.
|
|
18
|
+
*/
|
|
19
|
+
export declare class PageNetworkTracker {
|
|
20
|
+
private page;
|
|
21
|
+
private inflightCount;
|
|
22
|
+
private generation;
|
|
23
|
+
private currentGeneration;
|
|
24
|
+
private quietTimer;
|
|
25
|
+
private quietWindowMs;
|
|
26
|
+
private quietResolvers;
|
|
27
|
+
private onRequest;
|
|
28
|
+
private onRequestFinished;
|
|
29
|
+
private onRequestFailed;
|
|
30
|
+
/**
|
|
31
|
+
* Attach network event listeners to a page.
|
|
32
|
+
*
|
|
33
|
+
* Must be called before `waitForQuiet()` can be used.
|
|
34
|
+
* Safe to call multiple times - will detach previous listeners first.
|
|
35
|
+
*/
|
|
36
|
+
attach(page: Page): void;
|
|
37
|
+
/**
|
|
38
|
+
* Detach all event listeners and cleanup timers.
|
|
39
|
+
*
|
|
40
|
+
* Call this when the page is closed or no longer needs tracking.
|
|
41
|
+
*/
|
|
42
|
+
detach(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Mark that a navigation occurred.
|
|
45
|
+
*
|
|
46
|
+
* This safely resets state by bumping the generation counter, so any
|
|
47
|
+
* late events from the previous document are ignored. Use this instead
|
|
48
|
+
* of directly resetting state to avoid race conditions.
|
|
49
|
+
*/
|
|
50
|
+
markNavigation(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Wait for network to become quiet (no inflight requests for quietWindowMs).
|
|
53
|
+
*
|
|
54
|
+
* @param timeoutMs - Maximum time to wait before returning false
|
|
55
|
+
* @param quietWindowMs - Time with 0 inflight requests to consider "idle"
|
|
56
|
+
* @returns true if network became quiet, false if timed out (never throws)
|
|
57
|
+
*/
|
|
58
|
+
waitForQuiet(timeoutMs: number, quietWindowMs?: number): Promise<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* Get current inflight request count (for debugging/testing).
|
|
61
|
+
*/
|
|
62
|
+
getInflightCount(): number;
|
|
63
|
+
/**
|
|
64
|
+
* Check if tracker is attached to a page.
|
|
65
|
+
*/
|
|
66
|
+
isAttached(): boolean;
|
|
67
|
+
private cancelQuietTimer;
|
|
68
|
+
private checkQuiet;
|
|
69
|
+
private startQuietTimer;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get or create a network tracker for a page.
|
|
73
|
+
*
|
|
74
|
+
* Note: This does NOT automatically attach the tracker.
|
|
75
|
+
* Call `tracker.attach(page)` after getting the tracker.
|
|
76
|
+
*/
|
|
77
|
+
export declare function getOrCreateTracker(page: Page): PageNetworkTracker;
|
|
78
|
+
/**
|
|
79
|
+
* Remove and detach the tracker for a page.
|
|
80
|
+
*
|
|
81
|
+
* Call this when a page is closed to ensure proper cleanup.
|
|
82
|
+
*/
|
|
83
|
+
export declare function removeTracker(page: Page): void;
|
|
84
|
+
/**
|
|
85
|
+
* Check if a page has a tracker attached.
|
|
86
|
+
*/
|
|
87
|
+
export declare function hasTracker(page: Page): boolean;
|
|
88
|
+
//# sourceMappingURL=page-network-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-network-tracker.d.ts","sourceRoot":"","sources":["../../../src/browser/page-network-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAW,MAAM,YAAY,CAAC;AAKhD;;;;;;GAMG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,iBAAiB,CAAK;IAG9B,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,aAAa,CAAmC;IACxD,OAAO,CAAC,cAAc,CAAyE;IAG/F,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,iBAAiB,CAAyC;IAClE,OAAO,CAAC,eAAe,CAAyC;IAEhE;;;;;OAKG;IACH,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IA6CxB;;;;OAIG;IACH,MAAM,IAAI,IAAI;IA6Bd;;;;;;OAMG;IACH,cAAc,IAAI,IAAI;IAkDtB;;;;;;OAMG;IACG,YAAY,CAChB,SAAS,EAAE,MAAM,EACjB,aAAa,GAAE,MAAgC,GAC9C,OAAO,CAAC,OAAO,CAAC;IAuBnB;;OAEG;IACH,gBAAgB,IAAI,MAAM;IAI1B;;OAEG;IACH,UAAU,IAAI,OAAO;IAMrB,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,eAAe;CAexB;AAYD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,kBAAkB,CAOjE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAM9C;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAE9C"}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Network Tracker
|
|
3
|
+
*
|
|
4
|
+
* Tracks in-flight network requests for a page and provides a reliable
|
|
5
|
+
* "network quiet" wait mechanism. Unlike Playwright's waitForLoadState('networkidle'),
|
|
6
|
+
* this tracks requests triggered after page load (e.g., by user actions).
|
|
7
|
+
*
|
|
8
|
+
* Uses a generation counter to safely handle navigation - late events from
|
|
9
|
+
* previous documents are ignored.
|
|
10
|
+
*/
|
|
11
|
+
/** Default quiet window - time with 0 inflight requests to consider "idle" */
|
|
12
|
+
const DEFAULT_QUIET_WINDOW_MS = 500;
|
|
13
|
+
/**
|
|
14
|
+
* Tracks network requests for a single page.
|
|
15
|
+
*
|
|
16
|
+
* Attach to a page via `attach()`, then use `waitForQuiet()` to wait for
|
|
17
|
+
* network activity to settle. Call `markNavigation()` when navigating to
|
|
18
|
+
* safely reset state without race conditions.
|
|
19
|
+
*/
|
|
20
|
+
export class PageNetworkTracker {
|
|
21
|
+
page = null;
|
|
22
|
+
inflightCount = 0;
|
|
23
|
+
generation = 0;
|
|
24
|
+
currentGeneration = 0;
|
|
25
|
+
// Quiet window state
|
|
26
|
+
quietTimer = null;
|
|
27
|
+
quietWindowMs = DEFAULT_QUIET_WINDOW_MS;
|
|
28
|
+
quietResolvers = [];
|
|
29
|
+
// Event handlers (stored for cleanup via page.off())
|
|
30
|
+
onRequest = null;
|
|
31
|
+
onRequestFinished = null;
|
|
32
|
+
onRequestFailed = null;
|
|
33
|
+
/**
|
|
34
|
+
* Attach network event listeners to a page.
|
|
35
|
+
*
|
|
36
|
+
* Must be called before `waitForQuiet()` can be used.
|
|
37
|
+
* Safe to call multiple times - will detach previous listeners first.
|
|
38
|
+
*/
|
|
39
|
+
attach(page) {
|
|
40
|
+
// Detach from previous page if any
|
|
41
|
+
if (this.page) {
|
|
42
|
+
this.detach();
|
|
43
|
+
}
|
|
44
|
+
this.page = page;
|
|
45
|
+
this.generation++;
|
|
46
|
+
this.currentGeneration = this.generation;
|
|
47
|
+
this.inflightCount = 0;
|
|
48
|
+
const gen = this.currentGeneration;
|
|
49
|
+
this.onRequest = (req) => {
|
|
50
|
+
// Ignore stale events from previous document
|
|
51
|
+
if (this.currentGeneration !== gen)
|
|
52
|
+
return;
|
|
53
|
+
// Ignore websockets (persistent connections)
|
|
54
|
+
if (req.resourceType() === 'websocket')
|
|
55
|
+
return;
|
|
56
|
+
this.inflightCount++;
|
|
57
|
+
this.cancelQuietTimer();
|
|
58
|
+
};
|
|
59
|
+
this.onRequestFinished = (req) => {
|
|
60
|
+
if (this.currentGeneration !== gen)
|
|
61
|
+
return;
|
|
62
|
+
if (req.resourceType() === 'websocket')
|
|
63
|
+
return;
|
|
64
|
+
// Never go below 0 (handles edge cases)
|
|
65
|
+
this.inflightCount = Math.max(0, this.inflightCount - 1);
|
|
66
|
+
this.checkQuiet();
|
|
67
|
+
};
|
|
68
|
+
this.onRequestFailed = (req) => {
|
|
69
|
+
if (this.currentGeneration !== gen)
|
|
70
|
+
return;
|
|
71
|
+
if (req.resourceType() === 'websocket')
|
|
72
|
+
return;
|
|
73
|
+
this.inflightCount = Math.max(0, this.inflightCount - 1);
|
|
74
|
+
this.checkQuiet();
|
|
75
|
+
};
|
|
76
|
+
page.on('request', this.onRequest);
|
|
77
|
+
page.on('requestfinished', this.onRequestFinished);
|
|
78
|
+
page.on('requestfailed', this.onRequestFailed);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Detach all event listeners and cleanup timers.
|
|
82
|
+
*
|
|
83
|
+
* Call this when the page is closed or no longer needs tracking.
|
|
84
|
+
*/
|
|
85
|
+
detach() {
|
|
86
|
+
if (this.page) {
|
|
87
|
+
if (this.onRequest) {
|
|
88
|
+
this.page.off('request', this.onRequest);
|
|
89
|
+
}
|
|
90
|
+
if (this.onRequestFinished) {
|
|
91
|
+
this.page.off('requestfinished', this.onRequestFinished);
|
|
92
|
+
}
|
|
93
|
+
if (this.onRequestFailed) {
|
|
94
|
+
this.page.off('requestfailed', this.onRequestFailed);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
this.onRequest = null;
|
|
98
|
+
this.onRequestFinished = null;
|
|
99
|
+
this.onRequestFailed = null;
|
|
100
|
+
this.page = null;
|
|
101
|
+
// Cancel any pending quiet timers
|
|
102
|
+
this.cancelQuietTimer();
|
|
103
|
+
// Reject all pending waiters with false (not idle)
|
|
104
|
+
for (const { resolve, timeoutId } of this.quietResolvers) {
|
|
105
|
+
clearTimeout(timeoutId);
|
|
106
|
+
resolve(false);
|
|
107
|
+
}
|
|
108
|
+
this.quietResolvers = [];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Mark that a navigation occurred.
|
|
112
|
+
*
|
|
113
|
+
* This safely resets state by bumping the generation counter, so any
|
|
114
|
+
* late events from the previous document are ignored. Use this instead
|
|
115
|
+
* of directly resetting state to avoid race conditions.
|
|
116
|
+
*/
|
|
117
|
+
markNavigation() {
|
|
118
|
+
this.generation++;
|
|
119
|
+
this.currentGeneration = this.generation;
|
|
120
|
+
this.inflightCount = 0;
|
|
121
|
+
this.cancelQuietTimer();
|
|
122
|
+
// Re-attach handlers with new generation if we have a page
|
|
123
|
+
if (this.page) {
|
|
124
|
+
const page = this.page;
|
|
125
|
+
// Detach old handlers
|
|
126
|
+
if (this.onRequest) {
|
|
127
|
+
page.off('request', this.onRequest);
|
|
128
|
+
}
|
|
129
|
+
if (this.onRequestFinished) {
|
|
130
|
+
page.off('requestfinished', this.onRequestFinished);
|
|
131
|
+
}
|
|
132
|
+
if (this.onRequestFailed) {
|
|
133
|
+
page.off('requestfailed', this.onRequestFailed);
|
|
134
|
+
}
|
|
135
|
+
// Create new handlers with updated generation
|
|
136
|
+
const gen = this.currentGeneration;
|
|
137
|
+
this.onRequest = (req) => {
|
|
138
|
+
if (this.currentGeneration !== gen)
|
|
139
|
+
return;
|
|
140
|
+
if (req.resourceType() === 'websocket')
|
|
141
|
+
return;
|
|
142
|
+
this.inflightCount++;
|
|
143
|
+
this.cancelQuietTimer();
|
|
144
|
+
};
|
|
145
|
+
this.onRequestFinished = (req) => {
|
|
146
|
+
if (this.currentGeneration !== gen)
|
|
147
|
+
return;
|
|
148
|
+
if (req.resourceType() === 'websocket')
|
|
149
|
+
return;
|
|
150
|
+
this.inflightCount = Math.max(0, this.inflightCount - 1);
|
|
151
|
+
this.checkQuiet();
|
|
152
|
+
};
|
|
153
|
+
this.onRequestFailed = (req) => {
|
|
154
|
+
if (this.currentGeneration !== gen)
|
|
155
|
+
return;
|
|
156
|
+
if (req.resourceType() === 'websocket')
|
|
157
|
+
return;
|
|
158
|
+
this.inflightCount = Math.max(0, this.inflightCount - 1);
|
|
159
|
+
this.checkQuiet();
|
|
160
|
+
};
|
|
161
|
+
page.on('request', this.onRequest);
|
|
162
|
+
page.on('requestfinished', this.onRequestFinished);
|
|
163
|
+
page.on('requestfailed', this.onRequestFailed);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Wait for network to become quiet (no inflight requests for quietWindowMs).
|
|
168
|
+
*
|
|
169
|
+
* @param timeoutMs - Maximum time to wait before returning false
|
|
170
|
+
* @param quietWindowMs - Time with 0 inflight requests to consider "idle"
|
|
171
|
+
* @returns true if network became quiet, false if timed out (never throws)
|
|
172
|
+
*/
|
|
173
|
+
async waitForQuiet(timeoutMs, quietWindowMs = DEFAULT_QUIET_WINDOW_MS) {
|
|
174
|
+
// Store the quiet window for checkQuiet() to use
|
|
175
|
+
this.quietWindowMs = quietWindowMs;
|
|
176
|
+
return new Promise((resolve) => {
|
|
177
|
+
const hardTimeout = setTimeout(() => {
|
|
178
|
+
// Remove this resolver from the list
|
|
179
|
+
const index = this.quietResolvers.findIndex((r) => r.timeoutId === hardTimeout);
|
|
180
|
+
if (index !== -1) {
|
|
181
|
+
this.quietResolvers.splice(index, 1);
|
|
182
|
+
}
|
|
183
|
+
resolve(false);
|
|
184
|
+
}, timeoutMs);
|
|
185
|
+
this.quietResolvers.push({ resolve, timeoutId: hardTimeout });
|
|
186
|
+
// If already idle, start quiet timer immediately
|
|
187
|
+
if (this.inflightCount === 0) {
|
|
188
|
+
this.startQuietTimer();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Get current inflight request count (for debugging/testing).
|
|
194
|
+
*/
|
|
195
|
+
getInflightCount() {
|
|
196
|
+
return this.inflightCount;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Check if tracker is attached to a page.
|
|
200
|
+
*/
|
|
201
|
+
isAttached() {
|
|
202
|
+
return this.page !== null;
|
|
203
|
+
}
|
|
204
|
+
// --- Private methods ---
|
|
205
|
+
cancelQuietTimer() {
|
|
206
|
+
if (this.quietTimer) {
|
|
207
|
+
clearTimeout(this.quietTimer);
|
|
208
|
+
this.quietTimer = null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
checkQuiet() {
|
|
212
|
+
if (this.inflightCount === 0 && this.quietResolvers.length > 0) {
|
|
213
|
+
this.startQuietTimer();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
startQuietTimer() {
|
|
217
|
+
// Cancel any existing timer first
|
|
218
|
+
this.cancelQuietTimer();
|
|
219
|
+
// Start quiet window timer
|
|
220
|
+
this.quietTimer = setTimeout(() => {
|
|
221
|
+
this.quietTimer = null;
|
|
222
|
+
// Resolve all pending waiters
|
|
223
|
+
for (const { resolve, timeoutId } of this.quietResolvers) {
|
|
224
|
+
clearTimeout(timeoutId);
|
|
225
|
+
resolve(true);
|
|
226
|
+
}
|
|
227
|
+
this.quietResolvers = [];
|
|
228
|
+
}, this.quietWindowMs);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// --- Global Registry ---
|
|
232
|
+
/**
|
|
233
|
+
* WeakMap registry for page-scoped trackers.
|
|
234
|
+
*
|
|
235
|
+
* Using WeakMap keyed by Page object provides automatic cleanup when
|
|
236
|
+
* the Page is garbage collected, avoiding memory leaks.
|
|
237
|
+
*/
|
|
238
|
+
const trackers = new WeakMap();
|
|
239
|
+
/**
|
|
240
|
+
* Get or create a network tracker for a page.
|
|
241
|
+
*
|
|
242
|
+
* Note: This does NOT automatically attach the tracker.
|
|
243
|
+
* Call `tracker.attach(page)` after getting the tracker.
|
|
244
|
+
*/
|
|
245
|
+
export function getOrCreateTracker(page) {
|
|
246
|
+
let tracker = trackers.get(page);
|
|
247
|
+
if (!tracker) {
|
|
248
|
+
tracker = new PageNetworkTracker();
|
|
249
|
+
trackers.set(page, tracker);
|
|
250
|
+
}
|
|
251
|
+
return tracker;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Remove and detach the tracker for a page.
|
|
255
|
+
*
|
|
256
|
+
* Call this when a page is closed to ensure proper cleanup.
|
|
257
|
+
*/
|
|
258
|
+
export function removeTracker(page) {
|
|
259
|
+
const tracker = trackers.get(page);
|
|
260
|
+
if (tracker) {
|
|
261
|
+
tracker.detach();
|
|
262
|
+
trackers.delete(page);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Check if a page has a tracker attached.
|
|
267
|
+
*/
|
|
268
|
+
export function hasTracker(page) {
|
|
269
|
+
return trackers.has(page);
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=page-network-tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-network-tracker.js","sourceRoot":"","sources":["../../../src/browser/page-network-tracker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Stabilization Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities for waiting on network activity to settle.
|
|
5
|
+
* Used by session-manager and execute-action.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: This module uses PageNetworkTracker for reliable network idle
|
|
8
|
+
* detection. Unlike Playwright's waitForLoadState('networkidle'), the tracker
|
|
9
|
+
* monitors actual request/response events and works for in-page actions
|
|
10
|
+
* (not just navigation load states).
|
|
11
|
+
*/
|
|
12
|
+
import type { Page } from 'playwright';
|
|
13
|
+
/** Default timeout for network idle waiting after actions (ms) */
|
|
14
|
+
export declare const ACTION_NETWORK_IDLE_TIMEOUT_MS = 3000;
|
|
15
|
+
/** Default timeout for network idle waiting after navigation (ms) */
|
|
16
|
+
export declare const NAVIGATION_NETWORK_IDLE_TIMEOUT_MS = 5000;
|
|
17
|
+
/** Default quiet window - time with 0 inflight requests to consider "idle" (ms) */
|
|
18
|
+
export declare const DEFAULT_QUIET_WINDOW_MS = 500;
|
|
19
|
+
/**
|
|
20
|
+
* Wait for network to become quiet (no pending requests for 500ms).
|
|
21
|
+
*
|
|
22
|
+
* This catches pending API calls triggered by actions. Uses PageNetworkTracker
|
|
23
|
+
* which monitors request/response events directly, providing reliable detection
|
|
24
|
+
* of network activity for both navigation and in-page actions.
|
|
25
|
+
*
|
|
26
|
+
* Unlike Playwright's waitForLoadState('networkidle'), this works correctly
|
|
27
|
+
* for fetch/XHR requests triggered by user actions after page load.
|
|
28
|
+
*
|
|
29
|
+
* @param page - Playwright Page instance
|
|
30
|
+
* @param timeoutMs - Maximum time to wait for network idle
|
|
31
|
+
* @param quietWindowMs - Time with 0 inflight requests to consider "idle" (default: 500ms)
|
|
32
|
+
* @returns Whether network became idle (false = timed out, but that's OK - never throws)
|
|
33
|
+
*/
|
|
34
|
+
export declare function waitForNetworkQuiet(page: Page, timeoutMs: number, quietWindowMs?: number): Promise<boolean>;
|
|
35
|
+
//# sourceMappingURL=page-stabilization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-stabilization.d.ts","sourceRoot":"","sources":["../../../src/browser/page-stabilization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAGvC,kEAAkE;AAClE,eAAO,MAAM,8BAA8B,OAAO,CAAC;AAEnD,qEAAqE;AACrE,eAAO,MAAM,kCAAkC,OAAO,CAAC;AAEvD,mFAAmF;AACnF,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAE3C;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,MAAM,EACjB,aAAa,GAAE,MAAgC,GAC9C,OAAO,CAAC,OAAO,CAAC,CASlB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Stabilization Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities for waiting on network activity to settle.
|
|
5
|
+
* Used by session-manager and execute-action.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: This module uses PageNetworkTracker for reliable network idle
|
|
8
|
+
* detection. Unlike Playwright's waitForLoadState('networkidle'), the tracker
|
|
9
|
+
* monitors actual request/response events and works for in-page actions
|
|
10
|
+
* (not just navigation load states).
|
|
11
|
+
*/
|
|
12
|
+
import { getOrCreateTracker } from './page-network-tracker.js';
|
|
13
|
+
/** Default timeout for network idle waiting after actions (ms) */
|
|
14
|
+
export const ACTION_NETWORK_IDLE_TIMEOUT_MS = 3000;
|
|
15
|
+
/** Default timeout for network idle waiting after navigation (ms) */
|
|
16
|
+
export const NAVIGATION_NETWORK_IDLE_TIMEOUT_MS = 5000;
|
|
17
|
+
/** Default quiet window - time with 0 inflight requests to consider "idle" (ms) */
|
|
18
|
+
export const DEFAULT_QUIET_WINDOW_MS = 500;
|
|
19
|
+
/**
|
|
20
|
+
* Wait for network to become quiet (no pending requests for 500ms).
|
|
21
|
+
*
|
|
22
|
+
* This catches pending API calls triggered by actions. Uses PageNetworkTracker
|
|
23
|
+
* which monitors request/response events directly, providing reliable detection
|
|
24
|
+
* of network activity for both navigation and in-page actions.
|
|
25
|
+
*
|
|
26
|
+
* Unlike Playwright's waitForLoadState('networkidle'), this works correctly
|
|
27
|
+
* for fetch/XHR requests triggered by user actions after page load.
|
|
28
|
+
*
|
|
29
|
+
* @param page - Playwright Page instance
|
|
30
|
+
* @param timeoutMs - Maximum time to wait for network idle
|
|
31
|
+
* @param quietWindowMs - Time with 0 inflight requests to consider "idle" (default: 500ms)
|
|
32
|
+
* @returns Whether network became idle (false = timed out, but that's OK - never throws)
|
|
33
|
+
*/
|
|
34
|
+
export async function waitForNetworkQuiet(page, timeoutMs, quietWindowMs = DEFAULT_QUIET_WINDOW_MS) {
|
|
35
|
+
const tracker = getOrCreateTracker(page);
|
|
36
|
+
// Ensure tracker is attached (may not be if page was created outside SessionManager)
|
|
37
|
+
if (!tracker.isAttached()) {
|
|
38
|
+
tracker.attach(page);
|
|
39
|
+
}
|
|
40
|
+
return tracker.waitForQuiet(timeoutMs, quietWindowMs);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=page-stabilization.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-stabilization.js","sourceRoot":"","sources":["../../../src/browser/page-stabilization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,kEAAkE;AAClE,MAAM,CAAC,MAAM,8BAA8B,GAAG,IAAI,CAAC;AAEnD,qEAAqE;AACrE,MAAM,CAAC,MAAM,kCAAkC,GAAG,IAAI,CAAC;AAEvD,mFAAmF;AACnF,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAE3C;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAU,EACV,SAAiB,EACjB,gBAAwB,uBAAuB;IAE/C,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAEzC,qFAAqF;IACrF,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;QAC1B,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC;IAED,OAAO,OAAO,CAAC,YAAY,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;AACxD,CAAC"}
|
|
@@ -194,6 +194,10 @@ export declare class SessionManager {
|
|
|
194
194
|
/**
|
|
195
195
|
* Navigate a page to a URL
|
|
196
196
|
*
|
|
197
|
+
* Waits for both DOM ready and network idle to ensure the page is fully loaded.
|
|
198
|
+
* Network idle timeout is generous (5s) but never throws - pages with persistent
|
|
199
|
+
* connections (websockets, long-polling, analytics) may never reach idle.
|
|
200
|
+
*
|
|
197
201
|
* @param page_id - The page identifier
|
|
198
202
|
* @param url - URL to navigate to
|
|
199
203
|
* @throws Error if page not found or navigation fails
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../../src/browser/session-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAA0B,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAEzE,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../../src/browser/session-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,9 @@ import { PlaywrightCdpClient } from '../cdp/playwright-cdp-client.js';
|
|
|
9
9
|
import { PageRegistry } from './page-registry.js';
|
|
10
10
|
import { getLogger } from '../shared/services/logging.service.js';
|
|
11
11
|
import { BrowserSessionError } from '../shared/errors/browser-session.error.js';
|
|
12
|
+
import { observationAccumulator } from '../observation/index.js';
|
|
13
|
+
import { waitForNetworkQuiet, NAVIGATION_NETWORK_IDLE_TIMEOUT_MS } from './page-stabilization.js';
|
|
14
|
+
import { getOrCreateTracker, removeTracker } from './page-network-tracker.js';
|
|
12
15
|
/** Default CDP port for Athena Browser */
|
|
13
16
|
const DEFAULT_CDP_PORT = 9223;
|
|
14
17
|
/** Default CDP host */
|
|
@@ -307,6 +310,13 @@ export class SessionManager {
|
|
|
307
310
|
this.registry.updateMetadata(handle.page_id, {
|
|
308
311
|
url: page.url(),
|
|
309
312
|
});
|
|
313
|
+
// Inject observation accumulator for DOM mutation tracking
|
|
314
|
+
await observationAccumulator.inject(page);
|
|
315
|
+
// Attach network tracker for this page
|
|
316
|
+
const tracker = getOrCreateTracker(page);
|
|
317
|
+
tracker.attach(page);
|
|
318
|
+
// Cleanup tracker on unexpected page close
|
|
319
|
+
page.on('close', () => removeTracker(page));
|
|
310
320
|
this.logger.debug('Adopted page', { page_id: handle.page_id, url: page.url() });
|
|
311
321
|
return handle;
|
|
312
322
|
}
|
|
@@ -336,6 +346,13 @@ export class SessionManager {
|
|
|
336
346
|
url: page.url(),
|
|
337
347
|
});
|
|
338
348
|
}
|
|
349
|
+
// Inject observation accumulator for DOM mutation tracking
|
|
350
|
+
await observationAccumulator.inject(page);
|
|
351
|
+
// Attach network tracker for this page
|
|
352
|
+
const tracker = getOrCreateTracker(page);
|
|
353
|
+
tracker.attach(page);
|
|
354
|
+
// Cleanup tracker on unexpected page close
|
|
355
|
+
page.on('close', () => removeTracker(page));
|
|
339
356
|
return handle;
|
|
340
357
|
}
|
|
341
358
|
/**
|
|
@@ -406,6 +423,8 @@ export class SessionManager {
|
|
|
406
423
|
if (!handle) {
|
|
407
424
|
return false;
|
|
408
425
|
}
|
|
426
|
+
// Cleanup network tracker before closing
|
|
427
|
+
removeTracker(handle.page);
|
|
409
428
|
try {
|
|
410
429
|
// Close CDP session first
|
|
411
430
|
await handle.cdp.close();
|
|
@@ -434,6 +453,10 @@ export class SessionManager {
|
|
|
434
453
|
/**
|
|
435
454
|
* Navigate a page to a URL
|
|
436
455
|
*
|
|
456
|
+
* Waits for both DOM ready and network idle to ensure the page is fully loaded.
|
|
457
|
+
* Network idle timeout is generous (5s) but never throws - pages with persistent
|
|
458
|
+
* connections (websockets, long-polling, analytics) may never reach idle.
|
|
459
|
+
*
|
|
437
460
|
* @param page_id - The page identifier
|
|
438
461
|
* @param url - URL to navigate to
|
|
439
462
|
* @throws Error if page not found or navigation fails
|
|
@@ -444,10 +467,21 @@ export class SessionManager {
|
|
|
444
467
|
throw new Error('Page not found');
|
|
445
468
|
}
|
|
446
469
|
try {
|
|
470
|
+
// Wait for DOM ready first (fast baseline)
|
|
447
471
|
await handle.page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
472
|
+
// Mark navigation on tracker (bumps generation to ignore stale events)
|
|
473
|
+
const tracker = getOrCreateTracker(handle.page);
|
|
474
|
+
tracker.markNavigation();
|
|
475
|
+
// Then wait for network to settle (catches API calls)
|
|
476
|
+
const networkIdle = await waitForNetworkQuiet(handle.page, NAVIGATION_NETWORK_IDLE_TIMEOUT_MS);
|
|
477
|
+
if (!networkIdle) {
|
|
478
|
+
this.logger.debug('Network did not reach idle state', { page_id, url });
|
|
479
|
+
}
|
|
448
480
|
this.registry.updateMetadata(page_id, {
|
|
449
481
|
url: handle.page.url(),
|
|
450
482
|
});
|
|
483
|
+
// Re-inject observation accumulator (new document context)
|
|
484
|
+
await observationAccumulator.inject(handle.page);
|
|
451
485
|
this.logger.debug('Navigated page', { page_id, url });
|
|
452
486
|
}
|
|
453
487
|
catch (error) {
|