@dev-blinq/cucumber_client 1.0.1624-dev → 1.0.1625-dev

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.
@@ -327,10 +327,14 @@ async function BVTRecorderInit({ envName, projectDir, roomId, TOKEN, socket = nu
327
327
  console.log("Received browserUI.goForward", input);
328
328
  await recorder.goForward(input);
329
329
  },
330
- "browserUI.clipboardWrite": async (input) => {
331
- console.log("Received browserUI.clipboardWrite");
330
+ "browserUI.writeToClipboard": async (input) => {
331
+ console.log("Received browserUI.writeToClipboard");
332
332
  await recorder.applyClipboardPayload(input);
333
333
  },
334
+ "browserUI.readToClipboard": async (input) => {
335
+ console.log("Received browserUI.readToClipboard");
336
+ await recorder.readClipboardPayload(input);
337
+ },
334
338
  "recorderWindow.cleanup": async (input) => {
335
339
  return recorder.cleanup(input);
336
340
  },
@@ -7,8 +7,7 @@ import { getImplementedSteps, parseRouteFiles } from "./implemented_steps.js";
7
7
  import { NamesService, RemoteBrowserService, PublishService } from "./services.js";
8
8
  import { BVTStepRunner } from "./step_runner.js";
9
9
  import { readFile, writeFile } from "fs/promises";
10
- import { updateStepDefinitions, loadStepDefinitions, getCommandsForImplementedStep } from "./step_utils.js";
11
- import { updateFeatureFile } from "./update_feature.js";
10
+ import { loadStepDefinitions, getCommandsForImplementedStep } from "./step_utils.js";
12
11
  import { parseStepTextParameters } from "../cucumber/utils.js";
13
12
  import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
14
13
  import chokidar from "chokidar";
@@ -18,37 +17,36 @@ import socketLogger from "../utils/socket_logger.js";
18
17
  import { tmpdir } from "os";
19
18
  import { faker } from "@faker-js/faker/locale/en_US";
20
19
  import { chromium } from "playwright-core";
20
+
21
21
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
22
22
 
23
23
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
24
- export function getInitScript(config, options) {
25
- const preScript = `
26
- window.__bvt_Recorder_config = ${JSON.stringify(config ?? null)};
27
- window.__PW_options = ${JSON.stringify(options ?? null)};
28
- `;
29
- const recorderScript = readFileSync(
30
- path.join(__dirname, "..", "..", "assets", "bundled_scripts", "recorder.js"),
31
- "utf8"
32
- );
33
- const clipboardBridgeScript = `
24
+
25
+ const clipboardBridgeScript = `
34
26
  ;(() => {
35
27
  if (window.__bvtRecorderClipboardBridgeInitialized) {
28
+ console.log('[ClipboardBridge] Already initialized, skipping');
36
29
  return;
37
30
  }
38
31
  window.__bvtRecorderClipboardBridgeInitialized = true;
32
+ console.log('[ClipboardBridge] Initializing clipboard bridge');
39
33
 
40
34
  const emitPayload = (payload, attempt = 0) => {
41
35
  const reporter = window.__bvt_reportClipboard;
42
36
  if (typeof reporter === "function") {
43
37
  try {
38
+ console.log('[ClipboardBridge] Reporting clipboard payload:', payload);
44
39
  reporter(payload);
45
40
  } catch (error) {
46
- console.warn("Clipboard bridge failed to report payload", error);
41
+ console.warn("[ClipboardBridge] Failed to report payload", error);
47
42
  }
48
43
  return;
49
44
  }
50
45
  if (attempt < 5) {
46
+ console.log('[ClipboardBridge] Reporter not ready, retrying...', attempt);
51
47
  setTimeout(() => emitPayload(payload, attempt + 1), 50 * (attempt + 1));
48
+ } else {
49
+ console.warn('[ClipboardBridge] Reporter never became available');
52
50
  }
53
51
  };
54
52
 
@@ -78,7 +76,7 @@ export function getInitScript(config, options) {
78
76
  reader.onerror = () => resolve(null);
79
77
  reader.readAsDataURL(file);
80
78
  } catch (error) {
81
- console.warn("Clipboard bridge failed to serialize file", error);
79
+ console.warn("[ClipboardBridge] Failed to serialize file", error);
82
80
  resolve(null);
83
81
  }
84
82
  });
@@ -86,6 +84,7 @@ export function getInitScript(config, options) {
86
84
 
87
85
  const handleClipboardEvent = async (event) => {
88
86
  try {
87
+ console.log('[ClipboardBridge] Handling clipboard event:', event.type);
89
88
  const payload = { trigger: event.type };
90
89
  const clipboardData = event.clipboardData;
91
90
 
@@ -94,22 +93,25 @@ export function getInitScript(config, options) {
94
93
  const text = clipboardData.getData("text/plain");
95
94
  if (text) {
96
95
  payload.text = text;
96
+ console.log('[ClipboardBridge] Captured text:', text.substring(0, 50));
97
97
  }
98
98
  } catch (error) {
99
- console.warn("Clipboard bridge could not read text/plain", error);
99
+ console.warn("[ClipboardBridge] Could not read text/plain", error);
100
100
  }
101
101
 
102
102
  try {
103
103
  const html = clipboardData.getData("text/html");
104
104
  if (html) {
105
105
  payload.html = html;
106
+ console.log('[ClipboardBridge] Captured HTML:', html.substring(0, 50));
106
107
  }
107
108
  } catch (error) {
108
- console.warn("Clipboard bridge could not read text/html", error);
109
+ console.warn("[ClipboardBridge] Could not read text/html", error);
109
110
  }
110
111
 
111
112
  const files = clipboardData.files;
112
113
  if (files && files.length > 0) {
114
+ console.log('[ClipboardBridge] Processing files:', files.length);
113
115
  const serialized = [];
114
116
  for (const file of files) {
115
117
  const data = await fileToBase64(file);
@@ -134,6 +136,7 @@ export function getInitScript(config, options) {
134
136
  const selectionText = selection?.toString?.();
135
137
  if (selectionText) {
136
138
  payload.text = selectionText;
139
+ console.log('[ClipboardBridge] Using selection text:', selectionText.substring(0, 50));
137
140
  }
138
141
  } catch {
139
142
  // Ignore selection access errors.
@@ -142,10 +145,170 @@ export function getInitScript(config, options) {
142
145
 
143
146
  emitPayload(payload);
144
147
  } catch (error) {
145
- console.warn("Clipboard bridge could not process event", error);
148
+ console.warn("[ClipboardBridge] Could not process event", error);
146
149
  }
147
150
  };
148
151
 
152
+ // NEW: Function to apply clipboard data to the page
153
+ window.__bvt_applyClipboardData = (payload) => {
154
+ console.log('[ClipboardBridge] Applying clipboard data:', payload);
155
+
156
+ if (!payload) {
157
+ console.warn('[ClipboardBridge] No payload provided');
158
+ return false;
159
+ }
160
+
161
+ try {
162
+ // Create DataTransfer object
163
+ let dataTransfer = null;
164
+ try {
165
+ dataTransfer = new DataTransfer();
166
+ console.log('[ClipboardBridge] DataTransfer created');
167
+ } catch (error) {
168
+ console.warn('[ClipboardBridge] Could not create DataTransfer', error);
169
+ }
170
+
171
+ if (dataTransfer) {
172
+ if (payload.text) {
173
+ try {
174
+ dataTransfer.setData("text/plain", payload.text);
175
+ console.log('[ClipboardBridge] Set text/plain:', payload.text.substring(0, 50));
176
+ } catch (error) {
177
+ console.warn('[ClipboardBridge] Failed to set text/plain', error);
178
+ }
179
+ }
180
+ if (payload.html) {
181
+ try {
182
+ dataTransfer.setData("text/html", payload.html);
183
+ console.log('[ClipboardBridge] Set text/html:', payload.html.substring(0, 50));
184
+ } catch (error) {
185
+ console.warn('[ClipboardBridge] Failed to set text/html', error);
186
+ }
187
+ }
188
+ }
189
+
190
+ // Get target element
191
+ let target = document.activeElement || document.body;
192
+ console.log('[ClipboardBridge] Target element:', {
193
+ tagName: target.tagName,
194
+ type: target.type,
195
+ isContentEditable: target.isContentEditable,
196
+ id: target.id,
197
+ className: target.className
198
+ });
199
+
200
+ // Try synthetic paste event first
201
+ let pasteHandled = false;
202
+ if (dataTransfer && target && typeof target.dispatchEvent === "function") {
203
+ try {
204
+ const pasteEvent = new ClipboardEvent("paste", {
205
+ clipboardData: dataTransfer,
206
+ bubbles: true,
207
+ cancelable: true,
208
+ });
209
+ pasteHandled = target.dispatchEvent(pasteEvent);
210
+ console.log('[ClipboardBridge] Paste event dispatched, handled:', pasteHandled);
211
+ } catch (error) {
212
+ console.warn('[ClipboardBridge] Failed to dispatch paste event', error);
213
+ }
214
+ }
215
+
216
+
217
+ console.log('[ClipboardBridge] Paste event not handled, trying fallback methods');
218
+
219
+ // Fallback: Try execCommand with HTML first (for contenteditable)
220
+ if (payload.html && target.isContentEditable) {
221
+ console.log('[ClipboardBridge] Trying execCommand insertHTML');
222
+ try {
223
+ const inserted = document.execCommand('insertHTML', false, payload.html);
224
+ if (inserted) {
225
+ console.log('[ClipboardBridge] Successfully inserted HTML via execCommand');
226
+ return true;
227
+ }
228
+ } catch (error) {
229
+ console.warn('[ClipboardBridge] execCommand insertHTML failed', error);
230
+ }
231
+
232
+ // Try Range API for HTML
233
+ console.log('[ClipboardBridge] Trying Range API for HTML');
234
+ try {
235
+ const selection = window.getSelection?.();
236
+ if (selection && selection.rangeCount > 0) {
237
+ const range = selection.getRangeAt(0);
238
+ range.deleteContents();
239
+ const fragment = range.createContextualFragment(payload.html);
240
+ range.insertNode(fragment);
241
+ range.collapse(false);
242
+ console.log('[ClipboardBridge] Successfully inserted HTML via Range API');
243
+ return true;
244
+ }
245
+ } catch (error) {
246
+ console.warn('[ClipboardBridge] Range API HTML insertion failed', error);
247
+ }
248
+ }
249
+
250
+ // Fallback: Try execCommand with text
251
+ if (payload.text) {
252
+ console.log('[ClipboardBridge] Trying execCommand insertText');
253
+ try {
254
+ const inserted = document.execCommand('insertText', false, payload.text);
255
+ if (inserted) {
256
+ console.log('[ClipboardBridge] Successfully inserted text via execCommand');
257
+ return true;
258
+ }
259
+ } catch (error) {
260
+ console.warn('[ClipboardBridge] execCommand insertText failed', error);
261
+ }
262
+
263
+ // Try Range API for text
264
+ if (target.isContentEditable) {
265
+ console.log('[ClipboardBridge] Trying Range API for text');
266
+ try {
267
+ const selection = window.getSelection?.();
268
+ if (selection && selection.rangeCount > 0) {
269
+ const range = selection.getRangeAt(0);
270
+ range.deleteContents();
271
+ range.insertNode(document.createTextNode(payload.text));
272
+ range.collapse(false);
273
+ console.log('[ClipboardBridge] Successfully inserted text via Range API');
274
+ return true;
275
+ }
276
+ } catch (error) {
277
+ console.warn('[ClipboardBridge] Range API text insertion failed', error);
278
+ }
279
+ }
280
+
281
+ // Last resort: Direct value assignment for input/textarea
282
+ if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
283
+ console.log('[ClipboardBridge] Trying direct value assignment');
284
+ try {
285
+ const start = target.selectionStart ?? target.value.length ?? 0;
286
+ const end = target.selectionEnd ?? target.value.length ?? 0;
287
+ const value = target.value ?? "";
288
+ const text = payload.text;
289
+ target.value = value.slice(0, start) + text + value.slice(end);
290
+ const caret = start + text.length;
291
+ if (typeof target.setSelectionRange === 'function') {
292
+ target.setSelectionRange(caret, caret);
293
+ }
294
+ target.dispatchEvent(new Event('input', { bubbles: true }));
295
+ console.log('[ClipboardBridge] Successfully set value directly');
296
+ return true;
297
+ } catch (error) {
298
+ console.warn('[ClipboardBridge] Direct value assignment failed', error);
299
+ }
300
+ }
301
+ }
302
+
303
+ console.warn('[ClipboardBridge] All paste methods failed');
304
+ return false;
305
+ } catch (error) {
306
+ console.error('[ClipboardBridge] Error applying clipboard data:', error);
307
+ return false;
308
+ }
309
+ };
310
+
311
+ // Set up event listeners for copy/cut
149
312
  document.addEventListener(
150
313
  "copy",
151
314
  (event) => {
@@ -160,8 +323,20 @@ export function getInitScript(config, options) {
160
323
  },
161
324
  true
162
325
  );
326
+
327
+ console.log('[ClipboardBridge] Clipboard bridge initialized successfully');
163
328
  })();
329
+ `;
330
+
331
+ export function getInitScript(config, options) {
332
+ const preScript = `
333
+ window.__bvt_Recorder_config = ${JSON.stringify(config ?? null)};
334
+ window.__PW_options = ${JSON.stringify(options ?? null)};
164
335
  `;
336
+ const recorderScript = readFileSync(
337
+ path.join(__dirname, "..", "..", "assets", "bundled_scripts", "recorder.js"),
338
+ "utf8"
339
+ );
165
340
  return preScript + recorderScript + clipboardBridgeScript;
166
341
  }
167
342
 
@@ -1539,25 +1714,55 @@ export class BVTRecorder {
1539
1714
 
1540
1715
  async applyClipboardPayload(message) {
1541
1716
  const payload = message?.data ?? message;
1717
+
1718
+ this.logger.info("[BVTRecorder] applyClipboardPayload called", {
1719
+ hasPayload: !!payload,
1720
+ hasText: !!payload?.text,
1721
+ hasHtml: !!payload?.html,
1722
+ trigger: message?.trigger,
1723
+ });
1724
+
1542
1725
  if (!payload) {
1726
+ this.logger.warn("[BVTRecorder] No payload provided");
1543
1727
  return;
1544
1728
  }
1545
1729
 
1546
1730
  try {
1547
1731
  if (this.browserEmitter && typeof this.browserEmitter.applyClipboardPayload === "function") {
1732
+ this.logger.info("[BVTRecorder] Using RemoteBrowserService to apply clipboard");
1548
1733
  await this.browserEmitter.applyClipboardPayload(payload);
1549
1734
  return;
1550
1735
  }
1551
1736
 
1552
1737
  const activePage = this.browserEmitter?.getSelectedPage() ?? this.page;
1553
1738
  if (!activePage) {
1554
- this.logger.warn("No active page available to apply clipboard payload");
1739
+ this.logger.warn("[BVTRecorder] No active page available");
1555
1740
  return;
1556
1741
  }
1557
1742
 
1558
- await this.injectClipboardIntoPage(activePage, payload);
1743
+ this.logger.info("[BVTRecorder] Applying clipboard to page", {
1744
+ url: activePage.url(),
1745
+ isClosed: activePage.isClosed(),
1746
+ });
1747
+
1748
+ const result = await activePage.evaluate((clipboardData) => {
1749
+ console.log("[Page] Executing clipboard application", clipboardData);
1750
+ if (typeof window.__bvt_applyClipboardData === "function") {
1751
+ return window.__bvt_applyClipboardData(clipboardData);
1752
+ }
1753
+ console.error("[Page] __bvt_applyClipboardData function not found!");
1754
+ return false;
1755
+ }, payload);
1756
+
1757
+ this.logger.info("[BVTRecorder] Clipboard application result:", result);
1758
+
1759
+ if (!result) {
1760
+ this.logger.warn("[BVTRecorder] Clipboard data not applied successfully");
1761
+ } else {
1762
+ this.logger.info("[BVTRecorder] Clipboard data applied successfully");
1763
+ }
1559
1764
  } catch (error) {
1560
- this.logger.error("Error applying clipboard payload to page", error);
1765
+ this.logger.error("[BVTRecorder] Error applying clipboard payload", error);
1561
1766
  this.sendEvent(this.events.clipboardError, {
1562
1767
  message: "Failed to apply clipboard contents to the remote session",
1563
1768
  trigger: message?.trigger ?? "paste",
@@ -1565,6 +1770,152 @@ export class BVTRecorder {
1565
1770
  }
1566
1771
  }
1567
1772
 
1773
+ hasClipboardPayload(payload) {
1774
+ return Boolean(
1775
+ payload && (payload.text || payload.html || (Array.isArray(payload.files) && payload.files.length > 0))
1776
+ );
1777
+ }
1778
+
1779
+ async collectClipboardFromPage(page) {
1780
+ if (!page) {
1781
+ this.logger.warn("[BVTRecorder] No page available to collect clipboard data");
1782
+ return null;
1783
+ }
1784
+ try {
1785
+ await page
1786
+ .context()
1787
+ .grantPermissions(["clipboard-read", "clipboard-write"])
1788
+ .catch((error) => {
1789
+ this.logger.warn("[BVTRecorder] Failed to grant clipboard permissions before read", error);
1790
+ });
1791
+
1792
+ const payload = await page.evaluate(async () => {
1793
+ const result = {};
1794
+ if (typeof navigator === "undefined" || !navigator.clipboard) {
1795
+ return result;
1796
+ }
1797
+
1798
+ const arrayBufferToBase64 = (buffer) => {
1799
+ let binary = "";
1800
+ const bytes = new Uint8Array(buffer);
1801
+ const chunkSize = 0x8000;
1802
+ for (let index = 0; index < bytes.length; index += chunkSize) {
1803
+ const chunk = bytes.subarray(index, index + chunkSize);
1804
+ binary += String.fromCharCode(...chunk);
1805
+ }
1806
+ return btoa(binary);
1807
+ };
1808
+
1809
+ const files = [];
1810
+
1811
+ if (typeof navigator.clipboard.read === "function") {
1812
+ try {
1813
+ const items = await navigator.clipboard.read();
1814
+ for (const item of items) {
1815
+ if (item.types.includes("text/html") && !result.html) {
1816
+ const blob = await item.getType("text/html");
1817
+ result.html = await blob.text();
1818
+ }
1819
+ if (item.types.includes("text/plain") && !result.text) {
1820
+ const blob = await item.getType("text/plain");
1821
+ result.text = await blob.text();
1822
+ }
1823
+ for (const type of item.types) {
1824
+ if (type.startsWith("text/")) {
1825
+ continue;
1826
+ }
1827
+ try {
1828
+ const blob = await item.getType(type);
1829
+ const buffer = await blob.arrayBuffer();
1830
+ files.push({
1831
+ name: `clipboard-file-${files.length + 1}`,
1832
+ type,
1833
+ lastModified: Date.now(),
1834
+ data: arrayBufferToBase64(buffer),
1835
+ });
1836
+ } catch (error) {
1837
+ console.warn("[BVTRecorder] Failed to serialize clipboard blob", { type, error });
1838
+ }
1839
+ }
1840
+ }
1841
+ } catch (error) {
1842
+ console.warn("[BVTRecorder] navigator.clipboard.read failed", error);
1843
+ }
1844
+ }
1845
+
1846
+ if (!result.text && typeof navigator.clipboard.readText === "function") {
1847
+ try {
1848
+ const text = await navigator.clipboard.readText();
1849
+ if (text) {
1850
+ result.text = text;
1851
+ }
1852
+ } catch (error) {
1853
+ console.warn("[BVTRecorder] navigator.clipboard.readText failed", error);
1854
+ }
1855
+ }
1856
+
1857
+ if (!result.text) {
1858
+ const selection = window.getSelection?.()?.toString?.();
1859
+ if (selection) {
1860
+ result.text = selection;
1861
+ }
1862
+ }
1863
+
1864
+ if (files.length > 0) {
1865
+ result.files = files;
1866
+ }
1867
+
1868
+ return result;
1869
+ });
1870
+
1871
+ return payload;
1872
+ } catch (error) {
1873
+ this.logger.error("[BVTRecorder] Error collecting clipboard payload", error);
1874
+ return null;
1875
+ }
1876
+ }
1877
+
1878
+ async readClipboardPayload(message) {
1879
+ try {
1880
+ let payload = null;
1881
+ if (this.browserEmitter && typeof this.browserEmitter.readClipboardPayload === "function") {
1882
+ payload = await this.browserEmitter.readClipboardPayload();
1883
+ } else {
1884
+ const activePage = this.browserEmitter?.getSelectedPage() ?? this.page;
1885
+ payload = await this.collectClipboardFromPage(activePage);
1886
+ }
1887
+
1888
+ if (this.hasClipboardPayload(payload)) {
1889
+ this.logger.info("[BVTRecorder] Remote clipboard payload ready", {
1890
+ hasText: !!payload.text,
1891
+ hasHtml: !!payload.html,
1892
+ files: payload.files?.length ?? 0,
1893
+ });
1894
+ this.sendEvent(this.events.clipboardPush, {
1895
+ data: payload,
1896
+ trigger: message?.trigger ?? "copy",
1897
+ origin: message?.source ?? "browserUI",
1898
+ });
1899
+ return payload;
1900
+ }
1901
+
1902
+ this.logger.warn("[BVTRecorder] Remote clipboard payload empty or unavailable");
1903
+ this.sendEvent(this.events.clipboardError, {
1904
+ message: "Remote clipboard is empty",
1905
+ trigger: message?.trigger ?? "copy",
1906
+ });
1907
+ return null;
1908
+ } catch (error) {
1909
+ this.logger.error("[BVTRecorder] Error reading clipboard payload", error);
1910
+ this.sendEvent(this.events.clipboardError, {
1911
+ message: "Failed to read clipboard contents from the remote session",
1912
+ trigger: message?.trigger ?? "copy",
1913
+ details: error instanceof Error ? error.message : String(error),
1914
+ });
1915
+ throw error;
1916
+ }
1917
+ }
1918
+
1568
1919
  async injectClipboardIntoPage(page, payload) {
1569
1920
  if (!page) {
1570
1921
  return;
@@ -1576,6 +1927,7 @@ export class BVTRecorder {
1576
1927
  .grantPermissions(["clipboard-read", "clipboard-write"])
1577
1928
  .catch(() => {});
1578
1929
  await page.evaluate(async (clipboardPayload) => {
1930
+ console.log("Injecting clipboard payload", clipboardPayload);
1579
1931
  const toArrayBuffer = (base64) => {
1580
1932
  if (!base64) {
1581
1933
  return null;
@@ -220,6 +220,15 @@ export class RemoteBrowserService extends EventEmitter {
220
220
  pages = new Map();
221
221
  _selectedPageId = null;
222
222
  wsUrlBase; // Store the base URL
223
+ hasClipboardData(payload) {
224
+ return Boolean(payload && (payload.text || payload.html || (Array.isArray(payload.files) && payload.files.length > 0)));
225
+ }
226
+ getSelectedPageInfo() {
227
+ if (!this._selectedPageId) {
228
+ return null;
229
+ }
230
+ return this.pages.get(this._selectedPageId) ?? null;
231
+ }
223
232
  constructor({ CDP_CONNECT_URL, context }) {
224
233
  super();
225
234
  this.CDP_CONNECT_URL = CDP_CONNECT_URL;
@@ -507,182 +516,247 @@ export class RemoteBrowserService extends EventEmitter {
507
516
  }
508
517
  }
509
518
  async applyClipboardPayload(payload) {
519
+ this.log("📋 applyClipboardPayload called", {
520
+ hasText: !!payload?.text,
521
+ hasHtml: !!payload?.html,
522
+ hasFiles: !!payload?.files,
523
+ textLength: payload?.text?.length,
524
+ htmlLength: payload?.html?.length,
525
+ filesCount: payload?.files?.length,
526
+ });
510
527
  const pageInfo = this.getPageInfo(this._selectedPageId);
511
528
  if (!pageInfo) {
512
- this.log("âš ī¸ No active page available for clipboard payload");
529
+ this.log("âš ī¸ No active page available for clipboard payload", {
530
+ selectedPageId: this._selectedPageId,
531
+ totalPages: this.pages.size,
532
+ });
513
533
  return;
514
534
  }
535
+ this.log("📄 Target page info", {
536
+ stableTabId: this._selectedPageId,
537
+ url: pageInfo.page.url(),
538
+ isClosed: pageInfo.page.isClosed(),
539
+ title: await pageInfo.page.title().catch(() => "unknown"),
540
+ });
515
541
  try {
542
+ // Grant permissions first
543
+ this.log("🔐 Attempting to grant clipboard permissions");
516
544
  await pageInfo.page
517
545
  .context()
518
546
  .grantPermissions(["clipboard-read", "clipboard-write"])
519
- .catch(() => { });
520
- await pageInfo.page.evaluate(async (clipboardPayload) => {
521
- const toArrayBuffer = (base64) => {
522
- if (!base64) {
523
- return null;
524
- }
525
- const binary = atob(base64);
526
- const length = binary.length;
527
- const bytes = new Uint8Array(length);
528
- for (let index = 0; index < length; index += 1) {
529
- bytes[index] = binary.charCodeAt(index);
530
- }
531
- return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
532
- };
533
- const createFileFromPayload = (filePayload) => {
534
- const buffer = toArrayBuffer(filePayload?.data);
535
- if (!buffer) {
536
- return null;
537
- }
538
- const name = filePayload?.name || "clipboard-file";
539
- const type = filePayload?.type || "application/octet-stream";
540
- const lastModified = filePayload?.lastModified ?? Date.now();
541
- try {
542
- return new File([buffer], name, { type, lastModified });
543
- }
544
- catch (error) {
545
- console.warn("Clipboard bridge could not recreate File", error);
546
- return null;
547
- }
548
- };
549
- let dataTransfer = null;
547
+ .then(() => {
548
+ this.log("✅ Clipboard permissions granted");
549
+ })
550
+ .catch((error) => {
551
+ this.log("âš ī¸ Failed to grant clipboard permissions", { error: error.message });
552
+ });
553
+ // Apply the clipboard data using the injected function
554
+ this.log("🚀 Executing clipboard application script", {
555
+ payloadPreview: {
556
+ text: payload?.text?.substring(0, 100),
557
+ html: payload?.html?.substring(0, 100),
558
+ },
559
+ });
560
+ const result = await pageInfo.page.evaluate((clipboardData) => {
561
+ console.log("[RemoteBrowser->Page] Starting clipboard application");
562
+ console.log("[RemoteBrowser->Page] Payload:", {
563
+ hasText: !!clipboardData?.text,
564
+ hasHtml: !!clipboardData?.html,
565
+ text: clipboardData?.text?.substring(0, 50),
566
+ html: clipboardData?.html?.substring(0, 50),
567
+ });
568
+ // Check if the function exists
569
+ if (typeof window.__bvt_applyClipboardData !== "function") {
570
+ console.error("[RemoteBrowser->Page] ❌ __bvt_applyClipboardData function not found!");
571
+ console.log("[RemoteBrowser->Page] Available window properties:", Object.keys(window).filter((k) => k.includes("bvt")));
572
+ return false;
573
+ }
574
+ console.log("[RemoteBrowser->Page] ✅ __bvt_applyClipboardData function found, calling it");
550
575
  try {
551
- dataTransfer = new DataTransfer();
576
+ const result = window.__bvt_applyClipboardData(clipboardData);
577
+ console.log("[RemoteBrowser->Page] Function returned:", result);
578
+ return result;
552
579
  }
553
580
  catch (error) {
554
- console.warn("Clipboard bridge could not create DataTransfer", error);
581
+ console.error("[RemoteBrowser->Page] ❌ Error calling __bvt_applyClipboardData:", error);
582
+ return false;
555
583
  }
556
- const callLegacyExecCommand = (command, value) => {
557
- const execCommand = document.execCommand;
558
- if (typeof execCommand === "function") {
559
- try {
560
- return execCommand.call(document, command, false, value);
561
- }
562
- catch (error) {
563
- console.warn("Clipboard bridge failed to execute legacy command", error);
564
- }
584
+ }, payload);
585
+ this.log("📊 Clipboard application result", {
586
+ success: result,
587
+ selectedPageId: this._selectedPageId,
588
+ });
589
+ if (!result) {
590
+ this.log("âš ī¸ Clipboard script returned false - data may not have been applied");
591
+ }
592
+ else {
593
+ this.log("✅ Clipboard payload applied successfully");
594
+ }
595
+ }
596
+ catch (error) {
597
+ this.log("❌ Error applying clipboard payload", {
598
+ error: error instanceof Error ? error.message : String(error),
599
+ stack: error instanceof Error ? error.stack : undefined,
600
+ selectedPageId: this._selectedPageId,
601
+ pageUrl: pageInfo.page.url(),
602
+ });
603
+ throw error;
604
+ }
605
+ }
606
+ async readClipboardPayload() {
607
+ const pageInfo = this.getSelectedPageInfo();
608
+ if (!pageInfo) {
609
+ this.log("âš ī¸ Cannot read clipboard - no active page", { selectedPageId: this._selectedPageId });
610
+ return null;
611
+ }
612
+ try {
613
+ await pageInfo.page
614
+ .context()
615
+ .grantPermissions(["clipboard-read", "clipboard-write"])
616
+ .catch((error) => {
617
+ this.log("âš ī¸ Failed to ensure clipboard permissions before read", { error });
618
+ });
619
+ const payload = await pageInfo.page.evaluate(async () => {
620
+ const result = {};
621
+ if (typeof navigator === "undefined" || !navigator.clipboard) {
622
+ return result;
623
+ }
624
+ const arrayBufferToBase64 = (buffer) => {
625
+ let binary = "";
626
+ const bytes = new Uint8Array(buffer);
627
+ const chunkSize = 0x8000;
628
+ for (let index = 0; index < bytes.length; index += chunkSize) {
629
+ const chunk = bytes.subarray(index, index + chunkSize);
630
+ binary += String.fromCharCode(...chunk);
565
631
  }
566
- return false;
632
+ return btoa(binary);
567
633
  };
568
- if (dataTransfer) {
569
- if (clipboardPayload?.text) {
570
- try {
571
- dataTransfer.setData("text/plain", clipboardPayload.text);
572
- }
573
- catch (error) {
574
- console.warn("Clipboard bridge failed to attach text/plain", error);
575
- }
576
- }
577
- if (clipboardPayload?.html) {
578
- try {
579
- dataTransfer.setData("text/html", clipboardPayload.html);
580
- }
581
- catch (error) {
582
- console.warn("Clipboard bridge failed to attach text/html", error);
583
- }
584
- }
585
- if (Array.isArray(clipboardPayload?.files)) {
586
- for (const filePayload of clipboardPayload.files) {
587
- const file = createFileFromPayload(filePayload);
588
- if (file) {
634
+ const files = [];
635
+ if (typeof navigator.clipboard.read === "function") {
636
+ try {
637
+ const items = await navigator.clipboard.read();
638
+ for (const item of items) {
639
+ if (item.types.includes("text/html") && !result.html) {
640
+ const blob = await item.getType("text/html");
641
+ result.html = await blob.text();
642
+ }
643
+ if (item.types.includes("text/plain") && !result.text) {
644
+ const blob = await item.getType("text/plain");
645
+ result.text = await blob.text();
646
+ }
647
+ for (const type of item.types) {
648
+ if (type.startsWith("text/")) {
649
+ continue;
650
+ }
589
651
  try {
590
- dataTransfer.items.add(file);
652
+ const blob = await item.getType(type);
653
+ const buffer = await blob.arrayBuffer();
654
+ files.push({
655
+ name: `clipboard-file-${files.length + 1}`,
656
+ type,
657
+ lastModified: Date.now(),
658
+ data: arrayBufferToBase64(buffer),
659
+ });
591
660
  }
592
661
  catch (error) {
593
- console.warn("Clipboard bridge failed to attach file", error);
662
+ console.warn("[RemoteClipboard] Failed to serialize clipboard blob", { type, error });
594
663
  }
595
664
  }
596
665
  }
597
666
  }
598
- }
599
- let target = document.activeElement;
600
- if (!target || target === document.body) {
601
- target = document.body;
602
- }
603
- let pasteHandled = false;
604
- if (dataTransfer && target && typeof target.dispatchEvent === "function") {
605
- try {
606
- const clipboardEvent = new ClipboardEvent("paste", {
607
- clipboardData: dataTransfer,
608
- bubbles: true,
609
- cancelable: true,
610
- });
611
- pasteHandled = target.dispatchEvent(clipboardEvent);
612
- }
613
667
  catch (error) {
614
- console.warn("Clipboard bridge failed to dispatch paste event", error);
668
+ console.warn("[RemoteClipboard] navigator.clipboard.read failed", error);
615
669
  }
616
670
  }
617
- if (pasteHandled) {
618
- return;
619
- }
620
- if (clipboardPayload?.html) {
621
- const inserted = callLegacyExecCommand("insertHTML", clipboardPayload.html);
622
- if (inserted) {
623
- return;
624
- }
671
+ if (!result.text && typeof navigator.clipboard.readText === "function") {
625
672
  try {
626
- const selection = window.getSelection?.();
627
- if (selection && selection.rangeCount > 0) {
628
- const range = selection.getRangeAt(0);
629
- range.deleteContents();
630
- const fragment = range.createContextualFragment(clipboardPayload.html);
631
- range.insertNode(fragment);
632
- range.collapse(false);
633
- return;
673
+ const text = await navigator.clipboard.readText();
674
+ if (text) {
675
+ result.text = text;
634
676
  }
635
677
  }
636
678
  catch (error) {
637
- console.warn("Clipboard bridge could not insert HTML via Range APIs", error);
679
+ console.warn("[RemoteClipboard] navigator.clipboard.readText failed", error);
638
680
  }
639
681
  }
640
- if (clipboardPayload?.text) {
641
- const inserted = callLegacyExecCommand("insertText", clipboardPayload.text);
642
- if (inserted) {
643
- return;
644
- }
645
- try {
646
- const selection = window.getSelection?.();
647
- if (selection && selection.rangeCount > 0) {
648
- const range = selection.getRangeAt(0);
649
- range.deleteContents();
650
- range.insertNode(document.createTextNode(clipboardPayload.text));
651
- range.collapse(false);
652
- return;
653
- }
654
- }
655
- catch (error) {
656
- console.warn("Clipboard bridge could not insert text via Range APIs", error);
682
+ if (!result.text) {
683
+ const selection = window.getSelection?.()?.toString?.();
684
+ if (selection) {
685
+ result.text = selection;
657
686
  }
658
687
  }
659
- if (clipboardPayload?.text && target && "value" in target) {
660
- try {
661
- const input = target;
662
- const start = input.selectionStart ?? input.value.length ?? 0;
663
- const end = input.selectionEnd ?? input.value.length ?? 0;
664
- const value = input.value ?? "";
665
- const text = clipboardPayload.text;
666
- input.value = `${value.slice(0, start)}${text}${value.slice(end)}`;
667
- const caret = start + text.length;
668
- if (typeof input.setSelectionRange === "function") {
669
- input.setSelectionRange(caret, caret);
670
- }
671
- input.dispatchEvent(new Event("input", { bubbles: true }));
672
- }
673
- catch (error) {
674
- console.warn("Clipboard bridge failed to insert text into input", error);
675
- }
688
+ if (files.length > 0) {
689
+ result.files = files;
676
690
  }
677
- }, payload);
691
+ return result;
692
+ });
693
+ if (this.hasClipboardData(payload)) {
694
+ this.log("📋 Remote clipboard payload captured", {
695
+ hasText: !!payload.text,
696
+ hasHtml: !!payload.html,
697
+ fileCount: payload.files?.length ?? 0,
698
+ });
699
+ return payload;
700
+ }
701
+ this.log("â„šī¸ Remote clipboard read returned empty payload", {
702
+ selectedPageId: this._selectedPageId,
703
+ });
704
+ return null;
678
705
  }
679
706
  catch (error) {
680
- this.log("❌ Error applying clipboard payload", { error });
707
+ this.log("❌ Error reading remote clipboard", {
708
+ error: error instanceof Error ? error.message : String(error),
709
+ });
681
710
  throw error;
682
711
  }
683
712
  }
713
+ // Add a helper method to verify clipboard script is loaded
714
+ async verifyClipboardScript(stableTabId) {
715
+ const pageInfo = this.pages.get(stableTabId);
716
+ if (!pageInfo) {
717
+ this.log("âš ī¸ Cannot verify clipboard script - page not found", { stableTabId });
718
+ return false;
719
+ }
720
+ try {
721
+ const isLoaded = await pageInfo.page.evaluate(() => {
722
+ return (typeof window.__bvt_applyClipboardData === "function" && typeof window.__bvt_reportClipboard === "function");
723
+ });
724
+ this.log("🔍 Clipboard script verification", {
725
+ stableTabId,
726
+ isLoaded,
727
+ url: pageInfo.page.url(),
728
+ });
729
+ return isLoaded;
730
+ }
731
+ catch (error) {
732
+ this.log("❌ Error verifying clipboard script", {
733
+ stableTabId,
734
+ error: error instanceof Error ? error.message : String(error),
735
+ });
736
+ return false;
737
+ }
738
+ }
739
+ // Call this after navigation to ensure script is loaded
740
+ async ensureClipboardScriptLoaded(page) {
741
+ try {
742
+ const isLoaded = await page.evaluate(() => {
743
+ return typeof window.__bvt_applyClipboardData === "function";
744
+ });
745
+ if (!isLoaded) {
746
+ this.log("âš ī¸ Clipboard script not loaded, attempting to reload");
747
+ // The script should be in initScripts, so it will load on next navigation
748
+ // or you could manually inject it here
749
+ }
750
+ else {
751
+ this.log("✅ Clipboard script confirmed loaded");
752
+ }
753
+ }
754
+ catch (error) {
755
+ this.log("❌ Error checking clipboard script", { error });
756
+ }
757
+ }
684
758
  getSelectedPage() {
685
- const pageInfo = this._selectedPageId ? this.pages.get(this._selectedPageId) : null;
759
+ const pageInfo = this.getSelectedPageInfo();
686
760
  this.log("🔍 Getting selected page", {
687
761
  selectedPageId: this._selectedPageId,
688
762
  found: !!pageInfo,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev-blinq/cucumber_client",
3
- "version": "1.0.1624-dev",
3
+ "version": "1.0.1625-dev",
4
4
  "description": " ",
5
5
  "main": "bin/index.js",
6
6
  "types": "bin/index.d.ts",