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.
Files changed (82) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +11 -0
  3. package/.github/workflows/release.yml +296 -0
  4. package/.husky/pre-commit +1 -0
  5. package/.jscpd.json +20 -0
  6. package/.prettierignore +7 -0
  7. package/.prettierrc +10 -0
  8. package/CHANGELOG.md +32 -0
  9. package/LICENSE +24 -0
  10. package/README.md +320 -0
  11. package/bunfig.toml +3 -0
  12. package/deno.json +7 -0
  13. package/eslint.config.js +125 -0
  14. package/examples/react-test-app/index.html +25 -0
  15. package/examples/react-test-app/package.json +19 -0
  16. package/examples/react-test-app/src/App.jsx +473 -0
  17. package/examples/react-test-app/src/main.jsx +10 -0
  18. package/examples/react-test-app/src/styles.css +323 -0
  19. package/examples/react-test-app/vite.config.js +9 -0
  20. package/package.json +89 -0
  21. package/scripts/changeset-version.mjs +38 -0
  22. package/scripts/create-github-release.mjs +93 -0
  23. package/scripts/create-manual-changeset.mjs +86 -0
  24. package/scripts/format-github-release.mjs +83 -0
  25. package/scripts/format-release-notes.mjs +216 -0
  26. package/scripts/instant-version-bump.mjs +121 -0
  27. package/scripts/merge-changesets.mjs +260 -0
  28. package/scripts/publish-to-npm.mjs +126 -0
  29. package/scripts/setup-npm.mjs +37 -0
  30. package/scripts/validate-changeset.mjs +262 -0
  31. package/scripts/version-and-commit.mjs +237 -0
  32. package/src/ARCHITECTURE.md +270 -0
  33. package/src/README.md +517 -0
  34. package/src/bindings.js +298 -0
  35. package/src/browser/launcher.js +93 -0
  36. package/src/browser/navigation.js +513 -0
  37. package/src/core/constants.js +24 -0
  38. package/src/core/engine-adapter.js +466 -0
  39. package/src/core/engine-detection.js +49 -0
  40. package/src/core/logger.js +21 -0
  41. package/src/core/navigation-manager.js +503 -0
  42. package/src/core/navigation-safety.js +160 -0
  43. package/src/core/network-tracker.js +373 -0
  44. package/src/core/page-session.js +299 -0
  45. package/src/core/page-trigger-manager.js +564 -0
  46. package/src/core/preferences.js +46 -0
  47. package/src/elements/content.js +197 -0
  48. package/src/elements/locators.js +243 -0
  49. package/src/elements/selectors.js +360 -0
  50. package/src/elements/visibility.js +166 -0
  51. package/src/exports.js +121 -0
  52. package/src/factory.js +192 -0
  53. package/src/high-level/universal-logic.js +206 -0
  54. package/src/index.js +17 -0
  55. package/src/interactions/click.js +684 -0
  56. package/src/interactions/fill.js +383 -0
  57. package/src/interactions/scroll.js +341 -0
  58. package/src/utilities/url.js +33 -0
  59. package/src/utilities/wait.js +135 -0
  60. package/tests/e2e/playwright.e2e.test.js +442 -0
  61. package/tests/e2e/puppeteer.e2e.test.js +408 -0
  62. package/tests/helpers/mocks.js +542 -0
  63. package/tests/unit/bindings.test.js +218 -0
  64. package/tests/unit/browser/navigation.test.js +345 -0
  65. package/tests/unit/core/constants.test.js +72 -0
  66. package/tests/unit/core/engine-adapter.test.js +170 -0
  67. package/tests/unit/core/engine-detection.test.js +81 -0
  68. package/tests/unit/core/logger.test.js +80 -0
  69. package/tests/unit/core/navigation-safety.test.js +202 -0
  70. package/tests/unit/core/network-tracker.test.js +198 -0
  71. package/tests/unit/core/page-trigger-manager.test.js +358 -0
  72. package/tests/unit/elements/content.test.js +318 -0
  73. package/tests/unit/elements/locators.test.js +236 -0
  74. package/tests/unit/elements/selectors.test.js +302 -0
  75. package/tests/unit/elements/visibility.test.js +234 -0
  76. package/tests/unit/factory.test.js +174 -0
  77. package/tests/unit/high-level/universal-logic.test.js +299 -0
  78. package/tests/unit/interactions/click.test.js +340 -0
  79. package/tests/unit/interactions/fill.test.js +378 -0
  80. package/tests/unit/interactions/scroll.test.js +330 -0
  81. package/tests/unit/utilities/url.test.js +63 -0
  82. package/tests/unit/utilities/wait.test.js +207 -0
@@ -0,0 +1,360 @@
1
+ import { isNavigationError } from '../core/navigation-safety.js';
2
+ import { createPlaywrightLocator } from './locators.js';
3
+
4
+ /**
5
+ * Query single element
6
+ * @param {Object} options - Configuration options
7
+ * @param {Object} options.page - Browser page object
8
+ * @param {string} options.engine - Engine type ('playwright' or 'puppeteer')
9
+ * @param {string} options.selector - CSS selector
10
+ * @returns {Promise<Object|null>} - Element handle or null
11
+ */
12
+ export async function querySelector(options = {}) {
13
+ const { page, engine, selector } = options;
14
+
15
+ if (!selector) {
16
+ throw new Error('selector is required in options');
17
+ }
18
+
19
+ try {
20
+ if (engine === 'playwright') {
21
+ const locator = createPlaywrightLocator({ page, selector }).first();
22
+ const count = await locator.count();
23
+ return count > 0 ? locator : null;
24
+ } else {
25
+ return await page.$(selector);
26
+ }
27
+ } catch (error) {
28
+ if (isNavigationError(error)) {
29
+ console.log(
30
+ '⚠️ Navigation detected during querySelector, returning null'
31
+ );
32
+ return null;
33
+ }
34
+ throw error;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Query all elements
40
+ * @param {Object} options - Configuration options
41
+ * @param {Object} options.page - Browser page object
42
+ * @param {string} options.engine - Engine type ('playwright' or 'puppeteer')
43
+ * @param {string} options.selector - CSS selector
44
+ * @returns {Promise<Array>} - Array of element handles
45
+ */
46
+ export async function querySelectorAll(options = {}) {
47
+ const { page, engine, selector } = options;
48
+
49
+ if (!selector) {
50
+ throw new Error('selector is required in options');
51
+ }
52
+
53
+ try {
54
+ if (engine === 'playwright') {
55
+ const locator = createPlaywrightLocator({ page, selector });
56
+ const count = await locator.count();
57
+ const elements = [];
58
+ for (let i = 0; i < count; i++) {
59
+ elements.push(locator.nth(i));
60
+ }
61
+ return elements;
62
+ } else {
63
+ return await page.$$(selector);
64
+ }
65
+ } catch (error) {
66
+ if (isNavigationError(error)) {
67
+ console.log(
68
+ '⚠️ Navigation detected during querySelectorAll, returning empty array'
69
+ );
70
+ return [];
71
+ }
72
+ throw error;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Find elements by text content (works across both engines)
78
+ * @param {Object} options - Configuration options
79
+ * @param {string} options.engine - Engine type ('playwright' or 'puppeteer')
80
+ * @param {string} options.text - Text to search for
81
+ * @param {string} options.selector - Optional base selector (e.g., 'button', 'a', 'span')
82
+ * @param {boolean} options.exact - Exact match vs contains (default: false)
83
+ * @returns {Promise<string>} - CSS selector that can be used with other commander methods
84
+ */
85
+ export async function findByText(options = {}) {
86
+ const { engine, text, selector = '*', exact = false } = options;
87
+
88
+ if (!text) {
89
+ throw new Error('text is required in options');
90
+ }
91
+
92
+ if (engine === 'playwright') {
93
+ // Playwright supports :has-text() natively
94
+ const textSelector = exact ? `:text-is("${text}")` : `:has-text("${text}")`;
95
+ return `${selector}${textSelector}`;
96
+ } else {
97
+ // For Puppeteer, we need to use XPath or evaluate
98
+ // Return a special selector marker that will be handled by other methods
99
+ return {
100
+ _isPuppeteerTextSelector: true,
101
+ baseSelector: selector,
102
+ text,
103
+ exact,
104
+ };
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Check if a selector is a Playwright-specific text selector
110
+ * @param {string} selector - The selector to check
111
+ * @returns {boolean} - True if selector contains Playwright text pseudo-selectors
112
+ */
113
+ function isPlaywrightTextSelector(selector) {
114
+ if (typeof selector !== 'string') {
115
+ return false;
116
+ }
117
+ return selector.includes(':has-text(') || selector.includes(':text-is(');
118
+ }
119
+
120
+ /**
121
+ * Parse a Playwright text selector to extract base selector and text
122
+ * @param {string} selector - Playwright text selector like 'a:has-text("text")'
123
+ * @returns {Object|null} - { baseSelector, text, exact } or null if not parseable
124
+ */
125
+ function parsePlaywrightTextSelector(selector) {
126
+ // Match patterns like 'a:has-text("text")' or 'button:text-is("exact text")'
127
+ const hasTextMatch = selector.match(/^(.+?):has-text\("(.+?)"\)$/);
128
+ if (hasTextMatch) {
129
+ return {
130
+ baseSelector: hasTextMatch[1],
131
+ text: hasTextMatch[2],
132
+ exact: false,
133
+ };
134
+ }
135
+
136
+ const textIsMatch = selector.match(/^(.+?):text-is\("(.+?)"\)$/);
137
+ if (textIsMatch) {
138
+ return {
139
+ baseSelector: textIsMatch[1],
140
+ text: textIsMatch[2],
141
+ exact: true,
142
+ };
143
+ }
144
+
145
+ return null;
146
+ }
147
+
148
+ /**
149
+ * Normalize selector to handle both Puppeteer and Playwright text selectors
150
+ * Converts engine-specific text selectors to valid CSS selectors for browser context
151
+ *
152
+ * @param {Object} options - Configuration options
153
+ * @param {Object} options.page - Browser page object
154
+ * @param {string} options.engine - Engine type ('playwright' or 'puppeteer')
155
+ * @param {string|Object} options.selector - CSS selector or text selector object
156
+ * @returns {Promise<string|null>} - Valid CSS selector or null if not found
157
+ */
158
+ export async function normalizeSelector(options = {}) {
159
+ const { page, engine, selector } = options;
160
+
161
+ if (!selector) {
162
+ throw new Error('selector is required in options');
163
+ }
164
+
165
+ // Handle Playwright text selectors (strings containing :has-text or :text-is)
166
+ // These are valid for Playwright's locator API but NOT for document.querySelectorAll
167
+ if (
168
+ typeof selector === 'string' &&
169
+ engine === 'playwright' &&
170
+ isPlaywrightTextSelector(selector)
171
+ ) {
172
+ const parsed = parsePlaywrightTextSelector(selector);
173
+ if (!parsed) {
174
+ // Could not parse, return as-is and hope for the best
175
+ return selector;
176
+ }
177
+
178
+ try {
179
+ // Use page.evaluate to find matching element and generate a valid CSS selector
180
+ const result = await page.evaluate(({ baseSelector, text, exact }) => {
181
+ const elements = Array.from(document.querySelectorAll(baseSelector));
182
+ const matchingElement = elements.find((el) => {
183
+ const elementText = el.textContent.trim();
184
+ return exact ? elementText === text : elementText.includes(text);
185
+ });
186
+
187
+ if (!matchingElement) {
188
+ return null;
189
+ }
190
+
191
+ // Generate a unique selector using data-qa or nth-of-type
192
+ const dataQa = matchingElement.getAttribute('data-qa');
193
+ if (dataQa) {
194
+ return `[data-qa="${dataQa}"]`;
195
+ }
196
+
197
+ // Use nth-of-type as fallback
198
+ const tagName = matchingElement.tagName.toLowerCase();
199
+ const siblings = Array.from(
200
+ matchingElement.parentElement.children
201
+ ).filter((el) => el.tagName.toLowerCase() === tagName);
202
+ const index = siblings.indexOf(matchingElement);
203
+ return `${tagName}:nth-of-type(${index + 1})`;
204
+ }, parsed);
205
+
206
+ return result;
207
+ } catch (error) {
208
+ if (isNavigationError(error)) {
209
+ console.log(
210
+ '⚠️ Navigation detected during normalizeSelector (Playwright), returning null'
211
+ );
212
+ return null;
213
+ }
214
+ throw error;
215
+ }
216
+ }
217
+
218
+ // Plain string selector - return as-is
219
+ if (typeof selector === 'string') {
220
+ return selector;
221
+ }
222
+
223
+ // Handle Puppeteer text selector objects
224
+ if (selector._isPuppeteerTextSelector) {
225
+ try {
226
+ // Find element by text and generate a unique selector
227
+ const result = await page.evaluate(
228
+ (baseSelector, text, exact) => {
229
+ const elements = Array.from(document.querySelectorAll(baseSelector));
230
+ const matchingElement = elements.find((el) => {
231
+ const elementText = el.textContent.trim();
232
+ return exact ? elementText === text : elementText.includes(text);
233
+ });
234
+
235
+ if (!matchingElement) {
236
+ return null;
237
+ }
238
+
239
+ // Generate a unique selector using data-qa or nth-of-type
240
+ const dataQa = matchingElement.getAttribute('data-qa');
241
+ if (dataQa) {
242
+ return `[data-qa="${dataQa}"]`;
243
+ }
244
+
245
+ // Use nth-of-type as fallback
246
+ const tagName = matchingElement.tagName.toLowerCase();
247
+ const siblings = Array.from(
248
+ matchingElement.parentElement.children
249
+ ).filter((el) => el.tagName.toLowerCase() === tagName);
250
+ const index = siblings.indexOf(matchingElement);
251
+ return `${tagName}:nth-of-type(${index + 1})`;
252
+ },
253
+ selector.baseSelector,
254
+ selector.text,
255
+ selector.exact
256
+ );
257
+
258
+ return result;
259
+ } catch (error) {
260
+ if (isNavigationError(error)) {
261
+ console.log(
262
+ '⚠️ Navigation detected during normalizeSelector (Puppeteer), returning null'
263
+ );
264
+ return null;
265
+ }
266
+ throw error;
267
+ }
268
+ }
269
+
270
+ return selector;
271
+ }
272
+
273
+ /**
274
+ * Enhanced wrapper for functions that need to handle text selectors
275
+ * @param {Function} fn - The function to wrap
276
+ * @param {string} engine - Engine type ('playwright' or 'puppeteer')
277
+ * @param {Object} page - Browser page object
278
+ * @returns {Function} - Wrapped function
279
+ */
280
+ export function withTextSelectorSupport(fn, engine, page) {
281
+ return async (options = {}) => {
282
+ let { selector } = options;
283
+
284
+ // Normalize Puppeteer text selectors (object format)
285
+ if (
286
+ engine === 'puppeteer' &&
287
+ typeof selector === 'object' &&
288
+ selector._isPuppeteerTextSelector
289
+ ) {
290
+ selector = await normalizeSelector({ page, engine, selector });
291
+ if (!selector) {
292
+ throw new Error('Element with specified text not found');
293
+ }
294
+ }
295
+
296
+ // Normalize Playwright text selectors (string format with :has-text or :text-is)
297
+ if (
298
+ engine === 'playwright' &&
299
+ typeof selector === 'string' &&
300
+ isPlaywrightTextSelector(selector)
301
+ ) {
302
+ selector = await normalizeSelector({ page, engine, selector });
303
+ if (!selector) {
304
+ throw new Error('Element with specified text not found');
305
+ }
306
+ }
307
+
308
+ return fn({ ...options, selector });
309
+ };
310
+ }
311
+
312
+ /**
313
+ * Wait for selector to appear
314
+ * @param {Object} options - Configuration options
315
+ * @param {Object} options.page - Browser page object
316
+ * @param {string} options.engine - Engine type ('playwright' or 'puppeteer')
317
+ * @param {string} options.selector - CSS selector
318
+ * @param {boolean} options.visible - Wait for visibility (default: true)
319
+ * @param {number} options.timeout - Timeout in ms (default: TIMING.DEFAULT_TIMEOUT)
320
+ * @param {boolean} options.throwOnNavigation - Throw on navigation error (default: true)
321
+ * @returns {Promise<boolean>} - True if selector found, false on navigation
322
+ */
323
+ export async function waitForSelector(options = {}) {
324
+ const {
325
+ page,
326
+ engine,
327
+ selector,
328
+ visible = true,
329
+ timeout = 5000,
330
+ throwOnNavigation = true,
331
+ } = options;
332
+
333
+ if (!selector) {
334
+ throw new Error('selector is required in options');
335
+ }
336
+
337
+ try {
338
+ if (engine === 'playwright') {
339
+ const locator = createPlaywrightLocator({ page, selector });
340
+ await locator.waitFor({
341
+ state: visible ? 'visible' : 'attached',
342
+ timeout,
343
+ });
344
+ } else {
345
+ await page.waitForSelector(selector, { visible, timeout });
346
+ }
347
+ return true;
348
+ } catch (error) {
349
+ if (isNavigationError(error)) {
350
+ console.log(
351
+ '⚠️ Navigation detected during waitForSelector, recovering gracefully'
352
+ );
353
+ if (throwOnNavigation) {
354
+ throw error;
355
+ }
356
+ return false;
357
+ }
358
+ throw error;
359
+ }
360
+ }
@@ -0,0 +1,166 @@
1
+ import { TIMING } from '../core/constants.js';
2
+ import { isNavigationError } from '../core/navigation-safety.js';
3
+ import { getLocatorOrElement } from './locators.js';
4
+
5
+ /**
6
+ * Check if element is visible
7
+ * @param {Object} options - Configuration options
8
+ * @param {Object} options.page - Browser page object
9
+ * @param {string} options.engine - Engine type ('playwright' or 'puppeteer')
10
+ * @param {string|Object} options.selector - CSS selector or element
11
+ * @returns {Promise<boolean>} - True if visible
12
+ */
13
+ export async function isVisible(options = {}) {
14
+ const { page, engine, selector } = options;
15
+
16
+ if (!selector) {
17
+ throw new Error('selector is required in options');
18
+ }
19
+
20
+ try {
21
+ if (engine === 'playwright') {
22
+ const locator = await getLocatorOrElement({ page, engine, selector });
23
+ try {
24
+ await locator.waitFor({
25
+ state: 'visible',
26
+ timeout: TIMING.VISIBILITY_CHECK_TIMEOUT,
27
+ });
28
+ return true;
29
+ } catch {
30
+ return false;
31
+ }
32
+ } else {
33
+ const element = await getLocatorOrElement({ page, engine, selector });
34
+ if (!element) {
35
+ return false;
36
+ }
37
+ return await page.evaluate(
38
+ (el) => el.offsetWidth > 0 && el.offsetHeight > 0,
39
+ element
40
+ );
41
+ }
42
+ } catch (error) {
43
+ if (isNavigationError(error)) {
44
+ console.log(
45
+ '⚠️ Navigation detected during visibility check, returning false'
46
+ );
47
+ return false;
48
+ }
49
+ throw error;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Check if element is enabled (not disabled, not loading)
55
+ * @param {Object} options - Configuration options
56
+ * @param {Object} options.page - Browser page object
57
+ * @param {string} options.engine - Engine type ('playwright' or 'puppeteer')
58
+ * @param {string|Object} options.selector - CSS selector or locator
59
+ * @param {Array<string>} options.disabledClasses - Additional CSS classes that indicate disabled state (default: ['magritte-button_loading'])
60
+ * @returns {Promise<boolean>} - True if enabled
61
+ */
62
+ export async function isEnabled(options = {}) {
63
+ const {
64
+ page,
65
+ engine,
66
+ selector,
67
+ disabledClasses = ['magritte-button_loading'],
68
+ } = options;
69
+
70
+ if (!selector) {
71
+ throw new Error('selector is required in options');
72
+ }
73
+
74
+ try {
75
+ if (engine === 'playwright') {
76
+ // For Playwright, use locator API
77
+ const locator =
78
+ typeof selector === 'string'
79
+ ? page.locator(selector).first()
80
+ : selector;
81
+ return await locator.evaluate((el, classes) => {
82
+ const isDisabled =
83
+ el.hasAttribute('disabled') ||
84
+ el.getAttribute('aria-disabled') === 'true' ||
85
+ classes.some((cls) => el.classList.contains(cls));
86
+ return !isDisabled;
87
+ }, disabledClasses);
88
+ } else {
89
+ // For Puppeteer (selector should already be normalized by withTextSelectorSupport wrapper)
90
+ const element = await getLocatorOrElement({ page, engine, selector });
91
+ if (!element) {
92
+ return false;
93
+ }
94
+ return await page.evaluate(
95
+ (el, classes) => {
96
+ const isDisabled =
97
+ el.hasAttribute('disabled') ||
98
+ el.getAttribute('aria-disabled') === 'true' ||
99
+ classes.some((cls) => el.classList.contains(cls));
100
+ return !isDisabled;
101
+ },
102
+ element,
103
+ disabledClasses
104
+ );
105
+ }
106
+ } catch (error) {
107
+ if (isNavigationError(error)) {
108
+ console.log(
109
+ '⚠️ Navigation detected during enabled check, returning false'
110
+ );
111
+ }
112
+ return false;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Get element count
118
+ * @param {Object} options - Configuration options
119
+ * @param {Object} options.page - Browser page object
120
+ * @param {string} options.engine - Engine type ('playwright' or 'puppeteer')
121
+ * @param {string|Object} options.selector - CSS selector or special text selector
122
+ * @returns {Promise<number>} - Number of matching elements
123
+ */
124
+ export async function count(options = {}) {
125
+ const { page, engine, selector } = options;
126
+
127
+ if (!selector) {
128
+ throw new Error('selector is required in options');
129
+ }
130
+
131
+ try {
132
+ // Handle Puppeteer text selectors
133
+ if (
134
+ engine === 'puppeteer' &&
135
+ typeof selector === 'object' &&
136
+ selector._isPuppeteerTextSelector
137
+ ) {
138
+ const result = await page.evaluate(
139
+ (baseSelector, text, exact) => {
140
+ const elements = Array.from(document.querySelectorAll(baseSelector));
141
+ return elements.filter((el) => {
142
+ const elementText = el.textContent.trim();
143
+ return exact ? elementText === text : elementText.includes(text);
144
+ }).length;
145
+ },
146
+ selector.baseSelector,
147
+ selector.text,
148
+ selector.exact
149
+ );
150
+ return result;
151
+ }
152
+
153
+ if (engine === 'playwright') {
154
+ return await page.locator(selector).count();
155
+ } else {
156
+ const elements = await page.$$(selector);
157
+ return elements.length;
158
+ }
159
+ } catch (error) {
160
+ if (isNavigationError(error)) {
161
+ console.log('⚠️ Navigation detected during element count, returning 0');
162
+ return 0;
163
+ }
164
+ throw error;
165
+ }
166
+ }
package/src/exports.js ADDED
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Browser Commander - Public API exports
3
+ * This module centralizes all public exports from the browser-commander library.
4
+ */
5
+
6
+ // Re-export core utilities
7
+ export { CHROME_ARGS, TIMING } from './core/constants.js';
8
+ export { isVerboseEnabled, createLogger } from './core/logger.js';
9
+ export { disableTranslateInPreferences } from './core/preferences.js';
10
+ export { detectEngine } from './core/engine-detection.js';
11
+ export {
12
+ isNavigationError,
13
+ safeOperation,
14
+ makeNavigationSafe,
15
+ withNavigationSafety,
16
+ } from './core/navigation-safety.js';
17
+
18
+ // Re-export new core components
19
+ export { createNetworkTracker } from './core/network-tracker.js';
20
+ export { createNavigationManager } from './core/navigation-manager.js';
21
+ export { createPageSessionFactory } from './core/page-session.js';
22
+
23
+ // Re-export engine adapter
24
+ export {
25
+ EngineAdapter,
26
+ PlaywrightAdapter,
27
+ PuppeteerAdapter,
28
+ createEngineAdapter,
29
+ } from './core/engine-adapter.js';
30
+
31
+ // Page trigger system
32
+ export {
33
+ createPageTriggerManager,
34
+ ActionStoppedError,
35
+ isActionStoppedError,
36
+ makeUrlCondition,
37
+ allConditions,
38
+ anyCondition,
39
+ notCondition,
40
+ } from './core/page-trigger-manager.js';
41
+
42
+ // Re-export browser management
43
+ export { launchBrowser } from './browser/launcher.js';
44
+ export {
45
+ waitForUrlStabilization,
46
+ goto,
47
+ waitForNavigation,
48
+ waitForPageReady,
49
+ waitAfterAction,
50
+ // Navigation verification
51
+ defaultNavigationVerification,
52
+ verifyNavigation,
53
+ } from './browser/navigation.js';
54
+
55
+ // Re-export element operations
56
+ export {
57
+ createPlaywrightLocator,
58
+ getLocatorOrElement,
59
+ waitForLocatorOrElement,
60
+ waitForVisible,
61
+ locator,
62
+ } from './elements/locators.js';
63
+
64
+ export {
65
+ querySelector,
66
+ querySelectorAll,
67
+ findByText,
68
+ normalizeSelector,
69
+ withTextSelectorSupport,
70
+ waitForSelector,
71
+ } from './elements/selectors.js';
72
+
73
+ export { isVisible, isEnabled, count } from './elements/visibility.js';
74
+
75
+ export {
76
+ textContent,
77
+ inputValue,
78
+ getAttribute,
79
+ getInputValue,
80
+ logElementInfo,
81
+ } from './elements/content.js';
82
+
83
+ // Re-export interactions
84
+ export {
85
+ scrollIntoView,
86
+ needsScrolling,
87
+ scrollIntoViewIfNeeded,
88
+ // Scroll verification
89
+ defaultScrollVerification,
90
+ verifyScroll,
91
+ } from './interactions/scroll.js';
92
+
93
+ export {
94
+ clickElement,
95
+ clickButton,
96
+ // Click verification
97
+ defaultClickVerification,
98
+ capturePreClickState,
99
+ verifyClick,
100
+ } from './interactions/click.js';
101
+
102
+ export {
103
+ checkIfElementEmpty,
104
+ performFill,
105
+ fillTextArea,
106
+ // Fill verification
107
+ defaultFillVerification,
108
+ verifyFill,
109
+ } from './interactions/fill.js';
110
+
111
+ // Re-export utilities
112
+ export { wait, evaluate, safeEvaluate } from './utilities/wait.js';
113
+ export { getUrl, unfocusAddressBar } from './utilities/url.js';
114
+
115
+ // Re-export high-level universal logic
116
+ export {
117
+ waitForUrlCondition,
118
+ installClickListener,
119
+ checkAndClearFlag,
120
+ findToggleButton,
121
+ } from './high-level/universal-logic.js';