arn-browser 0.0.5 → 0.0.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arn-browser",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "A lightweight, browser autmation helper.",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -20,6 +20,22 @@ function sleep(ms) {
20
20
  return new Promise(resolve => setTimeout(resolve, ms));
21
21
  }
22
22
 
23
+ /**
24
+ * Simple mutex for serializing keyboard operations
25
+ */
26
+ let typingLock = Promise.resolve();
27
+ async function withTypingLock(fn) {
28
+ const previousLock = typingLock;
29
+ let releaseLock;
30
+ typingLock = new Promise(resolve => { releaseLock = resolve; });
31
+ await previousLock;
32
+ try {
33
+ return await fn();
34
+ } finally {
35
+ releaseLock();
36
+ }
37
+ }
38
+
23
39
  /**
24
40
  * Creates a HumanLocator that wraps a Playwright Locator
25
41
  * with human-like cursor movement for actions
@@ -70,18 +86,33 @@ function createHumanLocator(cursor, locator) {
70
86
  if (!cursor.config.humanize) {
71
87
  return await locator.fill(value, options);
72
88
  }
73
- await this.click(options);
74
- await cursor._page.keyboard.press('Control+A');
75
- await sleep(randInt(50, 100));
76
- await cursor._type(value, options);
89
+ // Humanize: Move to element, then fill instantly (Playwright handles focus)
90
+ await cursor._moveToLocator(locator, options);
91
+ return await locator.fill(value, options);
92
+ },
93
+
94
+ async fillSequentially(value, options = {}) {
95
+ if (!cursor.config.humanize) {
96
+ return await locator.fill(value, options);
97
+ }
98
+ return await withTypingLock(async () => {
99
+ await this.click(options);
100
+ // Clear field reliably using fill('') before human typing
101
+ await locator.fill('');
102
+ await sleep(randInt(50, 100));
103
+ // Human type with delays
104
+ await cursor._type(value, options);
105
+ });
77
106
  },
78
107
 
79
108
  async type(text, options = {}) {
80
109
  if (!cursor.config.humanize) {
81
110
  return await locator.type(text, options);
82
111
  }
83
- await this.click(options);
84
- await cursor._type(text, options);
112
+ return await withTypingLock(async () => {
113
+ await this.click(options);
114
+ await cursor._type(text, options);
115
+ });
85
116
  },
86
117
 
87
118
  async hover(options = {}) {
@@ -99,6 +130,46 @@ function createHumanLocator(cursor, locator) {
99
130
  await cursor._page.keyboard.press(key);
100
131
  },
101
132
 
133
+ async check(options = {}) {
134
+ if (!cursor.config.humanize) {
135
+ return await locator.check(options);
136
+ }
137
+ if (!(await locator.isChecked())) {
138
+ await this.click(options);
139
+ }
140
+ },
141
+
142
+ async uncheck(options = {}) {
143
+ if (!cursor.config.humanize) {
144
+ return await locator.uncheck(options);
145
+ }
146
+ if (await locator.isChecked()) {
147
+ await this.click(options);
148
+ }
149
+ },
150
+
151
+ async setChecked(checked, options = {}) {
152
+ if (!cursor.config.humanize) {
153
+ return await locator.setChecked(checked, options);
154
+ }
155
+ if (checked) {
156
+ await this.check(options);
157
+ } else {
158
+ await this.uncheck(options);
159
+ }
160
+ },
161
+
162
+ async pressSequentially(text, options = {}) {
163
+ if (!cursor.config.humanize) {
164
+ return await locator.pressSequentially(text, options);
165
+ }
166
+ return await withTypingLock(async () => {
167
+ await this.click(options);
168
+ await locator.clear();
169
+ await cursor._type(text, options);
170
+ });
171
+ },
172
+
102
173
  // Chainable methods that return new HumanLocators
103
174
  filter(options) {
104
175
  return createHumanLocator(cursor, locator.filter(options));
@@ -151,7 +222,7 @@ function createHumanLocator(cursor, locator) {
151
222
 
152
223
  /**
153
224
  * Create a HumanCursor that acts as a drop-in replacement for Page
154
- * All page methods work, but click/fill/type/hover use human cursor
225
+ * All page methods work, but click/fill/type/check/hover use human cursor
155
226
  *
156
227
  * @param {import('playwright').Page} page - Playwright Page object
157
228
  * @param {Object} options - Configuration options
@@ -411,6 +482,27 @@ export function createCursor(page, options = {}) {
411
482
  if (prop in locatorMethods) {
412
483
  return locatorMethods[prop];
413
484
  }
485
+
486
+ // Override Page methods to use HumanLocator
487
+ if (['click', 'dblclick', 'fill', 'hover', 'type', 'press', 'check', 'uncheck'].includes(prop)) {
488
+ return async (selector, ...args) => {
489
+ const humanLocator = createHumanLocator(cursor, page.locator(selector));
490
+ return await humanLocator[prop](...args);
491
+ };
492
+ }
493
+ if (prop === 'fillSequentially') {
494
+ return async (selector, ...args) => {
495
+ const humanLocator = createHumanLocator(cursor, page.locator(selector));
496
+ return await humanLocator.fillSequentially(...args);
497
+ };
498
+ }
499
+ if (prop === 'pressSequentially') {
500
+ return async (selector, ...args) => {
501
+ const humanLocator = createHumanLocator(cursor, page.locator(selector));
502
+ return await humanLocator.pressSequentially(...args);
503
+ };
504
+ }
505
+
414
506
  // Expose cursor utilities
415
507
  if (prop === 'showCursor') {
416
508
  return cursor.showCursor.bind(cursor);
@@ -66,7 +66,9 @@ export interface HumanClickOptions {
66
66
  }
67
67
 
68
68
  export interface HumanFillOptions extends HumanClickOptions {
69
+ /** Minimum delay between keystrokes in ms (default: 50) */
69
70
  minDelay?: number;
71
+ /** Maximum delay between keystrokes in ms (default: 150) */
70
72
  maxDelay?: number;
71
73
  }
72
74
 
@@ -77,8 +79,13 @@ export interface HumanFillOptions extends HumanClickOptions {
77
79
  export interface HumanLocator extends Locator {
78
80
  click(options?: HumanClickOptions): Promise<void>;
79
81
  dblclick(options?: HumanClickOptions): Promise<void>;
80
- fill(value: string, options?: HumanFillOptions): Promise<void>;
82
+ fill(value: string, options?: HumanClickOptions): Promise<void>;
83
+ fillSequentially(value: string, options?: HumanFillOptions): Promise<void>;
81
84
  type(text: string, options?: HumanFillOptions): Promise<void>;
85
+ pressSequentially(text: string, options?: HumanFillOptions): Promise<void>;
86
+ check(options?: HumanClickOptions): Promise<void>;
87
+ uncheck(options?: HumanClickOptions): Promise<void>;
88
+ setChecked(checked: boolean, options?: HumanClickOptions): Promise<void>;
82
89
  hover(options?: HumanClickOptions): Promise<void>;
83
90
  press(key: string, options?: HumanClickOptions): Promise<void>;
84
91