@wong2kim/wmux 1.1.1 → 1.1.2

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.
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyAntiDetection = applyAntiDetection;
4
+ exports.evaluateWithGesture = evaluateWithGesture;
5
+ // ---------------------------------------------------------------------------
6
+ // Anti-detection helpers
7
+ // ---------------------------------------------------------------------------
8
+ /**
9
+ * Apply anti-detection measures to the page via init scripts.
10
+ *
11
+ * Currently patches `navigator.webdriver` to return `undefined` so that
12
+ * common bot-detection scripts do not flag the session.
13
+ */
14
+ async function applyAntiDetection(page) {
15
+ await page.context().addInitScript(() => {
16
+ // Override navigator.webdriver to hide automation flag
17
+ Object.defineProperty(navigator, 'webdriver', {
18
+ get: () => undefined,
19
+ configurable: true,
20
+ });
21
+ });
22
+ }
23
+ // ---------------------------------------------------------------------------
24
+ // CDP-powered evaluate with user gesture
25
+ // ---------------------------------------------------------------------------
26
+ /**
27
+ * Evaluate a JavaScript expression in the page context with the
28
+ * `userGesture` flag set to `true`.
29
+ *
30
+ * This is useful for actions that require a transient user activation
31
+ * (e.g. opening a popup, triggering downloads) without an actual mouse /
32
+ * keyboard event.
33
+ *
34
+ * Internally opens a CDP session and calls `Runtime.evaluate`.
35
+ */
36
+ async function evaluateWithGesture(page, expression) {
37
+ const client = await page.context().newCDPSession(page);
38
+ try {
39
+ const result = await client.send('Runtime.evaluate', {
40
+ expression,
41
+ userGesture: true,
42
+ returnByValue: true,
43
+ awaitPromise: true,
44
+ });
45
+ if (result.exceptionDetails) {
46
+ const msg = result.exceptionDetails.exception?.description ??
47
+ result.exceptionDetails.text ??
48
+ 'CDP Runtime.evaluate threw an exception';
49
+ throw new Error(msg);
50
+ }
51
+ return result.result.value;
52
+ }
53
+ finally {
54
+ await client.detach().catch(() => {
55
+ /* best-effort cleanup */
56
+ });
57
+ }
58
+ }
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSmartSnapshot = getSmartSnapshot;
4
+ exports.getLocatorByRef = getLocatorByRef;
5
+ exports.clearElementCache = clearElementCache;
6
+ // ---------------------------------------------------------------------------
7
+ // Constants
8
+ // ---------------------------------------------------------------------------
9
+ const DEFAULT_MAX_CONTENT_LENGTH = 3000;
10
+ /** Roles considered interactive — elements with these roles get indexed */
11
+ const INTERACTIVE_ROLES = new Set([
12
+ 'button',
13
+ 'link',
14
+ 'textbox',
15
+ 'checkbox',
16
+ 'radio',
17
+ 'combobox',
18
+ 'listbox',
19
+ 'menuitem',
20
+ 'menuitemcheckbox',
21
+ 'menuitemradio',
22
+ 'option',
23
+ 'searchbox',
24
+ 'slider',
25
+ 'spinbutton',
26
+ 'switch',
27
+ 'tab',
28
+ 'treeitem',
29
+ ]);
30
+ // ---------------------------------------------------------------------------
31
+ // Element cache — stores indexed elements from the last snapshot
32
+ // ---------------------------------------------------------------------------
33
+ let elementCache = [];
34
+ // ---------------------------------------------------------------------------
35
+ // Internal helpers
36
+ // ---------------------------------------------------------------------------
37
+ /**
38
+ * Escape special characters in a string for use inside a Playwright
39
+ * locator expression (e.g. `getByRole('button', { name: '...' })`).
40
+ */
41
+ function escapeLocatorName(name) {
42
+ return name.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
43
+ }
44
+ /**
45
+ * Build a Playwright locator string for a given role and name.
46
+ *
47
+ * If the name is empty, falls back to `getByRole('role')` without a
48
+ * name filter. When duplicate names exist for the same role, callers
49
+ * should use `.nth()` — but we provide the base locator here.
50
+ */
51
+ function buildLocatorString(role, name) {
52
+ if (!name) {
53
+ return `getByRole('${role}')`;
54
+ }
55
+ return `getByRole('${role}', { name: '${escapeLocatorName(name)}' })`;
56
+ }
57
+ /**
58
+ * Recursively walk the CDP accessibility tree and collect interactive
59
+ * elements into the provided array, assigning 1-based ref numbers.
60
+ */
61
+ function collectInteractiveElements(nodeMap, node, elements) {
62
+ if (node.ignored)
63
+ return;
64
+ const role = node.role?.value ?? 'none';
65
+ const name = node.name?.value ?? '';
66
+ if (INTERACTIVE_ROLES.has(role)) {
67
+ const ref = elements.length + 1; // 1-based
68
+ const element = {
69
+ ref,
70
+ role,
71
+ name,
72
+ locator: buildLocatorString(role, name),
73
+ };
74
+ if (node.value?.value) {
75
+ element.value = node.value.value;
76
+ }
77
+ if (node.description?.value) {
78
+ element.description = node.description.value;
79
+ }
80
+ elements.push(element);
81
+ }
82
+ // Recurse into children
83
+ if (node.childIds) {
84
+ for (const childId of node.childIds) {
85
+ const child = nodeMap.get(childId);
86
+ if (child) {
87
+ collectInteractiveElements(nodeMap, child, elements);
88
+ }
89
+ }
90
+ }
91
+ }
92
+ /**
93
+ * Fetch the full accessibility tree via CDP and return indexed interactive
94
+ * elements.
95
+ */
96
+ async function getInteractiveElements(page) {
97
+ const client = await page.context().newCDPSession(page);
98
+ try {
99
+ const { nodes } = (await client.send('Accessibility.getFullAXTree'));
100
+ if (nodes.length === 0)
101
+ return [];
102
+ // Build a map for quick lookup by nodeId
103
+ const nodeMap = new Map();
104
+ for (const n of nodes)
105
+ nodeMap.set(n.nodeId, n);
106
+ const elements = [];
107
+ collectInteractiveElements(nodeMap, nodes[0], elements);
108
+ return elements;
109
+ }
110
+ finally {
111
+ await client.detach().catch(() => {
112
+ /* best-effort cleanup */
113
+ });
114
+ }
115
+ }
116
+ /**
117
+ * Retrieve truncated page text content.
118
+ */
119
+ async function getPageContent(page, maxLength) {
120
+ try {
121
+ const text = await page.innerText('body');
122
+ if (text.length <= maxLength)
123
+ return text;
124
+ return text.slice(0, maxLength) + '\n... (truncated)';
125
+ }
126
+ catch {
127
+ return '';
128
+ }
129
+ }
130
+ // ---------------------------------------------------------------------------
131
+ // Public API
132
+ // ---------------------------------------------------------------------------
133
+ /**
134
+ * Generate a "smart snapshot" of the page: a structured representation
135
+ * containing only interactive elements (with 1-based ref indices) plus a
136
+ * truncated text summary of the page content.
137
+ *
138
+ * The indexed elements are cached internally so that `getLocatorByRef()`
139
+ * can resolve a ref number back to a Playwright locator string without
140
+ * re-querying the page.
141
+ */
142
+ async function getSmartSnapshot(page, options) {
143
+ const maxContentLength = options?.maxContentLength ?? DEFAULT_MAX_CONTENT_LENGTH;
144
+ const [url, title, elements, content] = await Promise.all([
145
+ Promise.resolve(page.url()),
146
+ page.title(),
147
+ getInteractiveElements(page),
148
+ getPageContent(page, maxContentLength),
149
+ ]);
150
+ // Update element cache
151
+ elementCache = elements;
152
+ return { url, title, elements, content };
153
+ }
154
+ /**
155
+ * Look up a Playwright locator string by the 1-based ref number assigned
156
+ * during the most recent `getSmartSnapshot()` call.
157
+ *
158
+ * Returns `null` if the ref is out of range or no snapshot has been taken.
159
+ */
160
+ function getLocatorByRef(ref) {
161
+ if (ref < 1 || ref > elementCache.length)
162
+ return null;
163
+ return elementCache[ref - 1].locator;
164
+ }
165
+ /**
166
+ * Clear the cached element list. Useful when navigating to a new page
167
+ * to avoid stale refs.
168
+ */
169
+ function clearElementCache() {
170
+ elementCache = [];
171
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateDelaySchedule = generateDelaySchedule;
4
+ exports.typeHumanlike = typeHumanlike;
5
+ // ---------------------------------------------------------------------------
6
+ // Internal helpers
7
+ // ---------------------------------------------------------------------------
8
+ const DEFAULT_MIN_DELAY = 50;
9
+ const DEFAULT_MAX_DELAY = 150;
10
+ function randomDelay(min, max) {
11
+ return Math.random() * (max - min) + min;
12
+ }
13
+ function sleep(ms) {
14
+ return new Promise((resolve) => setTimeout(resolve, ms));
15
+ }
16
+ // ---------------------------------------------------------------------------
17
+ // Public API
18
+ // ---------------------------------------------------------------------------
19
+ /**
20
+ * Generate an array of per-character delay values (in ms) for the given
21
+ * text. This mirrors `HumanBehavior.generateTypingSchedule()` from the
22
+ * main process but is independent of that class.
23
+ */
24
+ function generateDelaySchedule(text, options) {
25
+ const min = options?.minDelay ?? DEFAULT_MIN_DELAY;
26
+ const max = options?.maxDelay ?? DEFAULT_MAX_DELAY;
27
+ return Array.from({ length: text.length }, () => randomDelay(min, max));
28
+ }
29
+ /**
30
+ * Type `text` into the element identified by `selector` with randomised
31
+ * inter-keystroke delays that mimic human typing.
32
+ *
33
+ * Each character is pressed individually via `page.keyboard.press()` with
34
+ * a random pause between `minDelay` and `maxDelay` milliseconds.
35
+ *
36
+ * If `selector` is provided the element is clicked first to ensure focus.
37
+ */
38
+ async function typeHumanlike(page, selector, text, options) {
39
+ // Focus the target element
40
+ if (selector) {
41
+ await page.click(selector);
42
+ }
43
+ const delays = generateDelaySchedule(text, options);
44
+ for (let i = 0; i < text.length; i++) {
45
+ await page.keyboard.press(text[i]);
46
+ await sleep(delays[i]);
47
+ }
48
+ }