@dev-blinq/cucumber_client 1.0.1387-stage → 1.0.1388-stage

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
  },
@@ -4,11 +4,10 @@ import { existsSync, readdirSync, readFileSync, rmSync } from "fs";
4
4
  import path from "path";
5
5
  import url from "url";
6
6
  import { getImplementedSteps, parseRouteFiles } from "./implemented_steps.js";
7
- import { NamesService, PublishService, RemoteBrowserService } from "./services.js";
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
 
@@ -331,7 +506,6 @@ export class BVTRecorder {
331
506
  });
332
507
  this.workspaceService = new PublishService(this.TOKEN);
333
508
  this.pageSet = new Set();
334
- this.pageMetaDataSet = new Set();
335
509
  this.lastKnownUrlPath = "";
336
510
  this.world = { attach: () => {} };
337
511
  this.shouldTakeScreenshot = true;
@@ -468,9 +642,12 @@ export class BVTRecorder {
468
642
  }
469
643
 
470
644
  async _initBrowser({ url }) {
471
- socketLogger.info("Only present in 1.0.1293-stage");
472
- this.#remoteDebuggerPort = await findAvailablePort();
473
- process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
645
+ if (process.env.CDP_LISTEN_PORT === undefined) {
646
+ this.#remoteDebuggerPort = await findAvailablePort();
647
+ process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
648
+ } else {
649
+ this.#remoteDebuggerPort = process.env.CDP_LISTEN_PORT;
650
+ }
474
651
 
475
652
  // this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
476
653
  this.world = { attach: () => {} };
@@ -534,6 +711,7 @@ export class BVTRecorder {
534
711
  this.sendEvent(this.events.browserStateSync, state);
535
712
  });
536
713
  }
714
+
537
715
  this.lastKnownUrlPath = this._updateUrlPath();
538
716
  const browser = await this.context.browser();
539
717
  this.browser = browser;
@@ -816,7 +994,6 @@ export class BVTRecorder {
816
994
  try {
817
995
  if (page.isClosed()) return;
818
996
  this.pageSet.add(page);
819
-
820
997
  await page.waitForLoadState("domcontentloaded");
821
998
 
822
999
  // add listener for frame navigation on new tab
@@ -910,7 +1087,7 @@ export class BVTRecorder {
910
1087
  [id, contextId, mode]
911
1088
  );
912
1089
 
913
- console.log(`Generated locators: for ${inputID}: `, JSON.stringify(locatorsObj));
1090
+ // console.log(`Generated locators: for ${inputID}: `, JSON.stringify(locatorsObj));
914
1091
  await newPage.close();
915
1092
  if (event.nestFrmLoc?.children) {
916
1093
  locatorsObj.nestFrmLoc = event.nestFrmLoc.children;
@@ -1537,25 +1714,55 @@ export class BVTRecorder {
1537
1714
 
1538
1715
  async applyClipboardPayload(message) {
1539
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
+
1540
1725
  if (!payload) {
1726
+ this.logger.warn("[BVTRecorder] No payload provided");
1541
1727
  return;
1542
1728
  }
1543
1729
 
1544
1730
  try {
1545
1731
  if (this.browserEmitter && typeof this.browserEmitter.applyClipboardPayload === "function") {
1732
+ this.logger.info("[BVTRecorder] Using RemoteBrowserService to apply clipboard");
1546
1733
  await this.browserEmitter.applyClipboardPayload(payload);
1547
1734
  return;
1548
1735
  }
1549
1736
 
1550
1737
  const activePage = this.browserEmitter?.getSelectedPage() ?? this.page;
1551
1738
  if (!activePage) {
1552
- this.logger.warn("No active page available to apply clipboard payload");
1739
+ this.logger.warn("[BVTRecorder] No active page available");
1553
1740
  return;
1554
1741
  }
1555
1742
 
1556
- 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
+ }
1557
1764
  } catch (error) {
1558
- this.logger.error("Error applying clipboard payload to page", error);
1765
+ this.logger.error("[BVTRecorder] Error applying clipboard payload", error);
1559
1766
  this.sendEvent(this.events.clipboardError, {
1560
1767
  message: "Failed to apply clipboard contents to the remote session",
1561
1768
  trigger: message?.trigger ?? "paste",
@@ -1563,6 +1770,152 @@ export class BVTRecorder {
1563
1770
  }
1564
1771
  }
1565
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
+
1566
1919
  async injectClipboardIntoPage(page, payload) {
1567
1920
  if (!page) {
1568
1921
  return;
@@ -1748,7 +2101,7 @@ export class BVTRecorder {
1748
2101
  }
1749
2102
  }
1750
2103
 
1751
- async closeTab({ pageId }) {
2104
+ async closeTab(pageId) {
1752
2105
  try {
1753
2106
  await this.browserEmitter?.closeTab(pageId);
1754
2107
  } catch (error) {
@@ -1760,7 +2113,7 @@ export class BVTRecorder {
1760
2113
  }
1761
2114
  }
1762
2115
 
1763
- async selectTab({ pageId }) {
2116
+ async selectTab(pageId) {
1764
2117
  try {
1765
2118
  await this.browserEmitter?.selectTab(pageId);
1766
2119
  } catch (error) {
@@ -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,
@@ -698,6 +772,6 @@ export class RemoteBrowserService extends EventEmitter {
698
772
  }
699
773
  this.pages.clear();
700
774
  this._selectedPageId = null;
701
- this.removeAllListeners();
775
+ this.removeAllListeners(); // Remove listeners on the emitter itself
702
776
  }
703
777
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev-blinq/cucumber_client",
3
- "version": "1.0.1387-stage",
3
+ "version": "1.0.1388-stage",
4
4
  "description": " ",
5
5
  "main": "bin/index.js",
6
6
  "types": "bin/index.d.ts",