browser-commander 0.8.1 → 0.9.1
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/CHANGELOG.md +14 -0
- package/README.md +47 -0
- package/eslint.config.js +3 -3
- package/examples/browser-commander-tests.example.js +30 -0
- package/package.json +11 -9
- package/scripts/clean-npm-config.mjs +39 -0
- package/scripts/npm-registry.mjs +31 -0
- package/scripts/publish-to-npm.mjs +12 -13
- package/scripts/run-tests.mjs +192 -0
- package/scripts/setup-npm.mjs +1 -1
- package/src/bindings.js +2 -0
- package/src/browser/navigation.js +2 -3
- package/src/core/dialog-manager.js +1 -1
- package/src/core/engine-adapter.js +46 -40
- package/src/core/navigation-manager.js +1 -1
- package/src/core/network-tracker.js +1 -2
- package/src/core/page-session.js +5 -5
- package/src/core/page-trigger-manager.js +4 -2
- package/src/elements/selectors.js +3 -3
- package/src/high-level/universal-logic.js +0 -1
- package/src/index.js +4 -1
- package/src/interactions/click.js +1 -1
- package/src/tests/index.js +709 -0
- package/tests/unit/bindings.test.js +8 -0
- package/tests/unit/factory.test.js +5 -0
- package/tests/unit/scripts/npm-release-helpers.test.js +138 -0
- package/tests/unit/tests/index.test.js +368 -0
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
|
|
14
14
|
import { TIMING } from './constants.js';
|
|
15
15
|
|
|
16
|
+
function notImplemented(methodName) {
|
|
17
|
+
return Promise.reject(
|
|
18
|
+
new Error(`${methodName}() must be implemented by subclass`)
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
/**
|
|
17
23
|
* Base class defining the engine adapter interface
|
|
18
24
|
* All engine-specific operations should be defined here
|
|
@@ -39,7 +45,7 @@ export class EngineAdapter {
|
|
|
39
45
|
* @param {string} selector - CSS selector
|
|
40
46
|
* @returns {Object} - Locator (Playwright) or ElementHandle (Puppeteer)
|
|
41
47
|
*/
|
|
42
|
-
createLocator(
|
|
48
|
+
createLocator(_selector) {
|
|
43
49
|
throw new Error('createLocator() must be implemented by subclass');
|
|
44
50
|
}
|
|
45
51
|
|
|
@@ -48,8 +54,8 @@ export class EngineAdapter {
|
|
|
48
54
|
* @param {string} selector - CSS selector
|
|
49
55
|
* @returns {Promise<Object|null>} - Locator/Element or null
|
|
50
56
|
*/
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
querySelector(_selector) {
|
|
58
|
+
return notImplemented('querySelector');
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
/**
|
|
@@ -57,8 +63,8 @@ export class EngineAdapter {
|
|
|
57
63
|
* @param {string} selector - CSS selector
|
|
58
64
|
* @returns {Promise<Array>} - Array of locators/elements
|
|
59
65
|
*/
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
querySelectorAll(_selector) {
|
|
67
|
+
return notImplemented('querySelectorAll');
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
/**
|
|
@@ -67,8 +73,8 @@ export class EngineAdapter {
|
|
|
67
73
|
* @param {Object} options - Wait options {visible, timeout}
|
|
68
74
|
* @returns {Promise<void>}
|
|
69
75
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
waitForSelector(_selector, _options = {}) {
|
|
77
|
+
return notImplemented('waitForSelector');
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
/**
|
|
@@ -77,8 +83,8 @@ export class EngineAdapter {
|
|
|
77
83
|
* @param {number} timeout - Timeout in ms
|
|
78
84
|
* @returns {Promise<Object>} - The locator/element
|
|
79
85
|
*/
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
waitForVisible(_locatorOrElement, _timeout = TIMING.DEFAULT_TIMEOUT) {
|
|
87
|
+
return notImplemented('waitForVisible');
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
/**
|
|
@@ -86,8 +92,8 @@ export class EngineAdapter {
|
|
|
86
92
|
* @param {string} selector - CSS selector
|
|
87
93
|
* @returns {Promise<number>} - Number of matching elements
|
|
88
94
|
*/
|
|
89
|
-
|
|
90
|
-
|
|
95
|
+
count(_selector) {
|
|
96
|
+
return notImplemented('count');
|
|
91
97
|
}
|
|
92
98
|
|
|
93
99
|
// ============================================================================
|
|
@@ -101,8 +107,8 @@ export class EngineAdapter {
|
|
|
101
107
|
* @param {any} args - Arguments to pass (optional)
|
|
102
108
|
* @returns {Promise<any>} - Result of evaluation
|
|
103
109
|
*/
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
evaluateOnElement(_locatorOrElement, _fn, _args) {
|
|
111
|
+
return notImplemented('evaluateOnElement');
|
|
106
112
|
}
|
|
107
113
|
|
|
108
114
|
/**
|
|
@@ -110,8 +116,8 @@ export class EngineAdapter {
|
|
|
110
116
|
* @param {Object} locatorOrElement - Locator or element
|
|
111
117
|
* @returns {Promise<string|null>} - Text content
|
|
112
118
|
*/
|
|
113
|
-
|
|
114
|
-
|
|
119
|
+
getTextContent(_locatorOrElement) {
|
|
120
|
+
return notImplemented('getTextContent');
|
|
115
121
|
}
|
|
116
122
|
|
|
117
123
|
/**
|
|
@@ -119,8 +125,8 @@ export class EngineAdapter {
|
|
|
119
125
|
* @param {Object} locatorOrElement - Locator or element
|
|
120
126
|
* @returns {Promise<string>} - Input value
|
|
121
127
|
*/
|
|
122
|
-
|
|
123
|
-
|
|
128
|
+
getInputValue(_locatorOrElement) {
|
|
129
|
+
return notImplemented('getInputValue');
|
|
124
130
|
}
|
|
125
131
|
|
|
126
132
|
/**
|
|
@@ -129,8 +135,8 @@ export class EngineAdapter {
|
|
|
129
135
|
* @param {string} attribute - Attribute name
|
|
130
136
|
* @returns {Promise<string|null>} - Attribute value
|
|
131
137
|
*/
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
getAttribute(_locatorOrElement, _attribute) {
|
|
139
|
+
return notImplemented('getAttribute');
|
|
134
140
|
}
|
|
135
141
|
|
|
136
142
|
// ============================================================================
|
|
@@ -143,8 +149,8 @@ export class EngineAdapter {
|
|
|
143
149
|
* @param {Object} options - Click options {force, etc.}
|
|
144
150
|
* @returns {Promise<void>}
|
|
145
151
|
*/
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
click(_locatorOrElement, _options = {}) {
|
|
153
|
+
return notImplemented('click');
|
|
148
154
|
}
|
|
149
155
|
|
|
150
156
|
/**
|
|
@@ -153,8 +159,8 @@ export class EngineAdapter {
|
|
|
153
159
|
* @param {string} text - Text to type
|
|
154
160
|
* @returns {Promise<void>}
|
|
155
161
|
*/
|
|
156
|
-
|
|
157
|
-
|
|
162
|
+
type(_locatorOrElement, _text) {
|
|
163
|
+
return notImplemented('type');
|
|
158
164
|
}
|
|
159
165
|
|
|
160
166
|
/**
|
|
@@ -163,8 +169,8 @@ export class EngineAdapter {
|
|
|
163
169
|
* @param {string} text - Text to fill
|
|
164
170
|
* @returns {Promise<void>}
|
|
165
171
|
*/
|
|
166
|
-
|
|
167
|
-
|
|
172
|
+
fill(_locatorOrElement, _text) {
|
|
173
|
+
return notImplemented('fill');
|
|
168
174
|
}
|
|
169
175
|
|
|
170
176
|
/**
|
|
@@ -172,8 +178,8 @@ export class EngineAdapter {
|
|
|
172
178
|
* @param {Object} locatorOrElement - Locator or element
|
|
173
179
|
* @returns {Promise<void>}
|
|
174
180
|
*/
|
|
175
|
-
|
|
176
|
-
|
|
181
|
+
focus(_locatorOrElement) {
|
|
182
|
+
return notImplemented('focus');
|
|
177
183
|
}
|
|
178
184
|
|
|
179
185
|
// ============================================================================
|
|
@@ -185,8 +191,8 @@ export class EngineAdapter {
|
|
|
185
191
|
* @param {string} key - Key name (Playwright/Puppeteer key format)
|
|
186
192
|
* @returns {Promise<void>}
|
|
187
193
|
*/
|
|
188
|
-
|
|
189
|
-
|
|
194
|
+
keyboardPress(_key) {
|
|
195
|
+
return notImplemented('keyboardPress');
|
|
190
196
|
}
|
|
191
197
|
|
|
192
198
|
/**
|
|
@@ -194,8 +200,8 @@ export class EngineAdapter {
|
|
|
194
200
|
* @param {string} text - Text to type
|
|
195
201
|
* @returns {Promise<void>}
|
|
196
202
|
*/
|
|
197
|
-
|
|
198
|
-
|
|
203
|
+
keyboardType(_text) {
|
|
204
|
+
return notImplemented('keyboardType');
|
|
199
205
|
}
|
|
200
206
|
|
|
201
207
|
/**
|
|
@@ -203,8 +209,8 @@ export class EngineAdapter {
|
|
|
203
209
|
* @param {string} key - Key name
|
|
204
210
|
* @returns {Promise<void>}
|
|
205
211
|
*/
|
|
206
|
-
|
|
207
|
-
|
|
212
|
+
keyboardDown(_key) {
|
|
213
|
+
return notImplemented('keyboardDown');
|
|
208
214
|
}
|
|
209
215
|
|
|
210
216
|
/**
|
|
@@ -212,8 +218,8 @@ export class EngineAdapter {
|
|
|
212
218
|
* @param {string} key - Key name
|
|
213
219
|
* @returns {Promise<void>}
|
|
214
220
|
*/
|
|
215
|
-
|
|
216
|
-
|
|
221
|
+
keyboardUp(_key) {
|
|
222
|
+
return notImplemented('keyboardUp');
|
|
217
223
|
}
|
|
218
224
|
|
|
219
225
|
// ============================================================================
|
|
@@ -226,8 +232,8 @@ export class EngineAdapter {
|
|
|
226
232
|
* @param {Array} args - Arguments to pass
|
|
227
233
|
* @returns {Promise<any>} - Result of evaluation
|
|
228
234
|
*/
|
|
229
|
-
|
|
230
|
-
|
|
235
|
+
evaluateOnPage(_fn, _args = []) {
|
|
236
|
+
return notImplemented('evaluateOnPage');
|
|
231
237
|
}
|
|
232
238
|
|
|
233
239
|
/**
|
|
@@ -243,8 +249,8 @@ export class EngineAdapter {
|
|
|
243
249
|
* @param {Object} options - PDF options (format, printBackground, margin, etc.)
|
|
244
250
|
* @returns {Promise<Buffer>} - PDF buffer
|
|
245
251
|
*/
|
|
246
|
-
|
|
247
|
-
|
|
252
|
+
pdf(_options = {}) {
|
|
253
|
+
return notImplemented('pdf');
|
|
248
254
|
}
|
|
249
255
|
}
|
|
250
256
|
|
|
@@ -435,7 +441,7 @@ export class PuppeteerAdapter extends EngineAdapter {
|
|
|
435
441
|
await this.page.waitForSelector(selector, { visible, timeout });
|
|
436
442
|
}
|
|
437
443
|
|
|
438
|
-
|
|
444
|
+
waitForVisible(locatorOrElement, _timeout = TIMING.DEFAULT_TIMEOUT) {
|
|
439
445
|
// For Puppeteer, locatorOrElement is already an ElementHandle
|
|
440
446
|
// We can't wait on it directly, so we just return it
|
|
441
447
|
// The caller should have already used waitForSelector
|
|
@@ -209,7 +209,6 @@ export function createNetworkTracker(options = {}) {
|
|
|
209
209
|
async function waitForNetworkIdle(opts = {}) {
|
|
210
210
|
const { timeout = 30000, idleTime = idleTimeout } = opts;
|
|
211
211
|
|
|
212
|
-
const startTime = Date.now();
|
|
213
212
|
const currentNavId = navigationId;
|
|
214
213
|
|
|
215
214
|
// If already idle, wait for idle time
|
|
@@ -317,7 +316,7 @@ export function createNetworkTracker(options = {}) {
|
|
|
317
316
|
*/
|
|
318
317
|
function getPendingUrls() {
|
|
319
318
|
const urls = [];
|
|
320
|
-
for (const
|
|
319
|
+
for (const req of pendingRequests.values()) {
|
|
321
320
|
const url = typeof req.url === 'function' ? req.url() : req.url;
|
|
322
321
|
urls.push(url);
|
|
323
322
|
}
|
package/src/core/page-session.js
CHANGED
|
@@ -65,7 +65,7 @@ export function createPageSessionFactory(options = {}) {
|
|
|
65
65
|
/**
|
|
66
66
|
* Handle navigation - deactivate session if URL no longer matches
|
|
67
67
|
*/
|
|
68
|
-
function handleUrlChange({
|
|
68
|
+
function handleUrlChange({ newUrl }) {
|
|
69
69
|
if (!isActive) {
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
@@ -115,7 +115,7 @@ export function createPageSessionFactory(options = {}) {
|
|
|
115
115
|
for (const { target, event, handler } of eventListeners) {
|
|
116
116
|
try {
|
|
117
117
|
target.off(event, handler);
|
|
118
|
-
} catch
|
|
118
|
+
} catch {
|
|
119
119
|
// Ignore removal errors
|
|
120
120
|
}
|
|
121
121
|
}
|
|
@@ -166,7 +166,7 @@ export function createPageSessionFactory(options = {}) {
|
|
|
166
166
|
log.debug(() => `⏭️ Skipping action - session "${name}" is inactive`);
|
|
167
167
|
return null;
|
|
168
168
|
}
|
|
169
|
-
return fn();
|
|
169
|
+
return await fn();
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
/**
|
|
@@ -214,7 +214,7 @@ export function createPageSessionFactory(options = {}) {
|
|
|
214
214
|
if (!networkTracker) {
|
|
215
215
|
return true;
|
|
216
216
|
}
|
|
217
|
-
return networkTracker.waitForNetworkIdle(opts);
|
|
217
|
+
return await networkTracker.waitForNetworkIdle(opts);
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
/**
|
|
@@ -224,7 +224,7 @@ export function createPageSessionFactory(options = {}) {
|
|
|
224
224
|
if (!isActive) {
|
|
225
225
|
return false;
|
|
226
226
|
}
|
|
227
|
-
return navigationManager.waitForPageReady(opts);
|
|
227
|
+
return await navigationManager.waitForPageReady(opts);
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
/**
|
|
@@ -310,7 +310,7 @@ export function createPageTriggerManager(options = {}) {
|
|
|
310
310
|
* @param {string} url - URL to start action for
|
|
311
311
|
* @param {Object} commander - BrowserCommander instance
|
|
312
312
|
*/
|
|
313
|
-
|
|
313
|
+
function startAction(url, commander) {
|
|
314
314
|
// Create context for condition checking
|
|
315
315
|
const conditionCtx = {
|
|
316
316
|
url,
|
|
@@ -321,7 +321,7 @@ export function createPageTriggerManager(options = {}) {
|
|
|
321
321
|
const matchingTrigger = findMatchingTrigger(conditionCtx);
|
|
322
322
|
if (!matchingTrigger) {
|
|
323
323
|
log.debug(() => `📋 No trigger registered for: ${url}`);
|
|
324
|
-
return;
|
|
324
|
+
return Promise.resolve();
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
log.debug(() => `🚀 Starting action "${matchingTrigger.name}" for: ${url}`);
|
|
@@ -369,6 +369,8 @@ export function createPageTriggerManager(options = {}) {
|
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
371
|
})();
|
|
372
|
+
|
|
373
|
+
return Promise.resolve();
|
|
372
374
|
}
|
|
373
375
|
|
|
374
376
|
/**
|
|
@@ -92,16 +92,16 @@ export async function findByText(options = {}) {
|
|
|
92
92
|
if (engine === 'playwright') {
|
|
93
93
|
// Playwright supports :has-text() natively
|
|
94
94
|
const textSelector = exact ? `:text-is("${text}")` : `:has-text("${text}")`;
|
|
95
|
-
return `${selector}${textSelector}
|
|
95
|
+
return await Promise.resolve(`${selector}${textSelector}`);
|
|
96
96
|
} else {
|
|
97
97
|
// For Puppeteer, we need to use XPath or evaluate
|
|
98
98
|
// Return a special selector marker that will be handled by other methods
|
|
99
|
-
return {
|
|
99
|
+
return await Promise.resolve({
|
|
100
100
|
_isPuppeteerTextSelector: true,
|
|
101
101
|
baseSelector: selector,
|
|
102
102
|
text,
|
|
103
103
|
exact,
|
|
104
|
-
};
|
|
104
|
+
});
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
package/src/index.js
CHANGED
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
// Export the factory function
|
|
14
|
-
export {
|
|
14
|
+
export {
|
|
15
|
+
makeBrowserCommander,
|
|
16
|
+
makeBrowserCommander as createCommander,
|
|
17
|
+
} from './factory.js';
|
|
15
18
|
|
|
16
19
|
// Re-export all public APIs from exports module
|
|
17
20
|
export * from './exports.js';
|
|
@@ -257,7 +257,7 @@ export async function clickElement(options = {}) {
|
|
|
257
257
|
* @param {Function} options.log - Logger instance
|
|
258
258
|
* @returns {Promise<{navigated: boolean, newUrl: string}>}
|
|
259
259
|
*/
|
|
260
|
-
|
|
260
|
+
function detectNavigation(options = {}) {
|
|
261
261
|
const { page, navigationManager, startUrl, log } = options;
|
|
262
262
|
|
|
263
263
|
const currentUrl = page.url();
|