browser-commander 0.2.0
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/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.github/workflows/release.yml +296 -0
- package/.husky/pre-commit +1 -0
- package/.jscpd.json +20 -0
- package/.prettierignore +7 -0
- package/.prettierrc +10 -0
- package/CHANGELOG.md +32 -0
- package/LICENSE +24 -0
- package/README.md +320 -0
- package/bunfig.toml +3 -0
- package/deno.json +7 -0
- package/eslint.config.js +125 -0
- package/examples/react-test-app/index.html +25 -0
- package/examples/react-test-app/package.json +19 -0
- package/examples/react-test-app/src/App.jsx +473 -0
- package/examples/react-test-app/src/main.jsx +10 -0
- package/examples/react-test-app/src/styles.css +323 -0
- package/examples/react-test-app/vite.config.js +9 -0
- package/package.json +89 -0
- package/scripts/changeset-version.mjs +38 -0
- package/scripts/create-github-release.mjs +93 -0
- package/scripts/create-manual-changeset.mjs +86 -0
- package/scripts/format-github-release.mjs +83 -0
- package/scripts/format-release-notes.mjs +216 -0
- package/scripts/instant-version-bump.mjs +121 -0
- package/scripts/merge-changesets.mjs +260 -0
- package/scripts/publish-to-npm.mjs +126 -0
- package/scripts/setup-npm.mjs +37 -0
- package/scripts/validate-changeset.mjs +262 -0
- package/scripts/version-and-commit.mjs +237 -0
- package/src/ARCHITECTURE.md +270 -0
- package/src/README.md +517 -0
- package/src/bindings.js +298 -0
- package/src/browser/launcher.js +93 -0
- package/src/browser/navigation.js +513 -0
- package/src/core/constants.js +24 -0
- package/src/core/engine-adapter.js +466 -0
- package/src/core/engine-detection.js +49 -0
- package/src/core/logger.js +21 -0
- package/src/core/navigation-manager.js +503 -0
- package/src/core/navigation-safety.js +160 -0
- package/src/core/network-tracker.js +373 -0
- package/src/core/page-session.js +299 -0
- package/src/core/page-trigger-manager.js +564 -0
- package/src/core/preferences.js +46 -0
- package/src/elements/content.js +197 -0
- package/src/elements/locators.js +243 -0
- package/src/elements/selectors.js +360 -0
- package/src/elements/visibility.js +166 -0
- package/src/exports.js +121 -0
- package/src/factory.js +192 -0
- package/src/high-level/universal-logic.js +206 -0
- package/src/index.js +17 -0
- package/src/interactions/click.js +684 -0
- package/src/interactions/fill.js +383 -0
- package/src/interactions/scroll.js +341 -0
- package/src/utilities/url.js +33 -0
- package/src/utilities/wait.js +135 -0
- package/tests/e2e/playwright.e2e.test.js +442 -0
- package/tests/e2e/puppeteer.e2e.test.js +408 -0
- package/tests/helpers/mocks.js +542 -0
- package/tests/unit/bindings.test.js +218 -0
- package/tests/unit/browser/navigation.test.js +345 -0
- package/tests/unit/core/constants.test.js +72 -0
- package/tests/unit/core/engine-adapter.test.js +170 -0
- package/tests/unit/core/engine-detection.test.js +81 -0
- package/tests/unit/core/logger.test.js +80 -0
- package/tests/unit/core/navigation-safety.test.js +202 -0
- package/tests/unit/core/network-tracker.test.js +198 -0
- package/tests/unit/core/page-trigger-manager.test.js +358 -0
- package/tests/unit/elements/content.test.js +318 -0
- package/tests/unit/elements/locators.test.js +236 -0
- package/tests/unit/elements/selectors.test.js +302 -0
- package/tests/unit/elements/visibility.test.js +234 -0
- package/tests/unit/factory.test.js +174 -0
- package/tests/unit/high-level/universal-logic.test.js +299 -0
- package/tests/unit/interactions/click.test.js +340 -0
- package/tests/unit/interactions/fill.test.js +378 -0
- package/tests/unit/interactions/scroll.test.js +330 -0
- package/tests/unit/utilities/url.test.js +63 -0
- package/tests/unit/utilities/wait.test.js +207 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Browser Commander - Architecture
|
|
2
|
+
|
|
3
|
+
A universal browser automation library that supports both Playwright and Puppeteer with a unified API.
|
|
4
|
+
|
|
5
|
+
## Core Concept
|
|
6
|
+
|
|
7
|
+
Browser Commander models browser automation as a **state machine** with page triggers that automatically start/stop based on URL conditions.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌─────────────────┐ ┌─────────────────┐
|
|
11
|
+
│ │ navigation start │ │
|
|
12
|
+
│ WORKING STATE │ ─────────────────► │ LOADING STATE │
|
|
13
|
+
│ (action runs) │ │ (wait only) │
|
|
14
|
+
│ │ ◄───────────────── │ │
|
|
15
|
+
└─────────────────┘ page ready └─────────────────┘
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Directory Structure
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
browser-commander/
|
|
22
|
+
├── index.js # Main entry, exports, makeBrowserCommander()
|
|
23
|
+
├── ARCHITECTURE.md # This file
|
|
24
|
+
├── README.md # API documentation and usage guide
|
|
25
|
+
│
|
|
26
|
+
├── core/ # Core infrastructure
|
|
27
|
+
│ ├── page-trigger-manager.js # Trigger lifecycle management
|
|
28
|
+
│ ├── navigation-manager.js # URL changes, abort signals
|
|
29
|
+
│ ├── network-tracker.js # HTTP request tracking
|
|
30
|
+
│ ├── page-session.js # Per-page context (legacy)
|
|
31
|
+
│ ├── navigation-safety.js # Handle navigation errors
|
|
32
|
+
│ ├── constants.js # CHROME_ARGS, TIMING
|
|
33
|
+
│ ├── logger.js # Logging utilities
|
|
34
|
+
│ ├── engine-detection.js # Detect Playwright/Puppeteer
|
|
35
|
+
│ └── preferences.js # Chrome preferences
|
|
36
|
+
│
|
|
37
|
+
├── browser/ # Browser lifecycle
|
|
38
|
+
│ ├── launcher.js # Browser launch (launchBrowser)
|
|
39
|
+
│ └── navigation.js # goto, waitForNavigation
|
|
40
|
+
│
|
|
41
|
+
├── elements/ # Element operations
|
|
42
|
+
│ ├── locators.js # Element location strategies
|
|
43
|
+
│ ├── selectors.js # querySelector, findByText
|
|
44
|
+
│ ├── visibility.js # isVisible, isEnabled
|
|
45
|
+
│ └── content.js # textContent, getAttribute
|
|
46
|
+
│
|
|
47
|
+
├── interactions/ # User interactions
|
|
48
|
+
│ ├── click.js # clickButton, clickElement
|
|
49
|
+
│ ├── fill.js # fillTextArea
|
|
50
|
+
│ └── scroll.js # scrollIntoView
|
|
51
|
+
│
|
|
52
|
+
├── utilities/ # Utility functions
|
|
53
|
+
│ ├── wait.js # wait(), evaluate()
|
|
54
|
+
│ └── url.js # getUrl
|
|
55
|
+
│
|
|
56
|
+
└── high-level/ # High-level automation patterns
|
|
57
|
+
└── universal-logic.js # Reusable patterns
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Component Relationships
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
64
|
+
│ makeBrowserCommander() │
|
|
65
|
+
│ (index.js) │
|
|
66
|
+
│ │
|
|
67
|
+
│ Binds all modules together and provides unified API │
|
|
68
|
+
└───────────────────────────────┬─────────────────────────────────────────┘
|
|
69
|
+
│
|
|
70
|
+
┌───────────────────────┼───────────────────────┐
|
|
71
|
+
│ │ │
|
|
72
|
+
▼ ▼ ▼
|
|
73
|
+
┌───────────────┐ ┌───────────────┐ ┌───────────────────┐
|
|
74
|
+
│NetworkTracker │ │NavigationMgr │ │PageTriggerManager │
|
|
75
|
+
│ │ │ │ │ │
|
|
76
|
+
│ • Track HTTP │◄─────│ • URL changes │◄─────│ • Register │
|
|
77
|
+
│ • Wait idle │ │ • Abort sigs │ │ triggers │
|
|
78
|
+
│ • 30s timeout │ │ • Events │ │ • Start/stop │
|
|
79
|
+
│ │ │ │ │ lifecycle │
|
|
80
|
+
└───────────────┘ └───────────────┘ └───────────────────┘
|
|
81
|
+
▲ ▲ ▲
|
|
82
|
+
│ │ │
|
|
83
|
+
│ ┌────────┴────────┐ │
|
|
84
|
+
│ │ │ │
|
|
85
|
+
│ ▼ ▼ │
|
|
86
|
+
│ ┌──────────────┐ ┌──────────────┐ │
|
|
87
|
+
│ │ browser/ │ │ interactions/│ │
|
|
88
|
+
│ │ navigation.js│ │ click.js │ │
|
|
89
|
+
│ │ launcher.js │ │ fill.js │ │
|
|
90
|
+
│ └──────────────┘ └──────────────┘ │
|
|
91
|
+
│ │
|
|
92
|
+
└──────────────────────────────────────────────┘
|
|
93
|
+
All use NetworkTracker for idle detection
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Key Design Patterns
|
|
97
|
+
|
|
98
|
+
### 1. Options Object Pattern
|
|
99
|
+
|
|
100
|
+
All functions accept options as an object for maximum flexibility:
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
await commander.clickButton({
|
|
104
|
+
selector: 'button.submit',
|
|
105
|
+
scrollIntoView: true,
|
|
106
|
+
waitForNavigation: true,
|
|
107
|
+
timeout: 30000,
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 2. Page Trigger Pattern
|
|
112
|
+
|
|
113
|
+
Declarative page handlers that automatically manage lifecycle:
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
commander.pageTrigger({
|
|
117
|
+
name: 'checkout-handler',
|
|
118
|
+
condition: makeUrlCondition('*/checkout*'),
|
|
119
|
+
action: async (ctx) => {
|
|
120
|
+
// ctx.checkStopped() - check if should stop
|
|
121
|
+
// ctx.commander - wrapped commander (throws on navigation)
|
|
122
|
+
// ctx.onCleanup(fn) - register cleanup
|
|
123
|
+
await ctx.commander.fillTextArea({ selector: 'input', text: 'value' });
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 3. Safe Iteration Pattern
|
|
129
|
+
|
|
130
|
+
`ctx.forEach()` automatically checks for navigation between items:
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
await ctx.forEach(items, async (item) => {
|
|
134
|
+
await ctx.commander.click({ selector: item.selector });
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 4. Engine Abstraction
|
|
139
|
+
|
|
140
|
+
Unified API works with both Playwright and Puppeteer:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
const { browser, page } = await launchBrowser({ engine: 'playwright' });
|
|
144
|
+
// or
|
|
145
|
+
const { browser, page } = await launchBrowser({ engine: 'puppeteer' });
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## State Machine Details
|
|
149
|
+
|
|
150
|
+
### LOADING STATE
|
|
151
|
+
|
|
152
|
+
When navigation is detected:
|
|
153
|
+
|
|
154
|
+
1. Signal current action to stop (AbortController.abort())
|
|
155
|
+
2. Wait for action cleanup (max 10 seconds)
|
|
156
|
+
3. Run cleanup callbacks (ctx.onCleanup)
|
|
157
|
+
4. Wait for URL stabilization (no redirects for 1s)
|
|
158
|
+
5. Wait for network idle (30s no HTTP requests)
|
|
159
|
+
|
|
160
|
+
### WORKING STATE
|
|
161
|
+
|
|
162
|
+
When page is ready:
|
|
163
|
+
|
|
164
|
+
1. Find matching trigger (condition check)
|
|
165
|
+
2. Create action context with wrapped commander
|
|
166
|
+
3. Execute action
|
|
167
|
+
4. Handle completion or errors
|
|
168
|
+
|
|
169
|
+
## Network Idle Detection
|
|
170
|
+
|
|
171
|
+
The library waits for **30 seconds** of zero pending HTTP requests:
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
// Ensures:
|
|
175
|
+
// - All lazy-loaded content fetched
|
|
176
|
+
// - All analytics complete
|
|
177
|
+
// - All async JavaScript executed
|
|
178
|
+
// - SPAs fully hydrated
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Error Handling
|
|
182
|
+
|
|
183
|
+
### ActionStoppedError
|
|
184
|
+
|
|
185
|
+
Thrown when navigation interrupts an action:
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
try {
|
|
189
|
+
await ctx.commander.clickButton({ selector: 'button' });
|
|
190
|
+
} catch (error) {
|
|
191
|
+
if (commander.isActionStoppedError(error)) {
|
|
192
|
+
// Navigation happened - action was stopped
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Navigation Errors
|
|
200
|
+
|
|
201
|
+
Handled gracefully with automatic recovery:
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
if (isNavigationError(error)) {
|
|
205
|
+
// Page navigated during operation - normal behavior
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## URL Condition Helpers
|
|
210
|
+
|
|
211
|
+
Multiple ways to define URL conditions:
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
// Exact match
|
|
215
|
+
makeUrlCondition('https://example.com/page');
|
|
216
|
+
|
|
217
|
+
// Wildcards
|
|
218
|
+
makeUrlCondition('*checkout*');
|
|
219
|
+
|
|
220
|
+
// Route patterns
|
|
221
|
+
makeUrlCondition('/vacancy/:id');
|
|
222
|
+
|
|
223
|
+
// Regex
|
|
224
|
+
makeUrlCondition(/\/product\/\d+/);
|
|
225
|
+
|
|
226
|
+
// Custom function
|
|
227
|
+
makeUrlCondition((url, ctx) => url.includes('/admin'));
|
|
228
|
+
|
|
229
|
+
// Combine
|
|
230
|
+
allConditions(condition1, condition2);
|
|
231
|
+
anyCondition(condition1, condition2);
|
|
232
|
+
notCondition(condition);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Design Principles Applied
|
|
236
|
+
|
|
237
|
+
### Modularity
|
|
238
|
+
|
|
239
|
+
- Each module has single responsibility
|
|
240
|
+
- Clear boundaries between components
|
|
241
|
+
- Easy to test in isolation
|
|
242
|
+
|
|
243
|
+
### Abstraction
|
|
244
|
+
|
|
245
|
+
- Unified API hides engine differences
|
|
246
|
+
- High-level patterns for common tasks
|
|
247
|
+
|
|
248
|
+
### Stable Contracts
|
|
249
|
+
|
|
250
|
+
- Options object pattern prevents breaking changes
|
|
251
|
+
- Adding new options is backward compatible
|
|
252
|
+
|
|
253
|
+
### Separation of Concerns
|
|
254
|
+
|
|
255
|
+
- Navigation tracking separate from page operations
|
|
256
|
+
- Network tracking separate from element interactions
|
|
257
|
+
- Trigger management separate from action execution
|
|
258
|
+
|
|
259
|
+
### Composition Over Complexity
|
|
260
|
+
|
|
261
|
+
- Complex operations built from simple primitives
|
|
262
|
+
- `clickButton` uses `scrollIntoView`, `click`, verification
|
|
263
|
+
|
|
264
|
+
## Future Improvements
|
|
265
|
+
|
|
266
|
+
See GitHub issues:
|
|
267
|
+
|
|
268
|
+
- [#94](https://github.com/konard/hh-job-application-automation/issues/94) - Engine adapter interface
|
|
269
|
+
- [#95](https://github.com/konard/hh-job-application-automation/issues/95) - Split large functions
|
|
270
|
+
- [#96](https://github.com/konard/hh-job-application-automation/issues/96) - withNavigationSafety wrapper
|